mirror of
https://github.com/deepset-ai/haystack.git
synced 2026-01-09 05:37:25 +00:00
feat: Relax requirement for creating a ToolCallDelta dataclass (#9582)
* Relax our requirement for ToolCallDelta to better match ChoiceDeltaToolCall and ChoiceDeltaToolCallFunction from OpenAI * Add reno * Update tests
This commit is contained in:
parent
9fd552f906
commit
16fc41cd95
@ -30,12 +30,6 @@ class ToolCallDelta:
|
||||
arguments: Optional[str] = field(default=None)
|
||||
id: Optional[str] = field(default=None) # noqa: A003
|
||||
|
||||
def __post_init__(self):
|
||||
# NOTE: We allow for name and arguments to both be present because some providers like Mistral provide the
|
||||
# name and full arguments in one chunk
|
||||
if self.tool_name is None and self.arguments is None:
|
||||
raise ValueError("At least one of tool_name or arguments must be provided.")
|
||||
|
||||
|
||||
@dataclass
|
||||
class ComponentInfo:
|
||||
|
||||
@ -0,0 +1,4 @@
|
||||
---
|
||||
enhancements:
|
||||
- |
|
||||
We relaxed the requirement that in ToolCallDelta (introduced in Haystack 2.15) which required the parameters arguments or name to be populated to be able to create a ToolCallDelta dataclass. We remove this requirement to be more in line with OpenAI's SDK and since this was causing errors for some hosted versions of open source models following OpenAI's SDK specification.
|
||||
@ -1177,6 +1177,32 @@ class TestChatCompletionChunkConversion:
|
||||
assert stream_chunk == haystack_chunk
|
||||
previous_chunks.append(stream_chunk)
|
||||
|
||||
def test_convert_chat_completion_chunk_with_empty_tool_calls(self):
|
||||
# This can happen with some LLM providers where tool calls are not present but the pydantic models are still
|
||||
# initialized.
|
||||
chunk = ChatCompletionChunk(
|
||||
id="chatcmpl-BC1y4wqIhe17R8sv3lgLcWlB4tXCw",
|
||||
choices=[
|
||||
chat_completion_chunk.Choice(
|
||||
delta=chat_completion_chunk.ChoiceDelta(
|
||||
tool_calls=[ChoiceDeltaToolCall(index=0, function=ChoiceDeltaToolCallFunction())]
|
||||
),
|
||||
index=0,
|
||||
)
|
||||
],
|
||||
created=1742207200,
|
||||
model="gpt-4o-mini-2024-07-18",
|
||||
object="chat.completion.chunk",
|
||||
)
|
||||
result = _convert_chat_completion_chunk_to_streaming_chunk(chunk=chunk, previous_chunks=[])
|
||||
assert result.content == ""
|
||||
assert result.start is False
|
||||
assert result.tool_calls == [ToolCallDelta(index=0)]
|
||||
assert result.tool_call_result is None
|
||||
assert result.index == 0
|
||||
assert result.meta["model"] == "gpt-4o-mini-2024-07-18"
|
||||
assert result.meta["received_at"] is not None
|
||||
|
||||
def test_handle_stream_response(self, chat_completion_chunks):
|
||||
openai_chunks = chat_completion_chunks
|
||||
comp = OpenAIChatGenerator(api_key=Secret.from_token("test-api-key"))
|
||||
|
||||
@ -388,6 +388,123 @@ def test_convert_streaming_chunk_to_chat_message_two_tool_calls_in_same_chunk():
|
||||
assert result.tool_calls[1].arguments == {"city": "Berlin"}
|
||||
|
||||
|
||||
def test_convert_streaming_chunk_to_chat_message_empty_tool_call_delta():
|
||||
chunks = [
|
||||
StreamingChunk(
|
||||
content="",
|
||||
meta={
|
||||
"model": "gpt-4o-mini-2024-07-18",
|
||||
"index": 0,
|
||||
"tool_calls": None,
|
||||
"finish_reason": None,
|
||||
"received_at": "2025-02-19T16:02:55.910076",
|
||||
},
|
||||
component_info=ComponentInfo(name="test", type="test"),
|
||||
),
|
||||
StreamingChunk(
|
||||
content="",
|
||||
meta={
|
||||
"model": "gpt-4o-mini-2024-07-18",
|
||||
"index": 0,
|
||||
"tool_calls": [
|
||||
chat_completion_chunk.ChoiceDeltaToolCall(
|
||||
index=0,
|
||||
id="call_ZOj5l67zhZOx6jqjg7ATQwb6",
|
||||
function=chat_completion_chunk.ChoiceDeltaToolCallFunction(
|
||||
arguments='{"query":', name="rag_pipeline_tool"
|
||||
),
|
||||
type="function",
|
||||
)
|
||||
],
|
||||
"finish_reason": None,
|
||||
"received_at": "2025-02-19T16:02:55.913919",
|
||||
},
|
||||
component_info=ComponentInfo(name="test", type="test"),
|
||||
index=0,
|
||||
start=True,
|
||||
tool_calls=[
|
||||
ToolCallDelta(
|
||||
id="call_ZOj5l67zhZOx6jqjg7ATQwb6", tool_name="rag_pipeline_tool", arguments='{"query":', index=0
|
||||
)
|
||||
],
|
||||
),
|
||||
StreamingChunk(
|
||||
content="",
|
||||
meta={
|
||||
"model": "gpt-4o-mini-2024-07-18",
|
||||
"index": 0,
|
||||
"tool_calls": [
|
||||
chat_completion_chunk.ChoiceDeltaToolCall(
|
||||
index=0,
|
||||
function=chat_completion_chunk.ChoiceDeltaToolCallFunction(
|
||||
arguments=' "Where does Mark live?"}'
|
||||
),
|
||||
)
|
||||
],
|
||||
"finish_reason": None,
|
||||
"received_at": "2025-02-19T16:02:55.924420",
|
||||
},
|
||||
component_info=ComponentInfo(name="test", type="test"),
|
||||
index=0,
|
||||
tool_calls=[ToolCallDelta(arguments=' "Where does Mark live?"}', index=0)],
|
||||
),
|
||||
StreamingChunk(
|
||||
content="",
|
||||
meta={
|
||||
"model": "gpt-4o-mini-2024-07-18",
|
||||
"index": 0,
|
||||
"tool_calls": [
|
||||
chat_completion_chunk.ChoiceDeltaToolCall(
|
||||
index=0, function=chat_completion_chunk.ChoiceDeltaToolCallFunction()
|
||||
)
|
||||
],
|
||||
"finish_reason": "tool_calls",
|
||||
"received_at": "2025-02-19T16:02:55.948772",
|
||||
},
|
||||
tool_calls=[ToolCallDelta(index=0)],
|
||||
component_info=ComponentInfo(name="test", type="test"),
|
||||
finish_reason="tool_calls",
|
||||
index=0,
|
||||
),
|
||||
StreamingChunk(
|
||||
content="",
|
||||
meta={
|
||||
"model": "gpt-4o-mini-2024-07-18",
|
||||
"index": 0,
|
||||
"tool_calls": None,
|
||||
"finish_reason": None,
|
||||
"received_at": "2025-02-19T16:02:55.948772",
|
||||
"usage": {
|
||||
"completion_tokens": 42,
|
||||
"prompt_tokens": 282,
|
||||
"total_tokens": 324,
|
||||
"completion_tokens_details": {
|
||||
"accepted_prediction_tokens": 0,
|
||||
"audio_tokens": 0,
|
||||
"reasoning_tokens": 0,
|
||||
"rejected_prediction_tokens": 0,
|
||||
},
|
||||
"prompt_tokens_details": {"audio_tokens": 0, "cached_tokens": 0},
|
||||
},
|
||||
},
|
||||
component_info=ComponentInfo(name="test", type="test"),
|
||||
),
|
||||
]
|
||||
|
||||
# Convert chunks to a chat message
|
||||
result = _convert_streaming_chunks_to_chat_message(chunks=chunks)
|
||||
|
||||
assert not result.texts
|
||||
assert not result.text
|
||||
|
||||
# Verify both tool calls were found and processed
|
||||
assert len(result.tool_calls) == 1
|
||||
assert result.tool_calls[0].id == "call_ZOj5l67zhZOx6jqjg7ATQwb6"
|
||||
assert result.tool_calls[0].tool_name == "rag_pipeline_tool"
|
||||
assert result.tool_calls[0].arguments == {"query": "Where does Mark live?"}
|
||||
assert result.meta["finish_reason"] == "tool_calls"
|
||||
|
||||
|
||||
def test_print_streaming_chunk_content_only():
|
||||
chunk = StreamingChunk(
|
||||
content="Hello, world!",
|
||||
|
||||
@ -99,11 +99,6 @@ def test_tool_call_delta():
|
||||
assert tool_call.index == 0
|
||||
|
||||
|
||||
def test_tool_call_delta_with_missing_fields():
|
||||
with pytest.raises(ValueError):
|
||||
_ = ToolCallDelta(id="123", index=0)
|
||||
|
||||
|
||||
def test_create_chunk_with_finish_reason():
|
||||
"""Test creating a chunk with the new finish_reason field."""
|
||||
chunk = StreamingChunk(content="Test content", finish_reason="stop")
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user