mirror of
https://github.com/tiennm99/litellm.git
synced 2026-08-05 12:23:28 +00:00
fix(responses): use output_index for parallel tool call streaming indices
Fixes #21331 — the Responses API streaming bridge hardcoded index=0 for all tool call chunks, making parallel tool calls indistinguishable. Now reads output_index from the Responses API chunk instead.
This commit is contained in:
@@ -960,9 +960,10 @@ class OpenAiResponsesToChatCompletionStreamIterator(BaseModelResponseIterator):
|
||||
provider_specific_fields
|
||||
)
|
||||
|
||||
tool_call_index = parsed_chunk.get("output_index", 0)
|
||||
tool_call_chunk = ChatCompletionToolCallChunk(
|
||||
id=output_item.get("call_id"),
|
||||
index=0,
|
||||
index=tool_call_index,
|
||||
type="function",
|
||||
function=function_chunk,
|
||||
)
|
||||
@@ -983,6 +984,7 @@ class OpenAiResponsesToChatCompletionStreamIterator(BaseModelResponseIterator):
|
||||
elif event_type == "response.function_call_arguments.delta":
|
||||
content_part: Optional[str] = parsed_chunk.get("delta", None)
|
||||
if content_part:
|
||||
tool_call_index = parsed_chunk.get("output_index", 0)
|
||||
return ModelResponseStream(
|
||||
choices=[
|
||||
StreamingChoices(
|
||||
@@ -991,7 +993,7 @@ class OpenAiResponsesToChatCompletionStreamIterator(BaseModelResponseIterator):
|
||||
tool_calls=[
|
||||
ChatCompletionToolCallChunk(
|
||||
id=None,
|
||||
index=0,
|
||||
index=tool_call_index,
|
||||
type="function",
|
||||
function=ChatCompletionToolCallFunctionChunk(
|
||||
name=None, arguments=content_part
|
||||
@@ -1033,9 +1035,10 @@ class OpenAiResponsesToChatCompletionStreamIterator(BaseModelResponseIterator):
|
||||
provider_specific_fields
|
||||
)
|
||||
|
||||
tool_call_index = parsed_chunk.get("output_index", 0)
|
||||
tool_call_chunk = ChatCompletionToolCallChunk(
|
||||
id=output_item.get("call_id"),
|
||||
index=0,
|
||||
index=tool_call_index,
|
||||
type="function",
|
||||
function=function_chunk,
|
||||
)
|
||||
|
||||
+91
@@ -1278,3 +1278,94 @@ def test_transform_response_preserves_annotations():
|
||||
assert result.usage.total_tokens == 30
|
||||
|
||||
print("✓ Annotations from Responses API are correctly preserved in Chat Completions format")
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Tests for issue #21331: Parallel tool call indices in streaming
|
||||
# =============================================================================
|
||||
|
||||
|
||||
def test_streaming_parallel_tool_calls_have_distinct_indices():
|
||||
"""
|
||||
Test that parallel tool calls get distinct indices matching output_index
|
||||
from the Responses API streaming chunks.
|
||||
|
||||
Regression test for issue #21331 where all tool calls were emitted with
|
||||
index=0, making it impossible to distinguish parallel calls.
|
||||
"""
|
||||
from litellm.completion_extras.litellm_responses_transformation.transformation import (
|
||||
OpenAiResponsesToChatCompletionStreamIterator,
|
||||
)
|
||||
|
||||
# Simulate two parallel tool calls with output_index 0 and 1
|
||||
chunks = [
|
||||
{
|
||||
"type": "response.output_item.added",
|
||||
"output_index": 0,
|
||||
"item": {
|
||||
"type": "function_call",
|
||||
"id": "fc_001",
|
||||
"call_id": "call_abc",
|
||||
"name": "get_weather",
|
||||
"arguments": "",
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "response.function_call_arguments.delta",
|
||||
"output_index": 0,
|
||||
"item_id": "fc_001",
|
||||
"delta": '{"city": "SF"}',
|
||||
},
|
||||
{
|
||||
"type": "response.output_item.done",
|
||||
"output_index": 0,
|
||||
"item": {
|
||||
"type": "function_call",
|
||||
"id": "fc_001",
|
||||
"call_id": "call_abc",
|
||||
"name": "get_weather",
|
||||
"arguments": '{"city": "SF"}',
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "response.output_item.added",
|
||||
"output_index": 1,
|
||||
"item": {
|
||||
"type": "function_call",
|
||||
"id": "fc_002",
|
||||
"call_id": "call_def",
|
||||
"name": "get_weather",
|
||||
"arguments": "",
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "response.function_call_arguments.delta",
|
||||
"output_index": 1,
|
||||
"item_id": "fc_002",
|
||||
"delta": '{"city": "NY"}',
|
||||
},
|
||||
{
|
||||
"type": "response.output_item.done",
|
||||
"output_index": 1,
|
||||
"item": {
|
||||
"type": "function_call",
|
||||
"id": "fc_002",
|
||||
"call_id": "call_def",
|
||||
"name": "get_weather",
|
||||
"arguments": '{"city": "NY"}',
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
for chunk in chunks:
|
||||
result = OpenAiResponsesToChatCompletionStreamIterator.translate_responses_chunk_to_openai_stream(
|
||||
chunk
|
||||
)
|
||||
expected_index = chunk["output_index"]
|
||||
for choice in result.choices:
|
||||
if choice.delta.tool_calls:
|
||||
for tc in choice.delta.tool_calls:
|
||||
assert tc.index == expected_index, (
|
||||
f"Event {chunk['type']}: expected tool_call.index={expected_index}, "
|
||||
f"got {tc.index}"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user