From 8eedb3d3deed33fe3373a782bd5ad1c4f7fc5e29 Mon Sep 17 00:00:00 2001 From: Iams4kura <126048986+Iams4kura@users.noreply.github.com> Date: Fri, 4 Sep 2026 11:29:14 +0800 Subject: [PATCH] fix(llm): reject invalid tool arguments --- src/llm/providers/openai_compat.py | 7 ++++++- tests/test_provider_tools.py | 28 ++++++++++++++++++++++++++-- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/llm/providers/openai_compat.py b/src/llm/providers/openai_compat.py index a497eea2f..e1da6069e 100644 --- a/src/llm/providers/openai_compat.py +++ b/src/llm/providers/openai_compat.py @@ -22,12 +22,17 @@ def _to_openai_tool_call_param( tool_call: ToolCall, ) -> ChatCompletionMessageToolCallParam: """Convert a domain tool call to the OpenAI request schema.""" + try: + arguments = json.dumps(tool_call.arguments, allow_nan=False) + except (TypeError, ValueError) as exc: + raise ValueError("OpenAI tool call arguments must be valid JSON") from exc + return { "id": tool_call.id, "type": "function", "function": { "name": tool_call.name, - "arguments": json.dumps(tool_call.arguments), + "arguments": arguments, }, } diff --git a/tests/test_provider_tools.py b/tests/test_provider_tools.py index e0bea308b..bdba6866d 100644 --- a/tests/test_provider_tools.py +++ b/tests/test_provider_tools.py @@ -145,12 +145,36 @@ def test_openai_compatible_provider_serializes_assistant_tool_calls( {"role": "tool", "content": "result", "tool_call_id": "call_1"}, ] - client.completions.params = None + submitted_params = client.completions.params with pytest.raises(ValueError, match="tool messages require a tool_call_id"): provider.generate( LLMInput(messages=[Message(role=Role.TOOL, content="unlinked result")]) ) - assert client.completions.params is None + assert client.completions.params is submitted_params + + for invalid_arguments in ({"score": float("nan")}, {"value": object()}): + with pytest.raises( + ValueError, + match="tool call arguments must be valid JSON", + ): + provider.generate( + LLMInput( + messages=[ + Message( + role=Role.ASSISTANT, + content="", + tool_calls=[ + ToolCall( + id="call_invalid", + name="score", + arguments=invalid_arguments, + ) + ], + ) + ] + ) + ) + assert client.completions.params is submitted_params def test_openai_provider_can_be_constructed_without_credentials(monkeypatch):