mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-06 02:48:13 +00:00
fix(responses): preserve multipart recovered text
This commit is contained in:
parent
9091c66795
commit
d0b0247e3c
3 changed files with 72 additions and 28 deletions
|
|
@ -67,19 +67,20 @@ class ResponsesToCompletionBridgeHandler:
|
|||
else:
|
||||
raise ValueError("Unexpected responses stream payload")
|
||||
|
||||
from .transformation import LiteLLMResponsesTransformationHandler
|
||||
response = base_response
|
||||
if not base_response.output:
|
||||
from .transformation import LiteLLMResponsesTransformationHandler
|
||||
|
||||
parsed_chunks: Final = tuple(
|
||||
payload
|
||||
for event in stream_events
|
||||
if (payload := ResponsesToCompletionBridgeHandler._stream_event_payload(event)) is not None
|
||||
)
|
||||
recovered_output: Final = LiteLLMResponsesTransformationHandler.recover_output_items_from_chunks(parsed_chunks)
|
||||
response: Final = (
|
||||
base_response
|
||||
if base_response.output or not recovered_output
|
||||
else base_response.model_copy(update={"output": recovered_output})
|
||||
)
|
||||
parsed_chunks: Final = (
|
||||
payload
|
||||
for event in stream_events
|
||||
if (payload := ResponsesToCompletionBridgeHandler._stream_event_payload(event)) is not None
|
||||
)
|
||||
recovered_output: Final = LiteLLMResponsesTransformationHandler.recover_output_items_from_chunks(
|
||||
parsed_chunks
|
||||
)
|
||||
if recovered_output:
|
||||
response = base_response.model_copy(update={"output": recovered_output})
|
||||
|
||||
if hidden_params:
|
||||
existing: Final = getattr(response, "_hidden_params", None)
|
||||
|
|
|
|||
|
|
@ -334,22 +334,29 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge):
|
|||
# Handle message items with output_text content
|
||||
if item_type == "message":
|
||||
content_list: Final = item.get("content", [])
|
||||
response_text_parts: Final[list[str]] = []
|
||||
message_annotations: Final[list[ChatCompletionAnnotation]] = []
|
||||
has_output_text = False
|
||||
for content_item in content_list:
|
||||
if isinstance(content_item, dict):
|
||||
content_type = content_item.get("type")
|
||||
if content_type == "output_text":
|
||||
response_text = content_item.get("text", "")
|
||||
# Extract annotations from content if present
|
||||
annotations = LiteLLMResponsesTransformationHandler._convert_annotations_to_chat_format(
|
||||
content_item.get("annotations", None)
|
||||
)
|
||||
msg = Message(
|
||||
role=item.get("role", "assistant"),
|
||||
content=response_text if response_text else "",
|
||||
annotations=annotations,
|
||||
)
|
||||
choice = Choices(message=msg, finish_reason="stop", index=index)
|
||||
return choice, index + 1
|
||||
if not isinstance(content_item, dict) or content_item.get("type") != "output_text":
|
||||
continue
|
||||
has_output_text = True
|
||||
response_text = content_item.get("text", "")
|
||||
response_text_parts.append(response_text if isinstance(response_text, str) else "")
|
||||
annotations = LiteLLMResponsesTransformationHandler._convert_annotations_to_chat_format(
|
||||
content_item.get("annotations", None)
|
||||
)
|
||||
if annotations:
|
||||
message_annotations.extend(annotations)
|
||||
|
||||
if has_output_text:
|
||||
msg = Message(
|
||||
role=item.get("role", "assistant"),
|
||||
content="".join(response_text_parts),
|
||||
annotations=message_annotations or None,
|
||||
)
|
||||
choice = Choices(message=msg, finish_reason="stop", index=index)
|
||||
return choice, index + 1
|
||||
|
||||
# function_call / custom_tool_call dicts are intercepted and accumulated by
|
||||
# _convert_response_output_to_choices before this callback is reached
|
||||
|
|
@ -879,7 +886,7 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge):
|
|||
if not raw_sse or not isinstance(raw_sse, str):
|
||||
return []
|
||||
|
||||
parsed_chunks: Final = tuple(
|
||||
parsed_chunks: Final = (
|
||||
parsed_chunk for chunk in raw_sse.splitlines() if (parsed_chunk := parse_sse_json_chunk(chunk)) is not None
|
||||
)
|
||||
return cls.recover_output_items_from_chunks(parsed_chunks)
|
||||
|
|
|
|||
|
|
@ -610,6 +610,42 @@ def test_transform_response_recovers_empty_output_from_raw_sse():
|
|||
assert result.choices[0].message.content == "Recovered from SSE"
|
||||
|
||||
|
||||
def test_transform_response_recovers_all_text_parts_from_raw_sse():
|
||||
from litellm.completion_extras.litellm_responses_transformation.transformation import (
|
||||
LiteLLMResponsesTransformationHandler,
|
||||
)
|
||||
|
||||
handler = LiteLLMResponsesTransformationHandler()
|
||||
raw_sse = "\n".join(
|
||||
[
|
||||
'data: {"type":"response.output_text.done","output_index":0,"content_index":0,"item_id":"msg_from_stream","text":"Hello, "}',
|
||||
'data: {"type":"response.output_text.done","output_index":0,"content_index":1,"item_id":"msg_from_stream","text":"world!"}',
|
||||
'data: {"type":"response.completed","response":{"id":"resp_from_stream","object":"response","created_at":1760144904,"status":"completed","model":"gpt-5.4","output":[]}}',
|
||||
"data: [DONE]",
|
||||
"",
|
||||
]
|
||||
)
|
||||
raw_response = _make_empty_responses_api_response()
|
||||
model_response = _make_empty_model_response()
|
||||
logging_obj = Mock()
|
||||
logging_obj.model_call_details = {"original_response": raw_sse}
|
||||
|
||||
result = handler.transform_response(
|
||||
model="gpt-5.4",
|
||||
raw_response=raw_response,
|
||||
model_response=model_response,
|
||||
logging_obj=logging_obj,
|
||||
request_data={"model": "gpt-5.4"},
|
||||
messages=[{"role": "user", "content": "Reply with exactly: Hello, world!"}],
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
encoding=Mock(),
|
||||
)
|
||||
|
||||
assert len(result.choices) == 1
|
||||
assert result.choices[0].message.content == "Hello, world!"
|
||||
|
||||
|
||||
def test_transform_response_recovers_output_item_done_from_raw_sse():
|
||||
from litellm.completion_extras.litellm_responses_transformation.transformation import (
|
||||
LiteLLMResponsesTransformationHandler,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue