diff --git a/litellm/completion_extras/litellm_responses_transformation/transformation.py b/litellm/completion_extras/litellm_responses_transformation/transformation.py index 7d10f6b216..2356b23b10 100644 --- a/litellm/completion_extras/litellm_responses_transformation/transformation.py +++ b/litellm/completion_extras/litellm_responses_transformation/transformation.py @@ -24,6 +24,7 @@ from litellm.llms.base_llm.base_model_iterator import BaseModelResponseIterator from litellm.llms.base_llm.bridges.completion_transformation import ( CompletionTransformationBridge, ) +from litellm.types.llms.openai import Reasoning if TYPE_CHECKING: from openai.types.responses import ResponseInputImageParam @@ -152,11 +153,12 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge): ) elif key in ResponsesAPIOptionalRequestParams.__annotations__.keys(): responses_api_request[key] = value # type: ignore - elif key == "metadata": + elif key in ("metadata"): responses_api_request["metadata"] = value - elif key == "previous_response_id": - # Support for responses API session management + elif key in ("previous_response_id"): responses_api_request["previous_response_id"] = value + elif key == "reasoning_effort": + responses_api_request["reasoning"] = self._map_reasoning_effort(value) # Get stream parameter from litellm_params if not in optional_params stream = optional_params.get("stream") or litellm_params.get("stream", False) @@ -465,6 +467,16 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge): ) return cast(List["ALL_RESPONSES_API_TOOL_PARAMS"], responses_tools) + def _map_reasoning_effort(self, reasoning_effort: str) -> Optional[Reasoning]: + if reasoning_effort == "high": + return Reasoning(effort="high", summary="detailed") + elif reasoning_effort == "medium": + # docs say "summary": "concise" is also an option, but it was rejected in practice, so defaulting "auto" + return Reasoning(effort="medium", summary="auto") + elif reasoning_effort == "low": + return Reasoning(effort="low", summary="auto") + return None + def _map_responses_status_to_finish_reason(self, status: Optional[str]) -> str: """Map responses API status to chat completion finish_reason""" if not status: @@ -623,6 +635,22 @@ class OpenAiResponsesToChatCompletionStreamIterator(BaseModelResponseIterator): ) else: raise ValueError(f"Chat provider: Invalid text delta {parsed_chunk}") + elif event_type == "response.reasoning_summary_text.delta": + content_part = parsed_chunk.get("delta", None) + if content_part: + from litellm.types.utils import ( + Delta, + ModelResponseStream, + StreamingChoices, + ) + + return ModelResponseStream( + choices=[ + StreamingChoices( + index=parsed_chunk.get("summary_index"), delta=Delta(reasoning_content=content_part) + ) + ] + ) else: pass # For any unhandled event types, create a minimal valid chunk or skip diff --git a/tests/test_litellm/completion_extras/litellm_responses_transformation/test_completion_extras_litellm_responses_transformation_transformation.py b/tests/test_litellm/completion_extras/litellm_responses_transformation/test_completion_extras_litellm_responses_transformation_transformation.py index 4d3654ca47..ef76cfa02d 100644 --- a/tests/test_litellm/completion_extras/litellm_responses_transformation/test_completion_extras_litellm_responses_transformation_transformation.py +++ b/tests/test_litellm/completion_extras/litellm_responses_transformation/test_completion_extras_litellm_responses_transformation_transformation.py @@ -50,3 +50,37 @@ def test_convert_chat_completion_messages_to_responses_api_image_input(): print("response: ", response) assert response[0]["content"][1]["image_url"] == user_image + + +def test_openai_responses_chunk_parser_reasoning_summary(): + from litellm.completion_extras.litellm_responses_transformation.transformation import ( + OpenAiResponsesToChatCompletionStreamIterator, + ) + from litellm.types.utils import Delta, ModelResponseStream, StreamingChoices + + iterator = OpenAiResponsesToChatCompletionStreamIterator( + streaming_response=None, sync_stream=True + ) + + chunk = { + "delta": "**Compar", + "item_id": "rs_686d544208748198b6912e27b7c299c00e24bd875d35bade", + "output_index": 0, + "sequence_number": 4, + "summary_index": 0, + "type": "response.reasoning_summary_text.delta", + } + + result = iterator.chunk_parser(chunk) + + assert isinstance(result, ModelResponseStream) + assert len(result.choices) == 1 + choice = result.choices[0] + assert isinstance(choice, StreamingChoices) + assert choice.index == 0 + delta = choice.delta + assert isinstance(delta, Delta) + assert delta.content is None + assert delta.reasoning_content == "**Compar" + assert delta.tool_calls is None + assert delta.function_call is None