diff --git a/run_agent.py b/run_agent.py index e696ded0..29f01120 100644 --- a/run_agent.py +++ b/run_agent.py @@ -5582,6 +5582,12 @@ class AIAgent: invalid_json_args = [] for tc in assistant_message.tool_calls: args = tc.function.arguments + if isinstance(args, (dict, list)): + tc.function.arguments = json.dumps(args) + continue + if args is not None and not isinstance(args, str): + tc.function.arguments = str(args) + args = tc.function.arguments # Treat empty/whitespace strings as empty object if not args or not args.strip(): tc.function.arguments = "{}" diff --git a/tests/test_dict_tool_call_args.py b/tests/test_dict_tool_call_args.py new file mode 100644 index 00000000..e8b4d70f --- /dev/null +++ b/tests/test_dict_tool_call_args.py @@ -0,0 +1,72 @@ +import json +from types import SimpleNamespace + + +def _tool_call(name: str, arguments): + return SimpleNamespace( + id="call_1", + type="function", + function=SimpleNamespace(name=name, arguments=arguments), + ) + + +def _response_with_tool_call(arguments): + assistant = SimpleNamespace( + content=None, + reasoning=None, + tool_calls=[_tool_call("read_file", arguments)], + ) + choice = SimpleNamespace(message=assistant, finish_reason="tool_calls") + return SimpleNamespace(choices=[choice], usage=None) + + +class _FakeChatCompletions: + def __init__(self): + self.calls = 0 + + def create(self, **kwargs): + self.calls += 1 + if self.calls == 1: + return _response_with_tool_call({"path": "README.md"}) + return SimpleNamespace( + choices=[ + SimpleNamespace( + message=SimpleNamespace(content="done", reasoning=None, tool_calls=[]), + finish_reason="stop", + ) + ], + usage=None, + ) + + +class _FakeClient: + def __init__(self): + self.chat = SimpleNamespace(completions=_FakeChatCompletions()) + + +def test_tool_call_validation_accepts_dict_arguments(monkeypatch): + from run_agent import AIAgent + + monkeypatch.setattr("run_agent.OpenAI", lambda **kwargs: _FakeClient()) + monkeypatch.setattr( + "run_agent.get_tool_definitions", + lambda *args, **kwargs: [{"function": {"name": "read_file"}}], + ) + monkeypatch.setattr( + "run_agent.handle_function_call", + lambda name, args, task_id=None, **kwargs: json.dumps({"ok": True, "args": args}), + ) + + agent = AIAgent( + model="test-model", + api_key="test-key", + base_url="http://localhost:8080/v1", + platform="cli", + max_iterations=3, + quiet_mode=True, + skip_memory=True, + ) + + result = agent.run_conversation("read the file") + + assert result["final_response"] == "done"