-
Notifications
You must be signed in to change notification settings - Fork 4.5k
fix: tolerate string-encoded skills param for LLM provider compatibility #771
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -2,13 +2,15 @@ | |||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| from __future__ import annotations | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| import json | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| from agents import RunContextWrapper, function_tool | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| from strix.skills import load_skills, validate_requested_skills | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| @function_tool(timeout=10) | ||||||||||||||||||||||||||||||||||||||||||
| async def load_skill(ctx: RunContextWrapper, skills: list[str]) -> str: | ||||||||||||||||||||||||||||||||||||||||||
| @function_tool(timeout=10, strict_mode=False) | ||||||||||||||||||||||||||||||||||||||||||
| async def load_skill(ctx: RunContextWrapper, skills: str | list[str]) -> str: | ||||||||||||||||||||||||||||||||||||||||||
| """Return the markdown body of one or more skills as reference material. | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
| Use this when you need exact syntax / workflow / payload guidance | ||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -25,6 +27,23 @@ async def load_skill(ctx: RunContextWrapper, skills: list[str]) -> str: | |||||||||||||||||||||||||||||||||||||||||
| ``strix/skills/<category>/<name>.md``. | ||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||
| del ctx | ||||||||||||||||||||||||||||||||||||||||||
| # Tolerate LLM providers that pass array params as JSON-encoded strings. | ||||||||||||||||||||||||||||||||||||||||||
| # Validate decoded shape: reject anything that isn't a list of strings. | ||||||||||||||||||||||||||||||||||||||||||
| if isinstance(skills, str): | ||||||||||||||||||||||||||||||||||||||||||
| original_skills = skills | ||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||
| decoded_skills = json.loads(skills) | ||||||||||||||||||||||||||||||||||||||||||
| except json.JSONDecodeError: | ||||||||||||||||||||||||||||||||||||||||||
| skills = [s.strip() for s in skills.split(",") if s.strip()] | ||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+32
to
+37
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When
Suggested change
Prompt To Fix With AIThis is a comment left during a code review.
Path: strix/tools/load_skill/tool.py
Line: 31-35
Comment:
**Decoded JSON Bypasses List Shape**
When `skills` contains valid non-array JSON, decoding succeeds without enforcing the downstream `list[str]` contract. A JSON string is split into characters, an object silently becomes its keys, and mixed element types can make validation raise, so `load_skill` can load the wrong selection or terminate the tool call unexpectedly.
```suggestion
if isinstance(skills, str):
original_skills = skills
try:
decoded_skills = json.loads(skills)
except json.JSONDecodeError:
skills = [s.strip() for s in skills.split(",") if s.strip()]
else:
if isinstance(decoded_skills, str):
skills = [decoded_skills]
elif isinstance(decoded_skills, list) and all(
isinstance(skill, str) for skill in decoded_skills
):
skills = decoded_skills
else:
skills = [original_skills]
```
How can I resolve this? If you propose a fix, please make it concise. |
||||||||||||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||||||||||||
| if isinstance(decoded_skills, str): | ||||||||||||||||||||||||||||||||||||||||||
| skills = [decoded_skills] | ||||||||||||||||||||||||||||||||||||||||||
| elif isinstance(decoded_skills, list) and all( | ||||||||||||||||||||||||||||||||||||||||||
| isinstance(skill, str) for skill in decoded_skills | ||||||||||||||||||||||||||||||||||||||||||
| ): | ||||||||||||||||||||||||||||||||||||||||||
| skills = decoded_skills | ||||||||||||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||||||||||||
| skills = [original_skills] | ||||||||||||||||||||||||||||||||||||||||||
| requested = list(skills or []) | ||||||||||||||||||||||||||||||||||||||||||
| err = validate_requested_skills(requested) | ||||||||||||||||||||||||||||||||||||||||||
| if err: | ||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When a provider sends valid JSON that is not a string array,
json.loads()still succeeds. A JSON string is expanded into individual characters bylist(skills), an object is treated as its keys, and a mixed array can raise insidevalidate_requested_skills()while formatting heterogeneous values, socreate_agentcan misapply skills or fail instead of returning a controlled validation error.Prompt To Fix With AI