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:
Chesars
2026-02-16 18:00:42 -03:00
parent bb17ca15e9
commit 0664ec51d8
2 changed files with 97 additions and 3 deletions
@@ -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,
)
@@ -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}"
)