Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion haystack/dataclasses/chat_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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``.
22 changes: 22 additions & 0 deletions test/dataclasses/test_chat_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading