diff --git a/haystack/dataclasses/chat_message.py b/haystack/dataclasses/chat_message.py index 39da893f66..6547b27919 100644 --- a/haystack/dataclasses/chat_message.py +++ b/haystack/dataclasses/chat_message.py @@ -792,10 +792,13 @@ def from_openai_dict_format(cls, message: dict[str, Any]) -> "ChatMessage": if tool_calls: haystack_tool_calls = [] for tc in tool_calls: + # Zero-argument tool calls from OpenAI-compatible servers may send an + # empty string, null, or omit `arguments` entirely; treat all as {}. + raw_arguments = tc["function"].get("arguments") haystack_tc = ToolCall( id=tc.get("id"), tool_name=tc["function"]["name"], - arguments=json.loads(tc["function"]["arguments"]), + arguments=json.loads(raw_arguments) if raw_arguments else {}, ) haystack_tool_calls.append(haystack_tc) return cls.from_assistant(text=content, name=name, tool_calls=haystack_tool_calls) diff --git a/releasenotes/notes/fix-openai-toolcall-empty-arguments-53c5621622bc1866.yaml b/releasenotes/notes/fix-openai-toolcall-empty-arguments-53c5621622bc1866.yaml new file mode 100644 index 0000000000..b09d7021b6 --- /dev/null +++ b/releasenotes/notes/fix-openai-toolcall-empty-arguments-53c5621622bc1866.yaml @@ -0,0 +1,8 @@ +--- +fixes: + - | + Fixed ``ChatMessage.from_openai_dict_format`` crashing on zero-argument tool + calls. OpenAI-compatible servers (vLLM, llama.cpp, Ollama, LM Studio, ...) may + send an empty string or omit the ``arguments`` field for a tool call that takes + no arguments; this previously raised ``json.JSONDecodeError`` or ``KeyError``. + Such tool calls are now parsed into a ``ToolCall`` with empty ``arguments``. diff --git a/test/dataclasses/test_chat_message.py b/test/dataclasses/test_chat_message.py index 52c600446e..22193802dc 100644 --- a/test/dataclasses/test_chat_message.py +++ b/test/dataclasses/test_chat_message.py @@ -966,6 +966,28 @@ def test_from_openai_dict_format_assistant_message_with_tool_calls(self): assert tool_call.tool_name == "get_weather" assert tool_call.arguments == {"location": "Berlin"} + def test_from_openai_dict_format_tool_call_with_empty_arguments(self): + # OpenAI-compatible servers (vLLM, llama.cpp, Ollama, ...) emit an empty + # string for a zero-argument tool call; it must not crash. + openai_msg = { + "role": "assistant", + "content": None, + "tool_calls": [{"id": "call_1", "function": {"name": "now", "arguments": ""}}], + } + message = ChatMessage.from_openai_dict_format(openai_msg) + assert message.tool_call == ToolCall(id="call_1", tool_name="now", arguments={}) + + def test_from_openai_dict_format_tool_call_with_missing_arguments(self): + # Some servers omit the `arguments` key entirely for zero-argument calls. + openai_msg = { + "role": "assistant", + "content": None, + "tool_calls": [{"id": "call_1", "function": {"name": "now"}}], + } + message = ChatMessage.from_openai_dict_format(openai_msg) + assert message.tool_call is not None + assert message.tool_call.arguments == {} + def test_from_openai_dict_format_tool_message(self): openai_msg = {"role": "tool", "content": "The weather is sunny", "tool_call_id": "call_123"} message = ChatMessage.from_openai_dict_format(openai_msg)