mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-30 01:52:18 +00:00
refactor: revert formatter churn, keep only the thinking_blocks gate
The previous commit ran a formatter over the whole file, which inflated the diff to +334/-98 in this file when the actual change is 16 lines. Reverted to upstream formatting and re-applied only the logic change, so the diff now shows just the provider gate and the reasoning_content fallback. Verified equivalent: the whole-file AST of this version is identical to the previous commit, so this is a presentation change only, with no behaviour change and no change to the tests.
This commit is contained in:
parent
77e0e46675
commit
aa4f29bb33
1 changed files with 104 additions and 326 deletions
|
|
@ -143,9 +143,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.
|
||||
|
||||
|
|
@ -178,27 +176,19 @@ class AnthropicAdapter:
|
|||
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
|
||||
|
||||
|
|
@ -247,15 +237,9 @@ class AnthropicAdapter:
|
|||
the sync handler) don't get back an async iterator they
|
||||
can't iterate without an event loop.
|
||||
"""
|
||||
applied_edits = (
|
||||
polyfill_result.applied_edits_for_response() if polyfill_result else None
|
||||
)
|
||||
compaction_block = (
|
||||
polyfill_result.compaction_block if polyfill_result is not None else None
|
||||
)
|
||||
iterations_usage = (
|
||||
polyfill_result.iterations_usage if polyfill_result is not None else None
|
||||
)
|
||||
applied_edits = polyfill_result.applied_edits_for_response() if polyfill_result else None
|
||||
compaction_block = polyfill_result.compaction_block if polyfill_result is not None else None
|
||||
iterations_usage = polyfill_result.iterations_usage if polyfill_result is not None else None
|
||||
anthropic_wrapper = AnthropicStreamWrapper(
|
||||
completion_stream=completion_stream,
|
||||
model=model,
|
||||
|
|
@ -283,26 +267,16 @@ class LiteLLMAnthropicMessagesAdapter:
|
|||
"""
|
||||
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.
|
||||
"""
|
||||
|
|
@ -332,18 +306,9 @@ class LiteLLMAnthropicMessagesAdapter:
|
|||
"""
|
||||
# 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)
|
||||
or self.is_bedrock_arn_model(model)
|
||||
)
|
||||
):
|
||||
if cache_control and model and (self.is_anthropic_claude_model(model) or self.is_bedrock_arn_model(model)):
|
||||
# TypedDict objects support dict operations at runtime
|
||||
# Use type ignore consistent with codebase pattern (see anthropic/chat/transformation.py:432)
|
||||
if isinstance(target, dict):
|
||||
|
|
@ -383,9 +348,7 @@ class LiteLLMAnthropicMessagesAdapter:
|
|||
"""
|
||||
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(
|
||||
self,
|
||||
|
|
@ -401,66 +364,38 @@ class LiteLLMAnthropicMessagesAdapter:
|
|||
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:
|
||||
|
|
@ -469,9 +404,7 @@ class LiteLLMAnthropicMessagesAdapter:
|
|||
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(
|
||||
|
|
@ -479,9 +412,7 @@ class LiteLLMAnthropicMessagesAdapter:
|
|||
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
|
||||
|
|
@ -498,41 +429,28 @@ class LiteLLMAnthropicMessagesAdapter:
|
|||
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
|
||||
|
|
@ -545,11 +463,7 @@ class LiteLLMAnthropicMessagesAdapter:
|
|||
] = []
|
||||
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(
|
||||
|
|
@ -561,10 +475,7 @@ class LiteLLMAnthropicMessagesAdapter:
|
|||
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(
|
||||
|
|
@ -582,9 +493,7 @@ class LiteLLMAnthropicMessagesAdapter:
|
|||
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:
|
||||
|
|
@ -598,14 +507,10 @@ class LiteLLMAnthropicMessagesAdapter:
|
|||
|
||||
## 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", ""))
|
||||
|
|
@ -619,9 +524,7 @@ class LiteLLMAnthropicMessagesAdapter:
|
|||
"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)
|
||||
|
|
@ -632,32 +535,21 @@ class LiteLLMAnthropicMessagesAdapter:
|
|||
"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 {}
|
||||
)
|
||||
provider_specific_fields["thought_signature"] = (
|
||||
signature
|
||||
)
|
||||
function_chunk["provider_specific_fields"] = (
|
||||
provider_specific_fields
|
||||
function_chunk.get("provider_specific_fields") or {}
|
||||
)
|
||||
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(
|
||||
|
|
@ -668,12 +560,10 @@ class LiteLLMAnthropicMessagesAdapter:
|
|||
)
|
||||
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)
|
||||
|
||||
|
|
@ -688,9 +578,7 @@ class LiteLLMAnthropicMessagesAdapter:
|
|||
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
|
||||
|
||||
|
|
@ -701,28 +589,19 @@ class LiteLLMAnthropicMessagesAdapter:
|
|||
if len(tool_calls) > 0:
|
||||
assistant_message["tool_calls"] = tool_calls # type: ignore
|
||||
if len(thinking_blocks) > 0:
|
||||
if (
|
||||
model is None
|
||||
or LiteLLMAnthropicMessagesAdapter.is_anthropic_claude_model(
|
||||
model
|
||||
)
|
||||
):
|
||||
# Claude/Anthropic backends (or unknown target) require
|
||||
# thinking_blocks for multi-turn extended thinking round-trips.
|
||||
if model is None or LiteLLMAnthropicMessagesAdapter.is_anthropic_claude_model(model):
|
||||
# Claude/Anthropic backends (or an unknown target) need thinking_blocks
|
||||
# for multi-turn extended thinking round-trips.
|
||||
assistant_message["thinking_blocks"] = thinking_blocks # type: ignore
|
||||
else:
|
||||
# Non-Claude backends (vLLM, SGLang, etc.) don't understand
|
||||
# thinking_blocks; forwarding it causes phrase-repetition loops on
|
||||
# long tool chains because the rendered reasoning re-enters the
|
||||
# prompt on top of any reasoning_content the backend already has.
|
||||
# Replay only the text via reasoning_content instead.
|
||||
# Non-Claude backends (vLLM, SGLang, etc.) do not understand thinking_blocks;
|
||||
# forwarding them causes phrase-repetition loops on long tool chains, because the
|
||||
# rendered reasoning re-enters the prompt on top of any reasoning_content the
|
||||
# backend already produced. Replay only the text via reasoning_content instead.
|
||||
reasoning_content = "\n".join(
|
||||
str(part)
|
||||
for block in thinking_blocks
|
||||
if (
|
||||
part := block.get("thinking", "")
|
||||
or block.get("data", "")
|
||||
)
|
||||
if (part := block.get("thinking", "") or block.get("data", ""))
|
||||
)
|
||||
if reasoning_content and reasoning_content.strip():
|
||||
assistant_message["reasoning_content"] = reasoning_content # type: ignore
|
||||
|
|
@ -751,9 +630,7 @@ class LiteLLMAnthropicMessagesAdapter:
|
|||
if thinking_type == "disabled":
|
||||
return None
|
||||
elif thinking_type == "enabled":
|
||||
return reasoning_effort_from_thinking_budget(
|
||||
thinking.get("budget_tokens", 0)
|
||||
)
|
||||
return reasoning_effort_from_thinking_budget(thinking.get("budget_tokens", 0))
|
||||
elif thinking_type == "adaptive":
|
||||
# Adaptive thinking: effort is controlled by output_config.effort,
|
||||
# not budget_tokens. Return a default; caller should override with
|
||||
|
|
@ -819,9 +696,7 @@ class LiteLLMAnthropicMessagesAdapter:
|
|||
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 {
|
||||
|
|
@ -851,18 +726,12 @@ class LiteLLMAnthropicMessagesAdapter:
|
|||
# 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
|
||||
|
|
@ -898,9 +767,7 @@ class LiteLLMAnthropicMessagesAdapter:
|
|||
continue
|
||||
|
||||
raw_name = tool.get("name")
|
||||
if raw_name is None or (
|
||||
isinstance(raw_name, str) and not str(raw_name).strip()
|
||||
):
|
||||
if raw_name is None or (isinstance(raw_name, str) and not str(raw_name).strip()):
|
||||
original_name = f"litellm_unnamed_tool_{idx}"
|
||||
else:
|
||||
original_name = str(raw_name)
|
||||
|
|
@ -921,17 +788,13 @@ class LiteLLMAnthropicMessagesAdapter:
|
|||
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.
|
||||
|
||||
|
|
@ -990,25 +853,19 @@ class LiteLLMAnthropicMessagesAdapter:
|
|||
|
||||
# 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,
|
||||
|
|
@ -1128,9 +985,7 @@ class LiteLLMAnthropicMessagesAdapter:
|
|||
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
|
||||
|
||||
|
|
@ -1185,9 +1040,7 @@ class LiteLLMAnthropicMessagesAdapter:
|
|||
output_format = output_config.get("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
|
||||
|
||||
|
|
@ -1218,11 +1071,7 @@ class LiteLLMAnthropicMessagesAdapter:
|
|||
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,
|
||||
|
|
@ -1309,10 +1158,7 @@ class LiteLLMAnthropicMessagesAdapter:
|
|||
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", "")
|
||||
|
|
@ -1320,16 +1166,8 @@ class LiteLLMAnthropicMessagesAdapter:
|
|||
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":
|
||||
|
|
@ -1341,10 +1179,7 @@ class LiteLLMAnthropicMessagesAdapter:
|
|||
).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",
|
||||
|
|
@ -1356,15 +1191,10 @@ class LiteLLMAnthropicMessagesAdapter:
|
|||
# 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)
|
||||
|
|
@ -1376,9 +1206,7 @@ class LiteLLMAnthropicMessagesAdapter:
|
|||
# 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
|
||||
)
|
||||
|
||||
# Strip Gemini thought-signature suffix and normalize id chars
|
||||
|
|
@ -1396,16 +1224,12 @@ class LiteLLMAnthropicMessagesAdapter:
|
|||
)
|
||||
# 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":
|
||||
|
|
@ -1425,9 +1249,7 @@ class LiteLLMAnthropicMessagesAdapter:
|
|||
return 0
|
||||
|
||||
@classmethod
|
||||
def _first_positive_usage_value(
|
||||
cls, usage: Usage, field_names: tuple[str, ...]
|
||||
) -> int:
|
||||
def _first_positive_usage_value(cls, usage: Usage, field_names: tuple[str, ...]) -> int:
|
||||
for field_name in field_names:
|
||||
value = cls._positive_int(getattr(usage, field_name, None))
|
||||
if value > 0:
|
||||
|
|
@ -1435,9 +1257,7 @@ class LiteLLMAnthropicMessagesAdapter:
|
|||
return 0
|
||||
|
||||
@classmethod
|
||||
def _first_positive_prompt_tokens_detail_value(
|
||||
cls, usage: Usage, field_names: tuple[str, ...]
|
||||
) -> int:
|
||||
def _first_positive_prompt_tokens_detail_value(cls, usage: Usage, field_names: tuple[str, ...]) -> int:
|
||||
prompt_tokens_details = getattr(usage, "prompt_tokens_details", None)
|
||||
if prompt_tokens_details is None:
|
||||
return 0
|
||||
|
|
@ -1446,18 +1266,14 @@ class LiteLLMAnthropicMessagesAdapter:
|
|||
if isinstance(prompt_tokens_details, dict):
|
||||
value = cls._positive_int(prompt_tokens_details.get(field_name))
|
||||
else:
|
||||
value = cls._positive_int(
|
||||
getattr(prompt_tokens_details, field_name, None)
|
||||
)
|
||||
value = cls._positive_int(getattr(prompt_tokens_details, field_name, None))
|
||||
if value > 0:
|
||||
return value
|
||||
return 0
|
||||
|
||||
@classmethod
|
||||
def _get_cache_read_input_tokens(cls, usage: Usage) -> int:
|
||||
explicit_value = cls._first_positive_usage_value(
|
||||
usage, ("cache_read_input_tokens", "_cache_read_input_tokens")
|
||||
)
|
||||
explicit_value = cls._first_positive_usage_value(usage, ("cache_read_input_tokens", "_cache_read_input_tokens"))
|
||||
if explicit_value > 0:
|
||||
return explicit_value
|
||||
return cls._first_positive_prompt_tokens_detail_value(usage, ("cached_tokens",))
|
||||
|
|
@ -1469,20 +1285,14 @@ class LiteLLMAnthropicMessagesAdapter:
|
|||
)
|
||||
if explicit_value > 0:
|
||||
return explicit_value
|
||||
return cls._first_positive_prompt_tokens_detail_value(
|
||||
usage, ("cache_creation_tokens", "cache_write_tokens")
|
||||
)
|
||||
return cls._first_positive_prompt_tokens_detail_value(usage, ("cache_creation_tokens", "cache_write_tokens"))
|
||||
|
||||
@classmethod
|
||||
def _translate_openai_usage_to_anthropic_usage_delta(
|
||||
cls, usage: Usage
|
||||
) -> UsageDelta:
|
||||
def _translate_openai_usage_to_anthropic_usage_delta(cls, usage: Usage) -> UsageDelta:
|
||||
cache_read_input_tokens = cls._get_cache_read_input_tokens(usage)
|
||||
cache_creation_input_tokens = cls._get_cache_creation_input_tokens(usage)
|
||||
input_tokens = max(
|
||||
(usage.prompt_tokens or 0)
|
||||
- cache_read_input_tokens
|
||||
- cache_creation_input_tokens,
|
||||
(usage.prompt_tokens or 0) - cache_read_input_tokens - cache_creation_input_tokens,
|
||||
0,
|
||||
)
|
||||
|
||||
|
|
@ -1555,13 +1365,9 @@ class LiteLLMAnthropicMessagesAdapter:
|
|||
stop_reason=anthropic_finish_reason,
|
||||
)
|
||||
|
||||
applied_edits = (
|
||||
polyfill_result.applied_edits_for_response() if polyfill_result else None
|
||||
)
|
||||
applied_edits = polyfill_result.applied_edits_for_response() if polyfill_result else None
|
||||
if applied_edits:
|
||||
translated_obj["context_management"] = ContextManagementResponse(
|
||||
applied_edits=list(applied_edits)
|
||||
)
|
||||
translated_obj["context_management"] = ContextManagementResponse(applied_edits=list(applied_edits))
|
||||
|
||||
return translated_obj
|
||||
|
||||
|
|
@ -1599,9 +1405,7 @@ class LiteLLMAnthropicMessagesAdapter:
|
|||
return "tool_use", cast("ContentBlockContentBlockDict", tool_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]
|
||||
|
|
@ -1625,12 +1429,8 @@ class LiteLLMAnthropicMessagesAdapter:
|
|||
# ``Delta`` deletes the ``thinking_blocks`` attribute when unset, so the
|
||||
# branch above is skipped entirely; open a ``thinking`` block here so the
|
||||
# matching ``thinking_delta`` stream is not emitted into a text block.
|
||||
elif isinstance(choice, StreamingChoices) and getattr(
|
||||
choice.delta, "reasoning_content", None
|
||||
):
|
||||
return "thinking", ChatCompletionThinkingBlock(
|
||||
type="thinking", thinking="", signature=""
|
||||
)
|
||||
elif isinstance(choice, StreamingChoices) and getattr(choice.delta, "reasoning_content", None):
|
||||
return "thinking", ChatCompletionThinkingBlock(type="thinking", thinking="", signature="")
|
||||
|
||||
return "text", TextBlock(type="text", text="")
|
||||
|
||||
|
|
@ -1655,14 +1455,9 @@ class LiteLLMAnthropicMessagesAdapter:
|
|||
if choice.delta.tool_calls:
|
||||
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:
|
||||
|
|
@ -1677,25 +1472,17 @@ class LiteLLMAnthropicMessagesAdapter:
|
|||
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
|
||||
|
|
@ -1712,23 +1499,16 @@ class LiteLLMAnthropicMessagesAdapter:
|
|||
## 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:
|
||||
usage_delta = self._translate_openai_usage_to_anthropic_usage_delta(
|
||||
litellm_usage_chunk
|
||||
)
|
||||
usage_delta = self._translate_openai_usage_to_anthropic_usage_delta(litellm_usage_chunk)
|
||||
else:
|
||||
usage_delta = UsageDelta(input_tokens=0, output_tokens=0)
|
||||
message_block = MessageBlockDelta(
|
||||
|
|
@ -1737,9 +1517,7 @@ class LiteLLMAnthropicMessagesAdapter:
|
|||
usage=usage_delta, # type: ignore
|
||||
)
|
||||
if applied_edits:
|
||||
message_block["context_management"] = ContextManagementResponse(
|
||||
applied_edits=list(applied_edits)
|
||||
)
|
||||
message_block["context_management"] = ContextManagementResponse(applied_edits=list(applied_edits))
|
||||
return message_block
|
||||
(
|
||||
type_of_content,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue