mirror of
https://github.com/usestrix/strix.git
synced 2026-10-15 04:18:10 +00:00
fix(agents): accept empty view_agent_graph schema
This commit is contained in:
parent
0c702723aa
commit
87c4e477bb
2 changed files with 135 additions and 0 deletions
|
|
@ -117,6 +117,28 @@ def _render_completion_report(
|
|||
return "\n".join(lines)
|
||||
|
||||
|
||||
def _groq_compatible_empty_object_schema(schema: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Drop the empty ``required``/``properties`` pair Groq rejects.
|
||||
|
||||
A no-argument strict schema is ``{"type": "object", "properties": {},
|
||||
"required": []}``. Groq's tool validator treats a present ``required``
|
||||
with no ``properties`` as invalid (``required is present but properties
|
||||
is missing``) and rejects the whole request. Omitting both keys leaves an
|
||||
object that accepts no arguments: ``additionalProperties`` stays false, so
|
||||
extra arguments are still rejected, and tools that declare real parameters
|
||||
keep their ``required`` lists.
|
||||
"""
|
||||
properties = schema.get("properties")
|
||||
required = schema.get("required")
|
||||
if properties or required:
|
||||
return schema
|
||||
if properties is not None and not isinstance(properties, dict):
|
||||
return schema
|
||||
if required is not None and not isinstance(required, list):
|
||||
return schema
|
||||
return {key: value for key, value in schema.items() if key not in {"properties", "required"}}
|
||||
|
||||
|
||||
@function_tool(timeout=30)
|
||||
async def view_agent_graph(ctx: RunContextWrapper) -> str:
|
||||
"""Print the multi-agent tree — every agent, its parent, its status.
|
||||
|
|
@ -169,6 +191,11 @@ async def view_agent_graph(ctx: RunContextWrapper) -> str:
|
|||
)
|
||||
|
||||
|
||||
view_agent_graph.params_json_schema = _groq_compatible_empty_object_schema(
|
||||
view_agent_graph.params_json_schema
|
||||
)
|
||||
|
||||
|
||||
@function_tool(timeout=30)
|
||||
async def send_message_to_agent(
|
||||
ctx: RunContextWrapper,
|
||||
|
|
|
|||
108
tests/test_view_agent_graph_schema.py
Normal file
108
tests/test_view_agent_graph_schema.py
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
"""Groq rejects a no-argument tool schema that lists ``required`` without properties.
|
||||
|
||||
``view_agent_graph`` takes only the run context, so its generated schema is an
|
||||
empty object. Groq's validator fails that shape with ``required is present but
|
||||
properties is missing`` and drops the whole request. The published schema must
|
||||
omit that pair without turning strict validation off for tools that do take
|
||||
arguments.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import dataclasses
|
||||
import json
|
||||
from typing import Any, cast
|
||||
|
||||
import pytest
|
||||
from agents.models.chatcmpl_converter import Converter
|
||||
from agents.tool import FunctionTool
|
||||
from agents.tool_context import ToolContext
|
||||
|
||||
from strix.agents.factory import build_strix_agent
|
||||
from strix.core.agents import AgentCoordinator
|
||||
from strix.tools.agents_graph.tools import (
|
||||
_groq_compatible_empty_object_schema,
|
||||
send_message_to_agent,
|
||||
view_agent_graph,
|
||||
)
|
||||
|
||||
|
||||
def test_view_agent_graph_schema_omits_empty_required_and_properties() -> None:
|
||||
schema = view_agent_graph.params_json_schema
|
||||
|
||||
assert view_agent_graph.strict_json_schema is True
|
||||
assert schema["type"] == "object"
|
||||
assert schema["additionalProperties"] is False
|
||||
assert "required" not in schema
|
||||
assert "properties" not in schema
|
||||
|
||||
|
||||
def test_published_chat_completions_schema_has_no_empty_required() -> None:
|
||||
"""The schema Groq receives is the chat-completions function parameters."""
|
||||
parameters = Converter.tool_to_openai(view_agent_graph)["function"]["parameters"]
|
||||
|
||||
assert parameters["type"] == "object"
|
||||
assert parameters["additionalProperties"] is False
|
||||
assert "required" not in parameters
|
||||
assert "properties" not in parameters
|
||||
assert Converter.tool_to_openai(view_agent_graph)["function"]["strict"] is True
|
||||
|
||||
|
||||
def test_argument_tools_keep_required_properties() -> None:
|
||||
schema = send_message_to_agent.params_json_schema
|
||||
|
||||
assert send_message_to_agent.strict_json_schema is True
|
||||
assert "target_agent_id" in schema["properties"]
|
||||
assert "target_agent_id" in schema["required"]
|
||||
assert "message" in schema["required"]
|
||||
|
||||
|
||||
def test_groq_schema_fix_does_not_strip_declared_parameters() -> None:
|
||||
original = {
|
||||
"type": "object",
|
||||
"properties": {"target_agent_id": {"type": "string"}},
|
||||
"required": ["target_agent_id"],
|
||||
"additionalProperties": False,
|
||||
}
|
||||
|
||||
assert _groq_compatible_empty_object_schema(original) == original
|
||||
# A required list with no properties is not the empty no-arg shape; leave
|
||||
# it alone rather than dropping a constraint this helper does not own.
|
||||
mismatched = {"type": "object", "required": ["target_agent_id"]}
|
||||
assert _groq_compatible_empty_object_schema(mismatched) is mismatched
|
||||
|
||||
|
||||
def test_agent_copy_keeps_the_groq_compatible_schema() -> None:
|
||||
agent = build_strix_agent(is_root=True)
|
||||
tool = next(t for t in agent.tools if t.name == "view_agent_graph")
|
||||
assert isinstance(tool, FunctionTool)
|
||||
|
||||
assert tool.strict_json_schema is True
|
||||
assert "required" not in tool.params_json_schema
|
||||
assert "properties" not in tool.params_json_schema
|
||||
# Disabling strict for another route copies the tool; that copy must not
|
||||
# reintroduce the rejected empty required/properties pair.
|
||||
copied = dataclasses.replace(tool, strict_json_schema=False)
|
||||
assert "required" not in copied.params_json_schema
|
||||
assert "properties" not in copied.params_json_schema
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_view_agent_graph_still_returns_the_tree() -> None:
|
||||
coordinator = AgentCoordinator()
|
||||
await coordinator.register("root", "strix", parent_id=None)
|
||||
await coordinator.register("child", "Validator", parent_id="root")
|
||||
ctx = ToolContext(
|
||||
context={"coordinator": coordinator, "agent_id": "root"},
|
||||
tool_name=view_agent_graph.name,
|
||||
tool_call_id="call-1",
|
||||
tool_arguments="{}",
|
||||
)
|
||||
|
||||
result = cast("dict[str, Any]", json.loads(await view_agent_graph.on_invoke_tool(ctx, "{}")))
|
||||
|
||||
assert result["success"] is True
|
||||
assert "strix (root)" in result["graph_structure"]
|
||||
assert "Validator (child)" in result["graph_structure"]
|
||||
assert "← you" in result["graph_structure"]
|
||||
assert result["summary"]["total"] == 2
|
||||
Loading…
Add table
Reference in a new issue