mirror of
https://github.com/usestrix/strix.git
synced 2026-11-06 07:47:28 +00:00
Some LLM providers (e.g. qwen3.7-max via DashScope OpenAI-compatible endpoint) serialize array-typed function call parameters as JSON-encoded strings instead of native JSON arrays. This causes Pydantic validation to reject the skills parameter before the function body executes, making every create_agent and load_skill call fail. Changes: - Widen skills type annotation: list[str] -> str | list[str] - Add strict_mode=False to @function_tool decorator - Parse string inputs in function body (json.loads with comma-split fallback) Tested with qwen3.7-max: create_agent successfully spawns child agents that execute 23+ exec_command calls for SQL injection testing. Closes #770
44 lines
1.7 KiB
Python
44 lines
1.7 KiB
Python
"""``load_skill`` — fetch skill reference material into the conversation."""
|
|
|
|
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, 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
|
|
right before acting on a technology that wasn't preloaded for your
|
|
agent. The skill content lands inline as a tool result — no
|
|
permanent prompt change, just in-conversation reference.
|
|
|
|
For permanent skill assignment, pass ``skills=[…]`` to
|
|
``create_agent`` when spawning a specialist child instead.
|
|
|
|
Args:
|
|
skills: List of skill names (e.g. ``["xss", "sql_injection"]``).
|
|
Max 5. Names match the bare files under
|
|
``strix/skills/<category>/<name>.md``.
|
|
"""
|
|
del ctx
|
|
# Tolerate LLM providers that pass array params as JSON-encoded strings
|
|
if isinstance(skills, str):
|
|
try:
|
|
skills = json.loads(skills)
|
|
except json.JSONDecodeError:
|
|
skills = [s.strip() for s in skills.split(",") if s.strip()]
|
|
requested = list(skills or [])
|
|
err = validate_requested_skills(requested)
|
|
if err:
|
|
return f"load_skill: {err}"
|
|
contents = load_skills(requested)
|
|
if not contents:
|
|
return "load_skill: no content loaded for requested skills."
|
|
sections = [f"## Skill: {name}\n\n{body}" for name, body in contents.items()]
|
|
return "\n\n---\n\n".join(sections)
|