diff --git a/backend/src/agents/expert.py b/backend/src/agents/expert.py index f886b91..c414e18 100644 --- a/backend/src/agents/expert.py +++ b/backend/src/agents/expert.py @@ -1,5 +1,6 @@ import inference from agent import Agent, AgentField +from tool import ToolSpec import asyncio @@ -43,18 +44,24 @@ async def chat_completion_loop(app, run, messages, model_id=None, tools=None): # Avoid infinite loops from server failures if len(messages) == pre_len: break + cancel_tools = False for tc in tool_calls: tool = next((t for t in tools if t.name == tc.tool_name), None) if tool is None: print(f"[agent] unknown tool name: {tc.tool_name}") continue if execute_tools: - tc.result = await tool.execute_async(**tc.arguments) + # Only known 'non-callable' tool spec + if isinstance(tool, ToolSpec) and not tool.func: + cancel_tools = True + tc.tool = tool + else: + tc.result = await tool.execute_async(**tc.arguments) else: tc.tool = tool - # If not calling, respond to end the turn - if not (tool_calls and execute_tools) and turn_response: break + # If not executing tools, respond to end the turn + if not (tool_calls and execute_tools) and turn_response or cancel_tools: break run.close() @@ -87,8 +94,6 @@ class ExpertAgent(Agent): if run.input.get('tool_policy') != 'caller': tools += self.get_tools(run) - print("Final tool object: " + repr(run.input.get('tools', []))) - # Build the initial message list messages = inference.build_messages( system_prompt=self.config.get("system_prompt", ""), diff --git a/backend/src/app_mixins/agent.py b/backend/src/app_mixins/agent.py index 84bde1d..7cd6806 100644 --- a/backend/src/app_mixins/agent.py +++ b/backend/src/app_mixins/agent.py @@ -186,6 +186,38 @@ class AgentMixin: # inference.Message objects. get_agent_control is awaited lazily on first # iteration, keeping the control object alive for the generator's # lifetime. + + def normalize_tool(t): + # this function makes a lot of sense in ToolSpec itself... + from tool import ToolSpec, ToolParameter + if isinstance(t, dict): + params = [] + + d = t.get('parameters', {}) + required = d.get('required', []) + props = d.get('properties', {}) + + for key, p in props.items(): + params.append(ToolParameter( + name=key, + type=p.get('type'), + description=p.get('description'), + required=key in required, + )) + return ToolSpec( + name=t['name'], + description=t['description'], + parameters=params, + ) + else: + return t + + if 'tools' in options: + options = {**options} + options['tools'] = [ + normalize_tool(t) for t in options['tools'] + ] + control = await self.get_agent_control(agent_id) async for msg in control.prompt(instruction, input, **options): yield msg