mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-14 23:21:35 +00:00
fix(responses-bridge): latch a single chunk id across bridged streaming chunks
This commit is contained in:
parent
60729f733e
commit
dd8b4b1d54
2 changed files with 79 additions and 1 deletions
|
|
@ -1074,6 +1074,7 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge):
|
|||
class OpenAiResponsesToChatCompletionStreamIterator(BaseModelResponseIterator):
|
||||
def __init__(self, streaming_response, sync_stream: bool, json_mode: Optional[bool] = False):
|
||||
super().__init__(streaming_response, sync_stream, json_mode)
|
||||
self._chat_completion_id: Optional[str] = None
|
||||
|
||||
def _handle_string_chunk(
|
||||
self, str_line: Union[str, "BaseModel"]
|
||||
|
|
@ -1381,4 +1382,9 @@ class OpenAiResponsesToChatCompletionStreamIterator(BaseModelResponseIterator):
|
|||
ModelResponseStream: OpenAI-formatted streaming chunk
|
||||
"""
|
||||
verbose_logger.debug(f"Chat provider: transform_streaming_response called with chunk: {chunk}")
|
||||
return OpenAiResponsesToChatCompletionStreamIterator.translate_responses_chunk_to_openai_stream(chunk)
|
||||
parsed_chunk = OpenAiResponsesToChatCompletionStreamIterator.translate_responses_chunk_to_openai_stream(chunk)
|
||||
if self._chat_completion_id is None and parsed_chunk.id:
|
||||
self._chat_completion_id = parsed_chunk.id
|
||||
elif self._chat_completion_id is not None:
|
||||
parsed_chunk.id = self._chat_completion_id
|
||||
return parsed_chunk
|
||||
|
|
|
|||
|
|
@ -2401,6 +2401,78 @@ def test_parallel_tool_calls_comprehensive_streaming_integration():
|
|||
)
|
||||
|
||||
|
||||
def test_bridge_streaming_chunks_share_single_id():
|
||||
"""Regression test for #32607.
|
||||
|
||||
When a chat completions request is bridged to the Responses API and the
|
||||
streaming events are translated back, every emitted chunk must share one
|
||||
stable id. Previously each ModelResponseStream got a fresh
|
||||
"chatcmpl-<uuid>", which broke clients that group tool-call deltas by
|
||||
chunk.id (splitting a single tool call across multiple messages).
|
||||
"""
|
||||
from litellm.completion_extras.litellm_responses_transformation.transformation import (
|
||||
OpenAiResponsesToChatCompletionStreamIterator,
|
||||
)
|
||||
|
||||
chunks = [
|
||||
{"type": "response.created", "response": {"id": "resp_001"}},
|
||||
{
|
||||
"type": "response.output_item.added",
|
||||
"output_index": 0,
|
||||
"item": {
|
||||
"type": "function_call",
|
||||
"id": "fc_001",
|
||||
"call_id": "call_1",
|
||||
"name": "get_weather",
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "response.function_call_arguments.delta",
|
||||
"output_index": 0,
|
||||
"delta": '{"city":"Amsterdam"}',
|
||||
},
|
||||
{
|
||||
"type": "response.output_item.done",
|
||||
"output_index": 0,
|
||||
"item": {
|
||||
"type": "function_call",
|
||||
"id": "fc_001",
|
||||
"call_id": "call_1",
|
||||
"name": "get_weather",
|
||||
"arguments": '{"city":"Amsterdam"}',
|
||||
},
|
||||
},
|
||||
{
|
||||
"type": "response.completed",
|
||||
"response": {
|
||||
"id": "resp_001",
|
||||
"status": "completed",
|
||||
"output": [
|
||||
{
|
||||
"type": "function_call",
|
||||
"call_id": "call_1",
|
||||
"name": "get_weather",
|
||||
"arguments": '{"city":"Amsterdam"}',
|
||||
}
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
iterator = OpenAiResponsesToChatCompletionStreamIterator(
|
||||
streaming_response=None, sync_stream=True
|
||||
)
|
||||
ids = [iterator.chunk_parser(chunk).id for chunk in chunks]
|
||||
|
||||
assert len(set(ids)) == 1, (
|
||||
f"All bridged streaming chunks must share one id, got {len(set(ids))} "
|
||||
f"distinct ids: {set(ids)}"
|
||||
)
|
||||
assert ids[0] and ids[0].startswith("chatcmpl-"), (
|
||||
f"Bridged chunk id must keep the chat completion prefix, got {ids[0]!r}"
|
||||
)
|
||||
|
||||
|
||||
def test_map_optional_params_preserves_reasoning_summary():
|
||||
"""Test that reasoning_effort dict with summary field is preserved.
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue