diff --git a/litellm/completion_extras/litellm_responses_transformation/transformation.py b/litellm/completion_extras/litellm_responses_transformation/transformation.py index e18218af2a4..7e1173e63f4 100644 --- a/litellm/completion_extras/litellm_responses_transformation/transformation.py +++ b/litellm/completion_extras/litellm_responses_transformation/transformation.py @@ -79,7 +79,9 @@ def _build_reasoning_item( summary: List[Dict[str, Any]] = [] for s in summary_raw or []: if isinstance(s, dict): - summary.append({"type": s.get("type", "summary_text"), "text": s.get("text", "")}) + summary.append( + {"type": s.get("type", "summary_text"), "text": s.get("text", "")} + ) else: summary.append( { @@ -118,7 +120,9 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge): def __init__(self): pass - def _handle_raw_dict_response_item(self, item: Dict[str, Any], index: int) -> Tuple[Optional[Any], int]: + def _handle_raw_dict_response_item( + self, item: Dict[str, Any], index: int + ) -> Tuple[Optional[Any], int]: """ Handle raw dict response items from Responses API (e.g., GPT-5 Codex format). @@ -161,9 +165,13 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge): if item_type == "function_call": # Extract provider_specific_fields if present and pass through as-is provider_specific_fields = item.get("provider_specific_fields") - if provider_specific_fields and not isinstance(provider_specific_fields, dict): + if provider_specific_fields and not isinstance( + provider_specific_fields, dict + ): provider_specific_fields = ( - dict(provider_specific_fields) if hasattr(provider_specific_fields, "__dict__") else {} + dict(provider_specific_fields) + if hasattr(provider_specific_fields, "__dict__") + else {} ) tool_call_dict = { @@ -179,7 +187,9 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge): if provider_specific_fields: tool_call_dict["provider_specific_fields"] = provider_specific_fields # Also add to function's provider_specific_fields for consistency - tool_call_dict["function"]["provider_specific_fields"] = provider_specific_fields + tool_call_dict["function"][ + "provider_specific_fields" + ] = provider_specific_fields msg = Message( content=None, @@ -291,8 +301,10 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge): if key in ("max_tokens", "max_completion_tokens"): responses_api_request["max_output_tokens"] = value elif key == "tools" and value is not None: - responses_api_request["tools"] = self._convert_tools_to_responses_format( - cast(List[Dict[str, Any]], value) + responses_api_request["tools"] = ( + self._convert_tools_to_responses_format( + cast(List[Dict[str, Any]], value) + ) ) elif key == "response_format": text_format = self._transform_response_format_to_text_format(value) @@ -309,9 +321,13 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge): def _build_sanitized_litellm_params(self, litellm_params: dict) -> Dict[str, Any]: """Build sanitized litellm_params with merged metadata.""" - responses_optional_param_keys = set(ResponsesAPIOptionalRequestParams.__annotations__.keys()) + responses_optional_param_keys = set( + ResponsesAPIOptionalRequestParams.__annotations__.keys() + ) sanitized: Dict[str, Any] = { - key: value for key, value in litellm_params.items() if key not in responses_optional_param_keys + key: value + for key, value in litellm_params.items() + if key not in responses_optional_param_keys } legacy_metadata = litellm_params.get("metadata") existing_litellm_metadata = litellm_params.get("litellm_metadata") @@ -373,7 +389,9 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge): if instructions: responses_api_request["instructions"] = instructions - self._map_optional_params_to_responses_api_request(optional_params, responses_api_request) + self._map_optional_params_to_responses_api_request( + optional_params, responses_api_request + ) stream = optional_params.get("stream") or litellm_params.get("stream", False) verbose_logger.debug(f"Chat provider: Stream parameter: {stream}") @@ -386,7 +404,9 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge): previous_response_id = optional_params.get("previous_response_id") if previous_response_id: # Use the existing session handler for responses API - verbose_logger.debug(f"Chat provider: Warning ignoring previous response ID: {previous_response_id}") + verbose_logger.debug( + f"Chat provider: Warning ignoring previous response ID: {previous_response_id}" + ) # Convert back to responses API format for the actual request @@ -406,9 +426,13 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge): "client": client, } - verbose_logger.debug(f"Chat provider: Final request model={api_model}, input_items={len(input_items)}") + verbose_logger.debug( + f"Chat provider: Final request model={api_model}, input_items={len(input_items)}" + ) - self._merge_responses_api_request_into_request_data(request_data, responses_api_request, instructions) + self._merge_responses_api_request_into_request_data( + request_data, responses_api_request, instructions + ) if headers: request_data["extra_headers"] = headers @@ -462,7 +486,11 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge): encrypted_content=getattr(item, "encrypted_content", None), summary_raw=item.summary, ) - reasoning_content = " ".join(s["text"] for s in pending_reasoning_item["summary"] if s.get("text")) + reasoning_content = " ".join( + s["text"] + for s in pending_reasoning_item["summary"] + if s.get("text") + ) elif isinstance(item, ResponseOutputMessage): for content in item.content: @@ -479,7 +507,11 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge): annotations=annotations, reasoning_items=cast( Optional[List[ChatCompletionReasoningItem]], - ([pending_reasoning_item] if pending_reasoning_item is not None else None), + ( + [pending_reasoning_item] + if pending_reasoning_item is not None + else None + ), ), ) @@ -500,25 +532,23 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge): LiteLLMCompletionResponsesConfig, ) - tool_call_dict = ( - LiteLLMCompletionResponsesConfig.convert_response_function_tool_call_to_chat_completion_tool_call( - tool_call_item=item, - index=tool_call_index, - ) + tool_call_dict = LiteLLMCompletionResponsesConfig.convert_response_function_tool_call_to_chat_completion_tool_call( + tool_call_item=item, + index=tool_call_index, ) accumulated_tool_calls.append(tool_call_dict) tool_call_index += 1 - elif ResponseApplyPatchToolCall is not None and isinstance(item, ResponseApplyPatchToolCall): + elif ResponseApplyPatchToolCall is not None and isinstance( + item, ResponseApplyPatchToolCall + ): from litellm.responses.litellm_completion_transformation.transformation import ( LiteLLMCompletionResponsesConfig, ) - tool_call_dict = ( - LiteLLMCompletionResponsesConfig.convert_apply_patch_tool_call_to_chat_completion_tool_call( - tool_call_item=item, - index=tool_call_index, - ) + tool_call_dict = LiteLLMCompletionResponsesConfig.convert_apply_patch_tool_call_to_chat_completion_tool_call( + tool_call_item=item, + index=tool_call_index, ) accumulated_tool_calls.append(tool_call_dict) tool_call_index += 1 @@ -539,10 +569,16 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge): reasoning_content=reasoning_content, reasoning_items=cast( Optional[List[ChatCompletionReasoningItem]], - ([pending_reasoning_item] if pending_reasoning_item is not None else None), + ( + [pending_reasoning_item] + if pending_reasoning_item is not None + else None + ), ), ) - choices.append(Choices(message=msg, finish_reason="tool_calls", index=index)) + choices.append( + Choices(message=msg, finish_reason="tool_calls", index=index) + ) reasoning_content = None pending_reasoning_item = None @@ -550,8 +586,14 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge): @classmethod def _parse_raw_sse_chunk(cls, chunk: str) -> Optional[Dict[str, Any]]: - stripped_chunk = (CustomStreamWrapper._strip_sse_data_from_chunk(chunk.strip()) or "").strip() - if not stripped_chunk or stripped_chunk == "[DONE]" or stripped_chunk.startswith("event:"): + stripped_chunk = ( + CustomStreamWrapper._strip_sse_data_from_chunk(chunk.strip()) or "" + ).strip() + if ( + not stripped_chunk + or stripped_chunk == "[DONE]" + or stripped_chunk.startswith("event:") + ): return None try: parsed_chunk = json.loads(stripped_chunk) @@ -562,7 +604,9 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge): return parsed_chunk @classmethod - def _extract_output_from_completed_event(cls, parsed_chunk: Dict[str, Any]) -> Optional[List[Dict[str, Any]]]: + def _extract_output_from_completed_event( + cls, parsed_chunk: Dict[str, Any] + ) -> Optional[List[Dict[str, Any]]]: response_payload = parsed_chunk.get("response") if not isinstance(response_payload, dict): return None @@ -573,7 +617,9 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge): @classmethod def _update_recovered_output_items( - cls, parsed_chunk: Dict[str, Any], recovered_output_items: Dict[int, Dict[str, Any]] + cls, + parsed_chunk: Dict[str, Any], + recovered_output_items: Dict[int, Dict[str, Any]], ) -> None: item = parsed_chunk.get("item") if not isinstance(item, dict): @@ -600,7 +646,9 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge): except (TypeError, ValueError): output_index = len(recovered_text_only_items) - item = recovered_output_items.get(output_index) or recovered_text_only_items.get(output_index) + item = recovered_output_items.get( + output_index + ) or recovered_text_only_items.get(output_index) if item is None: item = { "type": "message", @@ -642,7 +690,9 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge): content_item.setdefault("annotations", []) @classmethod - def _recover_output_items_from_raw_sse(cls, raw_sse: Optional[str]) -> List[Dict[str, Any]]: + def _recover_output_items_from_raw_sse( + cls, raw_sse: Optional[str] + ) -> List[Dict[str, Any]]: if not raw_sse or not isinstance(raw_sse, str): return [] @@ -657,7 +707,9 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge): event_type = parsed_chunk.get("type") if event_type == ResponsesAPIStreamEvents.RESPONSE_COMPLETED: - recovered_output = cls._extract_output_from_completed_event(parsed_chunk) + recovered_output = cls._extract_output_from_completed_event( + parsed_chunk + ) if recovered_output is not None: return recovered_output continue @@ -682,7 +734,9 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge): return [] @classmethod - def _recover_output_items_from_logging(cls, logging_obj: "LiteLLMLoggingObj") -> List[Dict[str, Any]]: + def _recover_output_items_from_logging( + cls, logging_obj: "LiteLLMLoggingObj" + ) -> List[Dict[str, Any]]: model_call_details = getattr(logging_obj, "model_call_details", {}) or {} original_response = model_call_details.get("original_response") return cls._recover_output_items_from_raw_sse(original_response) @@ -713,7 +767,9 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge): output_items = raw_response.output if len(output_items) == 0: - recovered_output_items = self._recover_output_items_from_logging(logging_obj) + recovered_output_items = self._recover_output_items_from_logging( + logging_obj + ) if recovered_output_items: output_items = recovered_output_items raw_response.output = recovered_output_items @@ -729,10 +785,17 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge): ) if len(choices) == 0: - if raw_response.incomplete_details is not None and raw_response.incomplete_details.reason is not None: - raise ValueError(f"{model} unable to complete request: {raw_response.incomplete_details.reason}") + if ( + raw_response.incomplete_details is not None + and raw_response.incomplete_details.reason is not None + ): + raise ValueError( + f"{model} unable to complete request: {raw_response.incomplete_details.reason}" + ) else: - raise ValueError(f"Unknown items in responses API response: {output_items}") + raise ValueError( + f"Unknown items in responses API response: {output_items}" + ) setattr(model_response, "choices", choices) @@ -741,21 +804,28 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge): setattr( model_response, "usage", - ResponseAPILoggingUtils._transform_response_api_usage_to_chat_usage(raw_response.usage), + ResponseAPILoggingUtils._transform_response_api_usage_to_chat_usage( + raw_response.usage + ), ) # Preserve hidden params from the ResponsesAPIResponse, especially the headers # which contain important provider information like x-request-id raw_response_hidden_params = getattr(raw_response, "_hidden_params", {}) if raw_response_hidden_params: - if not hasattr(model_response, "_hidden_params") or model_response._hidden_params is None: + if ( + not hasattr(model_response, "_hidden_params") + or model_response._hidden_params is None + ): model_response._hidden_params = {} # Merge the raw_response hidden params with model_response hidden params # Preserve existing keys in model_response but add/override with raw_response params for key, value in raw_response_hidden_params.items(): if key == "additional_headers" and key in model_response._hidden_params: # Merge additional_headers to preserve both sets - existing_additional_headers = model_response._hidden_params.get("additional_headers", {}) + existing_additional_headers = model_response._hidden_params.get( + "additional_headers", {} + ) merged_headers = {**value, **existing_additional_headers} model_response._hidden_params[key] = merged_headers else: @@ -765,13 +835,19 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge): def get_model_response_iterator( self, - streaming_response: Union[Iterator[str], AsyncIterator[str], "ModelResponse", "BaseModel"], + streaming_response: Union[ + Iterator[str], AsyncIterator[str], "ModelResponse", "BaseModel" + ], sync_stream: bool, json_mode: Optional[bool] = False, ) -> BaseModelResponseIterator: - return OpenAiResponsesToChatCompletionStreamIterator(streaming_response, sync_stream, json_mode) + return OpenAiResponsesToChatCompletionStreamIterator( + streaming_response, sync_stream, json_mode + ) - def _convert_content_str_to_input_text(self, content: str, role: str) -> Dict[str, Any]: + def _convert_content_str_to_input_text( + self, content: str, role: str + ) -> Dict[str, Any]: if role == "user" or role == "system" or role == "tool": return {"type": "input_text", "text": content} else: @@ -798,7 +874,9 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge): if actual_image_url is None: raise ValueError(f"Invalid image URL: {content_image_url}") - image_param = ResponseInputImageParam(image_url=actual_image_url, detail="auto", type="input_image") + image_param = ResponseInputImageParam( + image_url=actual_image_url, detail="auto", type="input_image" + ) if detail: image_param["detail"] = detail @@ -825,7 +903,9 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge): """Convert chat completion content to responses API format""" from litellm.types.llms.openai import ChatCompletionImageObject - verbose_logger.debug(f"Chat provider: Converting content to responses format - input type: {type(content)}") + verbose_logger.debug( + f"Chat provider: Converting content to responses format - input type: {type(content)}" + ) if content is None: return [self._convert_content_str_to_input_text("", role)] @@ -836,7 +916,9 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge): elif isinstance(content, list): result = [] for i, item in enumerate(content): - verbose_logger.debug(f"Chat provider: Processing content item {i}: {type(item)} = {item}") + verbose_logger.debug( + f"Chat provider: Processing content item {i}: {type(item)} = {item}" + ) if isinstance(item, str): converted = self._convert_content_str_to_input_text(item, role) result.append(converted) @@ -845,7 +927,9 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge): # Handle multimodal content original_type = item.get("type") if original_type == "text": - converted = self._convert_content_str_to_input_text(item.get("text", ""), role) + converted = self._convert_content_str_to_input_text( + item.get("text", ""), role + ) result.append(converted) verbose_logger.debug(f"Chat provider: text -> {converted}") elif original_type == "image_url": @@ -857,14 +941,18 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge): ), ) result.append(converted) - verbose_logger.debug(f"Chat provider: image_url -> {converted}") + verbose_logger.debug( + f"Chat provider: image_url -> {converted}" + ) else: # Try to map other types to responses API format item_type = original_type or "input_text" if item_type == "image": converted = {"type": "input_image", **item} result.append(converted) - verbose_logger.debug(f"Chat provider: image -> {converted}") + verbose_logger.debug( + f"Chat provider: image -> {converted}" + ) elif item_type == "file": # Map Chat Completion file to Responses API input_file # {"type": "file", "file": {"file_data": "...", "filename": "..."}} @@ -876,7 +964,9 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge): if key in file_data: converted[key] = file_data[key] result.append(converted) - verbose_logger.debug(f"Chat provider: file -> {converted}") + verbose_logger.debug( + f"Chat provider: file -> {converted}" + ) elif item_type in [ "input_text", "input_image", @@ -888,12 +978,18 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge): ]: # Already in responses API format result.append(item) - verbose_logger.debug(f"Chat provider: passthrough -> {item}") + verbose_logger.debug( + f"Chat provider: passthrough -> {item}" + ) else: # Default to input_text for unknown types - converted = self._convert_content_str_to_input_text(str(item.get("text", item)), role) + converted = self._convert_content_str_to_input_text( + str(item.get("text", item)), role + ) result.append(converted) - verbose_logger.debug(f"Chat provider: unknown({original_type}) -> {converted}") + verbose_logger.debug( + f"Chat provider: unknown({original_type}) -> {converted}" + ) verbose_logger.debug(f"Chat provider: Final converted content: {result}") return result else: @@ -901,13 +997,17 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge): verbose_logger.debug(f"Chat provider: Other content type -> {result}") return result - def _convert_tools_to_responses_format(self, tools: List[Dict[str, Any]]) -> List["ALL_RESPONSES_API_TOOL_PARAMS"]: + def _convert_tools_to_responses_format( + self, tools: List[Dict[str, Any]] + ) -> List["ALL_RESPONSES_API_TOOL_PARAMS"]: """Convert chat completion tools to responses API tools format""" responses_tools: List["ALL_RESPONSES_API_TOOL_PARAMS"] = [] for tool in tools: # convert function tool from chat completion to responses API format if tool.get("type") == "function": - function_tool = cast(ChatCompletionToolParamFunctionChunk, tool.get("function")) + function_tool = cast( + ChatCompletionToolParamFunctionChunk, tool.get("function") + ) responses_tools.append( FunctionToolParam( name=function_tool["name"], @@ -933,7 +1033,9 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge): if not extra_body: return optional_params - supported_responses_api_params = set(ResponsesAPIOptionalRequestParams.__annotations__.keys()) + supported_responses_api_params = set( + ResponsesAPIOptionalRequestParams.__annotations__.keys() + ) # Also include params we handle specially supported_responses_api_params.update( { @@ -951,7 +1053,9 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge): return optional_params - def _map_reasoning_effort(self, reasoning_effort: Union[str, Dict[str, Any]]) -> Optional[Reasoning]: + def _map_reasoning_effort( + self, reasoning_effort: Union[str, Dict[str, Any]] + ) -> Optional[Reasoning]: # If dict is passed, convert it directly to Reasoning object if isinstance(reasoning_effort, dict): return Reasoning(**reasoning_effort) # type: ignore[typeddict-item] @@ -959,25 +1063,38 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge): # Check if auto-summary is enabled via flag or environment variable # Priority: litellm.reasoning_auto_summary flag > LITELLM_REASONING_AUTO_SUMMARY env var auto_summary_enabled = ( - litellm.reasoning_auto_summary or os.getenv("LITELLM_REASONING_AUTO_SUMMARY", "false").lower() == "true" + litellm.reasoning_auto_summary + or os.getenv("LITELLM_REASONING_AUTO_SUMMARY", "false").lower() == "true" ) # If string is passed, map with optional summary based on flag/env var if reasoning_effort == "none": return Reasoning(effort="none", summary="detailed") if auto_summary_enabled else Reasoning(effort="none") # type: ignore elif reasoning_effort == "high": - return Reasoning(effort="high", summary="detailed") if auto_summary_enabled else Reasoning(effort="high") + return ( + Reasoning(effort="high", summary="detailed") + if auto_summary_enabled + else Reasoning(effort="high") + ) elif reasoning_effort == "xhigh": return Reasoning(effort="xhigh", summary="detailed") if auto_summary_enabled else Reasoning(effort="xhigh") # type: ignore[typeddict-item] elif reasoning_effort == "medium": return ( - Reasoning(effort="medium", summary="detailed") if auto_summary_enabled else Reasoning(effort="medium") + Reasoning(effort="medium", summary="detailed") + if auto_summary_enabled + else Reasoning(effort="medium") ) elif reasoning_effort == "low": - return Reasoning(effort="low", summary="detailed") if auto_summary_enabled else Reasoning(effort="low") + return ( + Reasoning(effort="low", summary="detailed") + if auto_summary_enabled + else Reasoning(effort="low") + ) elif reasoning_effort == "minimal": return ( - Reasoning(effort="minimal", summary="detailed") if auto_summary_enabled else Reasoning(effort="minimal") + Reasoning(effort="minimal", summary="detailed") + if auto_summary_enabled + else Reasoning(effort="minimal") ) return None @@ -993,7 +1110,10 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge): responses_api_request: The responses API request dict to modify web_search_options: Web search configuration (dict or other value) """ - if "tools" not in responses_api_request or responses_api_request["tools"] is None: + if ( + "tools" not in responses_api_request + or responses_api_request["tools"] is None + ): responses_api_request["tools"] = [] # Get the tools list with proper type narrowing @@ -1083,13 +1203,17 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge): annotation_dict = annotation else: # Skip unsupported annotation types - verbose_logger.debug(f"Skipping unsupported annotation type: {type(annotation)}") + verbose_logger.debug( + f"Skipping unsupported annotation type: {type(annotation)}" + ) continue result.append(annotation_dict) # type: ignore except Exception as e: # Skip malformed annotations - verbose_logger.debug(f"Skipping malformed annotation: {annotation}, error: {e}") + verbose_logger.debug( + f"Skipping malformed annotation: {annotation}, error: {e}" + ) continue return result if result else None @@ -1110,7 +1234,9 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge): class OpenAiResponsesToChatCompletionStreamIterator(BaseModelResponseIterator): - def __init__(self, streaming_response, sync_stream: bool, json_mode: Optional[bool] = False): + def __init__( + self, streaming_response, sync_stream: bool, json_mode: Optional[bool] = False + ): super().__init__(streaming_response, sync_stream, json_mode) def _handle_string_chunk( @@ -1123,7 +1249,9 @@ class OpenAiResponsesToChatCompletionStreamIterator(BaseModelResponseIterator): if not str_line or str_line.startswith("event:"): # ignore. - return GenericStreamingChunk(text="", tool_use=None, is_finished=False, finish_reason="", usage=None) + return GenericStreamingChunk( + text="", tool_use=None, is_finished=False, finish_reason="", usage=None + ) index = str_line.find("data:") if index != -1: str_line = str_line[index + 5 :] @@ -1186,9 +1314,13 @@ class OpenAiResponsesToChatCompletionStreamIterator(BaseModelResponseIterator): if output_item.get("type") == "function_call": # Extract provider_specific_fields if present provider_specific_fields = output_item.get("provider_specific_fields") - if provider_specific_fields and not isinstance(provider_specific_fields, dict): + if provider_specific_fields and not isinstance( + provider_specific_fields, dict + ): provider_specific_fields = ( - dict(provider_specific_fields) if hasattr(provider_specific_fields, "__dict__") else {} + dict(provider_specific_fields) + if hasattr(provider_specific_fields, "__dict__") + else {} ) function_chunk = ChatCompletionToolCallFunctionChunk( @@ -1197,7 +1329,9 @@ class OpenAiResponsesToChatCompletionStreamIterator(BaseModelResponseIterator): ) if provider_specific_fields: - function_chunk["provider_specific_fields"] = provider_specific_fields + function_chunk["provider_specific_fields"] = ( + provider_specific_fields + ) tool_call_index = parsed_chunk.get("output_index", 0) tool_call_chunk = ChatCompletionToolCallChunk( @@ -1234,7 +1368,9 @@ class OpenAiResponsesToChatCompletionStreamIterator(BaseModelResponseIterator): id=None, index=tool_call_index, type="function", - function=ChatCompletionToolCallFunctionChunk(name=None, arguments=content_part), + function=ChatCompletionToolCallFunctionChunk( + name=None, arguments=content_part + ), ) ] ), @@ -1243,16 +1379,22 @@ class OpenAiResponsesToChatCompletionStreamIterator(BaseModelResponseIterator): ] ) else: - raise ValueError(f"Chat provider: Invalid function argument delta {parsed_chunk}") + raise ValueError( + f"Chat provider: Invalid function argument delta {parsed_chunk}" + ) elif event_type == ResponsesAPIStreamEvents.OUTPUT_ITEM_DONE: # New output item added output_item = parsed_chunk.get("item", {}) if output_item.get("type") == "function_call": # Extract provider_specific_fields if present provider_specific_fields = output_item.get("provider_specific_fields") - if provider_specific_fields and not isinstance(provider_specific_fields, dict): + if provider_specific_fields and not isinstance( + provider_specific_fields, dict + ): provider_specific_fields = ( - dict(provider_specific_fields) if hasattr(provider_specific_fields, "__dict__") else {} + dict(provider_specific_fields) + if hasattr(provider_specific_fields, "__dict__") + else {} ) function_chunk = ChatCompletionToolCallFunctionChunk( @@ -1262,7 +1404,9 @@ class OpenAiResponsesToChatCompletionStreamIterator(BaseModelResponseIterator): # Add provider_specific_fields to function if present if provider_specific_fields: - function_chunk["provider_specific_fields"] = provider_specific_fields + function_chunk["provider_specific_fields"] = ( + provider_specific_fields + ) tool_call_index = parsed_chunk.get("output_index", 0) tool_call_chunk = ChatCompletionToolCallChunk( @@ -1338,7 +1482,9 @@ class OpenAiResponsesToChatCompletionStreamIterator(BaseModelResponseIterator): output_items = response_data.get("output", []) if response_data else [] has_function_calls = any( - item.get("type") == "function_call" for item in output_items if isinstance(item, dict) + item.get("type") == "function_call" + for item in output_items + if isinstance(item, dict) ) finish_reason = "tool_calls" if has_function_calls else "stop" @@ -1366,7 +1512,11 @@ class OpenAiResponsesToChatCompletionStreamIterator(BaseModelResponseIterator): if response_data.get("usage"): from litellm.responses.utils import ResponseAPILoggingUtils - usage = ResponseAPILoggingUtils._transform_response_api_usage_to_chat_usage(response_data.get("usage")) + usage = ( + ResponseAPILoggingUtils._transform_response_api_usage_to_chat_usage( + response_data.get("usage") + ) + ) return ModelResponseStream( choices=[ StreamingChoices( @@ -1383,7 +1533,9 @@ class OpenAiResponsesToChatCompletionStreamIterator(BaseModelResponseIterator): else: pass # For any unhandled event types, create a minimal valid chunk or skip - verbose_logger.debug(f"Chat provider: Unhandled event type '{event_type}', creating empty chunk") + verbose_logger.debug( + f"Chat provider: Unhandled event type '{event_type}', creating empty chunk" + ) # Return a minimal valid chunk for unknown events return ModelResponseStream( @@ -1406,5 +1558,9 @@ class OpenAiResponsesToChatCompletionStreamIterator(BaseModelResponseIterator): Returns: 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) + verbose_logger.debug( + f"Chat provider: transform_streaming_response called with chunk: {chunk}" + ) + return OpenAiResponsesToChatCompletionStreamIterator.translate_responses_chunk_to_openai_stream( + chunk + ) diff --git a/litellm/integrations/rubrik.py b/litellm/integrations/rubrik.py index ffe91799ace..a988c0b3604 100644 --- a/litellm/integrations/rubrik.py +++ b/litellm/integrations/rubrik.py @@ -1,7 +1,6 @@ """Rubrik LiteLLM Plugin for tool blocking and batch logging.""" import asyncio -import copy import os import random import time @@ -17,6 +16,7 @@ from litellm.integrations.custom_guardrail import ( CustomGuardrail, ModifyResponseException, ) +from litellm.litellm_core_utils.core_helpers import safe_deep_copy from litellm.llms.custom_httpx.http_handler import ( get_async_httpx_client, httpxSpecialProvider, @@ -293,7 +293,7 @@ class RubrikLogger(CustomGuardrail, CustomBatchLogger): return None # Deep-copy so mutations don't affect other callbacks sharing this object - standard_logging_payload: StandardLoggingPayload = copy.deepcopy( + standard_logging_payload: StandardLoggingPayload = safe_deep_copy( kwargs["standard_logging_object"] ) diff --git a/litellm/llms/chatgpt/responses/transformation.py b/litellm/llms/chatgpt/responses/transformation.py index bc7729445e9..d0e63fbfdf8 100644 --- a/litellm/llms/chatgpt/responses/transformation.py +++ b/litellm/llms/chatgpt/responses/transformation.py @@ -53,7 +53,9 @@ class ChatGPTResponsesAPIConfig(OpenAIResponsesAPIConfig): account_id = self.authenticator.get_account_id() session_id = ensure_chatgpt_session_id(litellm_params) - default_headers = get_chatgpt_default_headers(access_token, account_id, session_id) + default_headers = get_chatgpt_default_headers( + access_token, account_id, session_id + ) return {**default_headers, **headers} def transform_responses_api_request( @@ -75,7 +77,9 @@ class ChatGPTResponsesAPIConfig(OpenAIResponsesAPIConfig): existing_instructions = request.get("instructions") if existing_instructions: if base_instructions not in existing_instructions: - request["instructions"] = f"{base_instructions}\n\n{existing_instructions}" + request["instructions"] = ( + f"{base_instructions}\n\n{existing_instructions}" + ) else: request["instructions"] = base_instructions request["store"] = False @@ -108,7 +112,9 @@ class ChatGPTResponsesAPIConfig(OpenAIResponsesAPIConfig): logging_obj: Any, ): body_text = raw_response.text or "" - if not self._should_parse_as_sse(raw_response=raw_response, body_text=body_text): + if not self._should_parse_as_sse( + raw_response=raw_response, body_text=body_text + ): return super().transform_response_api_response( model=model, raw_response=raw_response, @@ -120,14 +126,18 @@ class ChatGPTResponsesAPIConfig(OpenAIResponsesAPIConfig): additional_args={"complete_input_dict": {}}, ) - completed_response, error_message = self._extract_completed_response_from_sse(body_text=body_text) + completed_response, error_message = self._extract_completed_response_from_sse( + body_text=body_text + ) if completed_response is None: raise OpenAIError( message=error_message or raw_response.text, status_code=raw_response.status_code, ) - self._attach_response_headers(completed_response=completed_response, raw_response=raw_response) + self._attach_response_headers( + completed_response=completed_response, raw_response=raw_response + ) return completed_response def _should_parse_as_sse(self, raw_response: Any, body_text: str) -> bool: @@ -157,12 +167,16 @@ class ChatGPTResponsesAPIConfig(OpenAIResponsesAPIConfig): event_type = parsed_chunk.get("type") if event_type == ResponsesAPIStreamEvents.OUTPUT_ITEM_DONE: - self._record_output_item_chunk(parsed_chunk=parsed_chunk, streamed_output_items=streamed_output_items) + self._record_output_item_chunk( + parsed_chunk=parsed_chunk, + streamed_output_items=streamed_output_items, + ) continue if event_type == ResponsesAPIStreamEvents.RESPONSE_COMPLETED: completed_response = self._build_completed_response_from_chunk( - parsed_chunk=parsed_chunk, streamed_output_items=streamed_output_items + parsed_chunk=parsed_chunk, + streamed_output_items=streamed_output_items, ) break @@ -191,7 +205,9 @@ class ChatGPTResponsesAPIConfig(OpenAIResponsesAPIConfig): return None return parsed_chunk - def _record_output_item_chunk(self, parsed_chunk: Dict[str, Any], streamed_output_items: Dict[int, dict]) -> None: + def _record_output_item_chunk( + self, parsed_chunk: Dict[str, Any], streamed_output_items: Dict[int, dict] + ) -> None: item = parsed_chunk.get("item") output_index = parsed_chunk.get("output_index") if not isinstance(item, dict): @@ -210,16 +226,22 @@ class ChatGPTResponsesAPIConfig(OpenAIResponsesAPIConfig): return None response_payload = dict(response_payload) if not response_payload.get("output") and streamed_output_items: - response_payload["output"] = [item for _, item in sorted(streamed_output_items.items())] + response_payload["output"] = [ + item for _, item in sorted(streamed_output_items.items()) + ] if "created_at" in response_payload: - response_payload["created_at"] = _safe_convert_created_field(response_payload["created_at"]) + response_payload["created_at"] = _safe_convert_created_field( + response_payload["created_at"] + ) try: return ResponsesAPIResponse(**response_payload) except Exception: return ResponsesAPIResponse.model_construct(**response_payload) def _extract_error_message(self, parsed_chunk: Dict[str, Any]) -> Optional[str]: - error_obj = parsed_chunk.get("error") or (parsed_chunk.get("response") or {}).get("error") + error_obj = parsed_chunk.get("error") or ( + parsed_chunk.get("response") or {} + ).get("error") if error_obj is None: return None if isinstance(error_obj, dict):