From acb238f6652879b06aadb95be7e7fb1521f76fac Mon Sep 17 00:00:00 2001 From: BillionClaw <267901332+BillionClaw@users.noreply.github.com> Date: Tue, 24 Mar 2026 11:39:25 +0800 Subject: [PATCH] fix(anthropic): allow tool_choice type 'none' in messages API The Anthropic /v1/messages API supports tool_choice={"type":"none"} to disable tool use. LiteLLM was rejecting this with a 500 error. Added 'none' as a valid tool_choice variant. Fixes #24443. --- .../adapters/transformation.py | 360 +++++------------- 1 file changed, 89 insertions(+), 271 deletions(-) diff --git a/litellm/llms/anthropic/experimental_pass_through/adapters/transformation.py b/litellm/llms/anthropic/experimental_pass_through/adapters/transformation.py index ed49943b7fe..68996d0fa0b 100644 --- a/litellm/llms/anthropic/experimental_pass_through/adapters/transformation.py +++ b/litellm/llms/anthropic/experimental_pass_through/adapters/transformation.py @@ -129,9 +129,7 @@ class AnthropicAdapter: def __init__(self) -> None: pass - def translate_completion_input_params( - self, kwargs - ) -> Optional[ChatCompletionRequest]: + def translate_completion_input_params(self, kwargs) -> Optional[ChatCompletionRequest]: """ Translate Anthropic request params to OpenAI format. @@ -164,27 +162,19 @@ def translate_completion_input_params_with_tool_mapping( model = kwargs.pop("model") messages = kwargs.pop("messages") if not model: - raise ValueError( - "Bad Request: model is required for Anthropic Messages Request" - ) + raise ValueError("Bad Request: model is required for Anthropic Messages Request") if not messages: - raise ValueError( - "Bad Request: messages is required for Anthropic Messages Request" - ) + raise ValueError("Bad Request: messages is required for Anthropic Messages Request") ######################################################### # Created Typed Request Body ######################################################### - request_body = AnthropicMessagesRequest( - model=model, messages=messages, **kwargs - ) + request_body = AnthropicMessagesRequest(model=model, messages=messages, **kwargs) ( translated_body, tool_name_mapping, - ) = LiteLLMAnthropicMessagesAdapter().translate_anthropic_to_openai( - anthropic_message_request=request_body - ) + ) = LiteLLMAnthropicMessagesAdapter().translate_anthropic_to_openai(anthropic_message_request=request_body) return translated_body, tool_name_mapping @@ -243,26 +233,16 @@ def _extract_signature_from_tool_call(self, tool_call: Any) -> Optional[str]: """ signature = None - if ( - hasattr(tool_call, "provider_specific_fields") - and tool_call.provider_specific_fields - ): + if hasattr(tool_call, "provider_specific_fields") and tool_call.provider_specific_fields: if "thought_signature" in tool_call.provider_specific_fields: signature = tool_call.provider_specific_fields["thought_signature"] - elif ( - hasattr(tool_call.function, "provider_specific_fields") - and tool_call.function.provider_specific_fields - ): + elif hasattr(tool_call.function, "provider_specific_fields") and tool_call.function.provider_specific_fields: if "thought_signature" in tool_call.function.provider_specific_fields: - signature = tool_call.function.provider_specific_fields[ - "thought_signature" - ] + signature = tool_call.function.provider_specific_fields["thought_signature"] return signature - def _extract_signature_from_tool_use_content( - self, content: Dict[str, Any] - ) -> Optional[str]: + def _extract_signature_from_tool_use_content(self, content: Dict[str, Any]) -> Optional[str]: """ Extract signature from a tool_use content block's provider_specific_fields. """ @@ -292,9 +272,7 @@ def _add_cache_control_if_applicable( """ # TypedDict objects are dicts at runtime, so .get() works cache_control = ( - source.get("cache_control") - if isinstance(source, dict) - else getattr(source, "cache_control", None) + source.get("cache_control") if isinstance(source, dict) else getattr(source, "cache_control", None) ) if cache_control and model and self.is_anthropic_claude_model(model): # TypedDict objects support dict operations at runtime @@ -335,9 +313,7 @@ def _is_web_search_tool(self, tool: Dict[str, Any]) -> bool: """ tool_type = tool.get("type", "") tool_name = tool.get("name", "") - return ( - isinstance(tool_type, str) and tool_type.startswith("web_search") - ) or tool_name == "web_search" + return (isinstance(tool_type, str) and tool_type.startswith("web_search")) or tool_name == "web_search" def translate_anthropic_messages_to_openai( # noqa: PLR0915 self, @@ -353,66 +329,38 @@ def translate_anthropic_messages_to_openai( # noqa: PLR0915 for m in messages: user_message: Optional[ChatCompletionUserMessage] = None tool_message_list: List[ChatCompletionToolMessage] = [] - new_user_content_list: List[ - Union[ChatCompletionTextObject, ChatCompletionImageObject] - ] = [] + new_user_content_list: List[Union[ChatCompletionTextObject, ChatCompletionImageObject]] = [] ## USER MESSAGE ## if m["role"] == "user": ## translate user message message_content = m.get("content") if message_content and isinstance(message_content, str): - user_message = ChatCompletionUserMessage( - role="user", content=message_content - ) + user_message = ChatCompletionUserMessage(role="user", content=message_content) elif message_content and isinstance(message_content, list): for content in message_content: if content.get("type") == "text": - text_obj = ChatCompletionTextObject( - type="text", text=content.get("text", "") - ) - self._add_cache_control_if_applicable( - content, text_obj, model - ) + text_obj = ChatCompletionTextObject(type="text", text=content.get("text", "")) + self._add_cache_control_if_applicable(content, text_obj, model) new_user_content_list.append(text_obj) # type: ignore elif content.get("type") == "image": # Convert Anthropic image format to OpenAI format source = content.get("source", {}) - openai_image_url = ( - self._translate_anthropic_image_to_openai( - cast(dict, source) - ) - ) + openai_image_url = self._translate_anthropic_image_to_openai(cast(dict, source)) if openai_image_url: - image_url_obj = ChatCompletionImageUrlObject( - url=openai_image_url - ) - image_obj = ChatCompletionImageObject( - type="image_url", image_url=image_url_obj - ) - self._add_cache_control_if_applicable( - content, image_obj, model - ) + image_url_obj = ChatCompletionImageUrlObject(url=openai_image_url) + image_obj = ChatCompletionImageObject(type="image_url", image_url=image_url_obj) + self._add_cache_control_if_applicable(content, image_obj, model) new_user_content_list.append(image_obj) # type: ignore elif content.get("type") == "document": # Convert Anthropic document format (PDF, etc.) to OpenAI format source = content.get("source", {}) - openai_image_url = ( - self._translate_anthropic_image_to_openai( - cast(dict, source) - ) - ) + openai_image_url = self._translate_anthropic_image_to_openai(cast(dict, source)) if openai_image_url: - image_url_obj = ChatCompletionImageUrlObject( - url=openai_image_url - ) - doc_obj = ChatCompletionImageObject( - type="image_url", image_url=image_url_obj - ) - self._add_cache_control_if_applicable( - content, doc_obj, model - ) + image_url_obj = ChatCompletionImageUrlObject(url=openai_image_url) + doc_obj = ChatCompletionImageObject(type="image_url", image_url=image_url_obj) + self._add_cache_control_if_applicable(content, doc_obj, model) new_user_content_list.append(doc_obj) # type: ignore elif content.get("type") == "tool_result": if "content" not in content: @@ -421,9 +369,7 @@ def translate_anthropic_messages_to_openai( # noqa: PLR0915 tool_call_id=content.get("tool_use_id", ""), content="", ) - self._add_cache_control_if_applicable( - content, tool_result, model - ) + self._add_cache_control_if_applicable(content, tool_result, model) tool_message_list.append(tool_result) # type: ignore[arg-type] elif isinstance(content.get("content"), str): tool_result = ChatCompletionToolMessage( @@ -431,9 +377,7 @@ def translate_anthropic_messages_to_openai( # noqa: PLR0915 tool_call_id=content.get("tool_use_id", ""), content=str(content.get("content", "")), ) - self._add_cache_control_if_applicable( - content, tool_result, model - ) + self._add_cache_control_if_applicable(content, tool_result, model) tool_message_list.append(tool_result) # type: ignore[arg-type] elif isinstance(content.get("content"), list): # Combine all content items into a single tool message @@ -450,41 +394,28 @@ def translate_anthropic_messages_to_openai( # noqa: PLR0915 tool_call_id=content.get("tool_use_id", ""), content=c, ) - self._add_cache_control_if_applicable( - content, tool_result, model - ) + self._add_cache_control_if_applicable(content, tool_result, model) tool_message_list.append(tool_result) # type: ignore[arg-type] elif isinstance(c, dict): if c.get("type") == "text": tool_result = ChatCompletionToolMessage( role="tool", - tool_call_id=content.get( - "tool_use_id", "" - ), + tool_call_id=content.get("tool_use_id", ""), content=c.get("text", ""), ) - self._add_cache_control_if_applicable( - content, tool_result, model - ) + self._add_cache_control_if_applicable(content, tool_result, model) tool_message_list.append(tool_result) # type: ignore[arg-type] elif c.get("type") == "image": source = c.get("source", {}) openai_image_url = ( - self._translate_anthropic_image_to_openai( - cast(dict, source) - ) - or "" + self._translate_anthropic_image_to_openai(cast(dict, source)) or "" ) tool_result = ChatCompletionToolMessage( role="tool", - tool_call_id=content.get( - "tool_use_id", "" - ), + tool_call_id=content.get("tool_use_id", ""), content=openai_image_url, ) - self._add_cache_control_if_applicable( - content, tool_result, model - ) + self._add_cache_control_if_applicable(content, tool_result, model) tool_message_list.append(tool_result) # type: ignore[arg-type] else: # For multiple content items, combine into a single tool message @@ -497,11 +428,7 @@ def translate_anthropic_messages_to_openai( # noqa: PLR0915 ] = [] for c in content_items: if isinstance(c, str): - combined_content_parts.append( - ChatCompletionTextObject( - type="text", text=c - ) - ) + combined_content_parts.append(ChatCompletionTextObject(type="text", text=c)) elif isinstance(c, dict): if c.get("type") == "text": combined_content_parts.append( @@ -513,10 +440,7 @@ def translate_anthropic_messages_to_openai( # noqa: PLR0915 elif c.get("type") == "image": source = c.get("source", {}) openai_image_url = ( - self._translate_anthropic_image_to_openai( - cast(dict, source) - ) - or "" + self._translate_anthropic_image_to_openai(cast(dict, source)) or "" ) if openai_image_url: combined_content_parts.append( @@ -534,9 +458,7 @@ def translate_anthropic_messages_to_openai( # noqa: PLR0915 tool_call_id=content.get("tool_use_id", ""), content=combined_content_parts, # type: ignore ) - self._add_cache_control_if_applicable( - content, tool_result, model - ) + self._add_cache_control_if_applicable(content, tool_result, model) tool_message_list.append(tool_result) # type: ignore[arg-type] if len(tool_message_list) > 0: @@ -550,14 +472,10 @@ def translate_anthropic_messages_to_openai( # noqa: PLR0915 ## ASSISTANT MESSAGE ## assistant_message_str: Optional[str] = None - assistant_content_list: List[ - Dict[str, Any] - ] = [] # For content blocks with cache_control + assistant_content_list: List[Dict[str, Any]] = [] # For content blocks with cache_control has_cache_control_in_text = False tool_calls: List[ChatCompletionAssistantToolCall] = [] - thinking_blocks: List[ - Union[ChatCompletionThinkingBlock, ChatCompletionRedactedThinkingBlock] - ] = [] + thinking_blocks: List[Union[ChatCompletionThinkingBlock, ChatCompletionRedactedThinkingBlock]] = [] if m["role"] == "assistant": if isinstance(m.get("content"), str): assistant_message_str = str(m.get("content", "")) @@ -571,9 +489,7 @@ def translate_anthropic_messages_to_openai( # noqa: PLR0915 "type": "text", "text": content.get("text", ""), } - self._add_cache_control_if_applicable( - content, text_block, model - ) + self._add_cache_control_if_applicable(content, text_block, model) if "cache_control" in text_block: has_cache_control_in_text = True assistant_content_list.append(text_block) @@ -584,32 +500,21 @@ def translate_anthropic_messages_to_openai( # noqa: PLR0915 "name": tool_name, "arguments": json.dumps(content.get("input", {})), } - signature = ( - self._extract_signature_from_tool_use_content( - cast(Dict[str, Any], content) - ) - ) + signature = self._extract_signature_from_tool_use_content(cast(Dict[str, Any], content)) if signature: provider_specific_fields: Dict[str, Any] = ( - function_chunk.get("provider_specific_fields") - or {} + function_chunk.get("provider_specific_fields") or {} ) - provider_specific_fields[ - "thought_signature" - ] = signature - function_chunk[ - "provider_specific_fields" - ] = provider_specific_fields + provider_specific_fields["thought_signature"] = signature + function_chunk["provider_specific_fields"] = provider_specific_fields tool_call = ChatCompletionAssistantToolCall( id=content.get("id", ""), type="function", function=function_chunk, ) - self._add_cache_control_if_applicable( - content, tool_call, model - ) + self._add_cache_control_if_applicable(content, tool_call, model) tool_calls.append(tool_call) elif content.get("type") == "thinking": thinking_block = ChatCompletionThinkingBlock( @@ -620,12 +525,10 @@ def translate_anthropic_messages_to_openai( # noqa: PLR0915 ) thinking_blocks.append(thinking_block) elif content.get("type") == "redacted_thinking": - redacted_thinking_block = ( - ChatCompletionRedactedThinkingBlock( - type="redacted_thinking", - data=content.get("data") or "", - cache_control=content.get("cache_control", {}), - ) + redacted_thinking_block = ChatCompletionRedactedThinkingBlock( + type="redacted_thinking", + data=content.get("data") or "", + cache_control=content.get("cache_control", {}), ) thinking_blocks.append(redacted_thinking_block) @@ -640,18 +543,14 @@ def translate_anthropic_messages_to_openai( # noqa: PLR0915 assistant_content: Any = assistant_content_list elif len(assistant_content_list) > 0 and not has_cache_control_in_text: # Concatenate text blocks into string when no cache_control - assistant_content = "".join( - block.get("text", "") for block in assistant_content_list - ) + assistant_content = "".join(block.get("text", "") for block in assistant_content_list) else: assistant_content = assistant_message_str assistant_message = ChatCompletionAssistantMessage( role="assistant", content=assistant_content, - thinking_blocks=( - thinking_blocks if len(thinking_blocks) > 0 else None - ), + thinking_blocks=(thinking_blocks if len(thinking_blocks) > 0 else None), ) if len(tool_calls) > 0: assistant_message["tool_calls"] = tool_calls # type: ignore @@ -662,9 +561,7 @@ def translate_anthropic_messages_to_openai( # noqa: PLR0915 return new_messages @staticmethod - def translate_anthropic_thinking_to_reasoning_effort( - thinking: Dict[str, Any] - ) -> Optional[str]: + def translate_anthropic_thinking_to_reasoning_effort(thinking: Dict[str, Any]) -> Optional[str]: """ Translate Anthropic's thinking parameter to OpenAI's reasoning_effort. @@ -738,9 +635,7 @@ def translate_thinking_for_model( thinking ) if reasoning_effort: - summary = ( - thinking.get("summary") if isinstance(thinking, dict) else None - ) + summary = thinking.get("summary") if isinstance(thinking, dict) else None auto_summary = is_reasoning_auto_summary_enabled() if summary: return { @@ -770,16 +665,12 @@ def translate_anthropic_tool_choice_to_openai( # Truncate tool name if it exceeds OpenAI's 64-char limit original_name = tool_choice.get("name", "") truncated_name = truncate_tool_name(original_name) - tc_function_param = ChatCompletionToolChoiceFunctionParam( - name=truncated_name - ) - return ChatCompletionToolChoiceObjectParam( - type="function", function=tc_function_param - ) + tc_function_param = ChatCompletionToolChoiceFunctionParam(name=truncated_name) + return ChatCompletionToolChoiceObjectParam(type="function", function=tc_function_param) + elif tool_choice["type"] == "none": + return "none" else: - raise ValueError( - "Incompatible tool choice param submitted - {}".format(tool_choice) - ) + raise ValueError("Incompatible tool choice param submitted - {}".format(tool_choice)) def translate_anthropic_tools_to_openai( self, tools: List[AllAnthropicToolsValues], model: Optional[str] = None @@ -822,17 +713,13 @@ def translate_anthropic_tools_to_openai( for k, v in tool.items(): if k not in mapped_tool_params: # pass additional computer kwargs function_chunk.setdefault("parameters", {}).update({k: v}) - tool_param = ChatCompletionToolParam( - type="function", function=function_chunk - ) + tool_param = ChatCompletionToolParam(type="function", function=function_chunk) self._add_cache_control_if_applicable(tool, tool_param, model) new_tools.append(tool_param) # type: ignore[arg-type] return new_tools, tool_name_mapping # type: ignore[return-value] - def translate_anthropic_output_format_to_openai( - self, output_format: Any - ) -> Optional[Dict[str, Any]]: + def translate_anthropic_output_format_to_openai(self, output_format: Any) -> Optional[Dict[str, Any]]: """ Translate Anthropic's output_format to OpenAI's response_format. @@ -891,25 +778,19 @@ def _add_additional_properties_false(schema: dict) -> None: # Handle array items if "items" in schema: - LiteLLMAnthropicMessagesAdapter._add_additional_properties_false( - schema["items"] - ) + LiteLLMAnthropicMessagesAdapter._add_additional_properties_false(schema["items"]) # Handle anyOf/oneOf/allOf for key in ("anyOf", "oneOf", "allOf"): if key in schema: for sub_schema in schema[key]: - LiteLLMAnthropicMessagesAdapter._add_additional_properties_false( - sub_schema - ) + LiteLLMAnthropicMessagesAdapter._add_additional_properties_false(sub_schema) # Handle $defs / definitions for key in ("$defs", "definitions"): if key in schema: for def_schema in schema[key].values(): - LiteLLMAnthropicMessagesAdapter._add_additional_properties_false( - def_schema - ) + LiteLLMAnthropicMessagesAdapter._add_additional_properties_false(def_schema) def _add_system_message_to_messages( self, @@ -1029,9 +910,7 @@ def _translate_thinking_to_openai( new_kwargs["thinking"] = thinking # type: ignore return - reasoning_effort = self.translate_anthropic_thinking_to_reasoning_effort( - cast(Dict[str, Any], thinking) - ) + reasoning_effort = self.translate_anthropic_thinking_to_reasoning_effort(cast(Dict[str, Any], thinking)) if not reasoning_effort: return @@ -1067,9 +946,7 @@ def _translate_output_format_to_openai( output_format = anthropic_message_request["output_format"] if not output_format: return - response_format = self.translate_anthropic_output_format_to_openai( - output_format=output_format - ) + response_format = self.translate_anthropic_output_format_to_openai(output_format=output_format) if response_format: new_kwargs["response_format"] = response_format @@ -1100,11 +977,7 @@ def translate_anthropic_to_openai( tool_name_mapping: Dict[str, str] = {} ## CONVERT ANTHROPIC MESSAGES TO OPENAI - messages_list: List[ - Union[ - AnthropicMessagesUserMessageParam, AnthopicMessagesAssistantMessageParam - ] - ] = cast( + messages_list: List[Union[AnthropicMessagesUserMessageParam, AnthopicMessagesAssistantMessageParam]] = cast( List[ Union[ AnthropicMessagesUserMessageParam, @@ -1191,10 +1064,7 @@ def _translate_openai_content_to_anthropic( new_content: List[Dict[str, Any]] = [] for choice in choices: # Handle thinking blocks first - if ( - hasattr(choice.message, "thinking_blocks") - and choice.message.thinking_blocks - ): + if hasattr(choice.message, "thinking_blocks") and choice.message.thinking_blocks: for thinking_block in choice.message.thinking_blocks: if thinking_block.get("type") == "thinking": thinking_value = thinking_block.get("thinking", "") @@ -1202,16 +1072,8 @@ def _translate_openai_content_to_anthropic( new_content.append( AnthropicResponseContentBlockThinking( type="thinking", - thinking=( - str(thinking_value) - if thinking_value is not None - else "" - ), - signature=( - str(signature_value) - if signature_value is not None - else None - ), + thinking=(str(thinking_value) if thinking_value is not None else ""), + signature=(str(signature_value) if signature_value is not None else None), ).model_dump() ) elif thinking_block.get("type") == "redacted_thinking": @@ -1223,10 +1085,7 @@ def _translate_openai_content_to_anthropic( ).model_dump() ) # Handle reasoning_content when thinking_blocks is not present - elif ( - hasattr(choice.message, "reasoning_content") - and choice.message.reasoning_content - ): + elif hasattr(choice.message, "reasoning_content") and choice.message.reasoning_content: new_content.append( AnthropicResponseContentBlockThinking( type="thinking", @@ -1238,15 +1097,10 @@ def _translate_openai_content_to_anthropic( # Handle text content if choice.message.content is not None: new_content.append( - AnthropicResponseContentBlockText( - type="text", text=choice.message.content - ).model_dump() + AnthropicResponseContentBlockText(type="text", text=choice.message.content).model_dump() ) # Handle tool calls (in parallel to text content) - if ( - choice.message.tool_calls is not None - and len(choice.message.tool_calls) > 0 - ): + if choice.message.tool_calls is not None and len(choice.message.tool_calls) > 0: for tool_call in choice.message.tool_calls: # Extract signature from provider_specific_fields only signature = self._extract_signature_from_tool_call(tool_call) @@ -1258,9 +1112,7 @@ def _translate_openai_content_to_anthropic( # Restore original tool name if it was truncated truncated_name = tool_call.function.name or "" original_name = ( - tool_name_mapping.get(truncated_name, truncated_name) - if tool_name_mapping - else truncated_name + tool_name_mapping.get(truncated_name, truncated_name) if tool_name_mapping else truncated_name ) tool_use_block = AnthropicResponseContentBlockToolUse( @@ -1275,16 +1127,12 @@ def _translate_openai_content_to_anthropic( ) # Add provider_specific_fields if signature is present if provider_specific_fields: - tool_use_block.provider_specific_fields = ( - provider_specific_fields - ) + tool_use_block.provider_specific_fields = provider_specific_fields new_content.append(tool_use_block.model_dump()) return new_content - def _translate_openai_finish_reason_to_anthropic( - self, openai_finish_reason: str - ) -> AnthropicFinishReason: + def _translate_openai_finish_reason_to_anthropic(self, openai_finish_reason: str) -> AnthropicFinishReason: if openai_finish_reason == "stop": return "end_turn" elif openai_finish_reason == "length": @@ -1321,22 +1169,15 @@ def translate_openai_response_to_anthropic( uncached_input_tokens = usage.prompt_tokens or 0 cached_tokens = 0 if hasattr(usage, "prompt_tokens_details") and usage.prompt_tokens_details: - cached_tokens = ( - getattr(usage.prompt_tokens_details, "cached_tokens", 0) or 0 - ) + cached_tokens = getattr(usage.prompt_tokens_details, "cached_tokens", 0) or 0 uncached_input_tokens -= cached_tokens anthropic_usage = AnthropicUsage( input_tokens=uncached_input_tokens, output_tokens=usage.completion_tokens or 0, ) - if ( - hasattr(usage, "_cache_creation_input_tokens") - and usage._cache_creation_input_tokens > 0 - ): - anthropic_usage[ - "cache_creation_input_tokens" - ] = usage._cache_creation_input_tokens + if hasattr(usage, "_cache_creation_input_tokens") and usage._cache_creation_input_tokens > 0: + anthropic_usage["cache_creation_input_tokens"] = usage._cache_creation_input_tokens if cached_tokens > 0: anthropic_usage["cache_read_input_tokens"] = cached_tokens @@ -1376,9 +1217,7 @@ def _translate_streaming_openai_chunk_to_anthropic_content_block( ) elif choice.delta.content is not None and len(choice.delta.content) > 0: return "text", TextBlock(type="text", text="") - elif isinstance(choice, StreamingChoices) and hasattr( - choice.delta, "thinking_blocks" - ): + elif isinstance(choice, StreamingChoices) and hasattr(choice.delta, "thinking_blocks"): thinking_blocks = choice.delta.thinking_blocks or [] if len(thinking_blocks) > 0: thinking_block = thinking_blocks[0] @@ -1421,14 +1260,9 @@ def _translate_streaming_openai_chunk_to_anthropic( if choice.delta.tool_calls is not None: partial_json = "" for tool in choice.delta.tool_calls: - if ( - tool.function is not None - and tool.function.arguments is not None - ): + if tool.function is not None and tool.function.arguments is not None: partial_json = (partial_json or "") + tool.function.arguments - elif isinstance(choice, StreamingChoices) and hasattr( - choice.delta, "thinking_blocks" - ): + elif isinstance(choice, StreamingChoices) and hasattr(choice.delta, "thinking_blocks"): thinking_blocks = choice.delta.thinking_blocks or [] if len(thinking_blocks) > 0: for thinking_block in thinking_blocks: @@ -1443,25 +1277,17 @@ def _translate_streaming_openai_chunk_to_anthropic( reasoning_signature += signature # Handle reasoning_content when thinking_blocks is not present # This handles providers like OpenRouter that return reasoning_content - elif isinstance(choice, StreamingChoices) and hasattr( - choice.delta, "reasoning_content" - ): + elif isinstance(choice, StreamingChoices) and hasattr(choice.delta, "reasoning_content"): if choice.delta.reasoning_content is not None: reasoning_content += choice.delta.reasoning_content if reasoning_content and reasoning_signature: - raise ValueError( - "Both `reasoning` and `signature` in a single streaming chunk isn't supported." - ) + raise ValueError("Both `reasoning` and `signature` in a single streaming chunk isn't supported.") if partial_json is not None: - return "input_json_delta", ContentJsonBlockDelta( - type="input_json_delta", partial_json=partial_json - ) + return "input_json_delta", ContentJsonBlockDelta(type="input_json_delta", partial_json=partial_json) elif reasoning_content: - return "thinking_delta", ContentThinkingBlockDelta( - type="thinking_delta", thinking=reasoning_content - ) + return "thinking_delta", ContentThinkingBlockDelta(type="thinking_delta", thinking=reasoning_content) elif reasoning_signature: return "signature_delta", ContentThinkingSignatureBlockDelta( type="signature_delta", signature=reasoning_signature @@ -1475,26 +1301,18 @@ def translate_streaming_openai_response_to_anthropic( ## base case - final chunk w/ finish reason if response.choices[0].finish_reason is not None: delta = MessageDelta( - stop_reason=self._translate_openai_finish_reason_to_anthropic( - response.choices[0].finish_reason - ), + stop_reason=self._translate_openai_finish_reason_to_anthropic(response.choices[0].finish_reason), ) if getattr(response, "usage", None) is not None: litellm_usage_chunk: Optional[Usage] = response.usage # type: ignore - elif ( - hasattr(response, "_hidden_params") - and "usage" in response._hidden_params - ): + elif hasattr(response, "_hidden_params") and "usage" in response._hidden_params: litellm_usage_chunk = response._hidden_params["usage"] else: litellm_usage_chunk = None if litellm_usage_chunk is not None: uncached_input_tokens = litellm_usage_chunk.prompt_tokens or 0 cached_tokens = 0 - if ( - hasattr(litellm_usage_chunk, "prompt_tokens_details") - and litellm_usage_chunk.prompt_tokens_details - ): + if hasattr(litellm_usage_chunk, "prompt_tokens_details") and litellm_usage_chunk.prompt_tokens_details: cached_tokens = ( getattr( litellm_usage_chunk.prompt_tokens_details, @@ -1513,15 +1331,15 @@ def translate_streaming_openai_response_to_anthropic( hasattr(litellm_usage_chunk, "_cache_creation_input_tokens") and litellm_usage_chunk._cache_creation_input_tokens > 0 ): - usage_delta[ - "cache_creation_input_tokens" - ] = litellm_usage_chunk._cache_creation_input_tokens + usage_delta["cache_creation_input_tokens"] = litellm_usage_chunk._cache_creation_input_tokens if cached_tokens > 0: usage_delta["cache_read_input_tokens"] = cached_tokens else: usage_delta = UsageDelta(input_tokens=0, output_tokens=0) return MessageBlockDelta( - type="message_delta", delta=delta, usage=usage_delta # type: ignore + type="message_delta", + delta=delta, + usage=usage_delta, # type: ignore ) ( type_of_content,