mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-09 22:31:41 +00:00
fix(anthropic): strip Gemini thought suffix from streaming tool_use id
Gemini tool call ids embed thought signatures as call_*__thought__*; the Anthropic /v1/messages SSE adapter now exposes a clean id and moves the signature to provider_specific_fields.signature for round-trip. Fixes #25836. Made-with: Cursor
This commit is contained in:
parent
f69b9d6564
commit
d881a48220
2 changed files with 70 additions and 7 deletions
|
|
@ -72,6 +72,9 @@ from openai.types.chat.chat_completion_chunk import Choice as OpenAIStreamingCho
|
|||
from litellm.litellm_core_utils.prompt_templates.common_utils import (
|
||||
parse_tool_call_arguments,
|
||||
)
|
||||
from litellm.litellm_core_utils.prompt_templates.factory import (
|
||||
THOUGHT_SIGNATURE_SEPARATOR,
|
||||
)
|
||||
from litellm.types.llms.anthropic import (
|
||||
ANTHROPIC_HOSTED_TOOLS,
|
||||
AllAnthropicToolsValues,
|
||||
|
|
@ -1366,7 +1369,7 @@ class LiteLLMAnthropicMessagesAdapter:
|
|||
"ContentBlockContentBlockDict",
|
||||
]:
|
||||
from litellm._uuid import uuid
|
||||
from litellm.types.llms.anthropic import TextBlock, ToolUseBlock
|
||||
from litellm.types.llms.anthropic import TextBlock
|
||||
|
||||
for choice in choices:
|
||||
if (
|
||||
|
|
@ -1374,12 +1377,25 @@ class LiteLLMAnthropicMessagesAdapter:
|
|||
and len(choice.delta.tool_calls) > 0
|
||||
and choice.delta.tool_calls[0].function is not None
|
||||
):
|
||||
return "tool_use", ToolUseBlock(
|
||||
type="tool_use",
|
||||
id=choice.delta.tool_calls[0].id or str(uuid.uuid4()),
|
||||
name=choice.delta.tool_calls[0].function.name or "",
|
||||
input={}, # type: ignore[typeddict-item]
|
||||
)
|
||||
raw_id = choice.delta.tool_calls[0].id or str(uuid.uuid4())
|
||||
tool_name = choice.delta.tool_calls[0].function.name or ""
|
||||
base_id = raw_id
|
||||
thought_sig: Optional[str] = None
|
||||
if THOUGHT_SIGNATURE_SEPARATOR in raw_id:
|
||||
parts = raw_id.split(THOUGHT_SIGNATURE_SEPARATOR, 1)
|
||||
base_id = parts[0]
|
||||
thought_sig = parts[1] if len(parts) > 1 else None
|
||||
tool_block: Dict[str, Any] = {
|
||||
"type": "tool_use",
|
||||
"id": base_id,
|
||||
"name": tool_name,
|
||||
"input": {},
|
||||
}
|
||||
if thought_sig:
|
||||
tool_block["provider_specific_fields"] = {
|
||||
"signature": thought_sig,
|
||||
}
|
||||
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(
|
||||
|
|
|
|||
|
|
@ -7,6 +7,9 @@ import pytest
|
|||
sys.path.insert(0, os.path.abspath("../../../../.."))
|
||||
|
||||
|
||||
from litellm.litellm_core_utils.prompt_templates.factory import (
|
||||
THOUGHT_SIGNATURE_SEPARATOR,
|
||||
)
|
||||
from litellm.llms.anthropic.experimental_pass_through.adapters.transformation import (
|
||||
OPENAI_MAX_TOOL_NAME_LENGTH,
|
||||
LiteLLMAnthropicMessagesAdapter,
|
||||
|
|
@ -74,6 +77,50 @@ def test_translate_streaming_openai_chunk_to_anthropic_content_block():
|
|||
}
|
||||
|
||||
|
||||
def test_translate_streaming_openai_chunk_strips_gemini_thought_from_tool_call_id():
|
||||
"""Gemini embeds thought signatures in OpenAI tool ids; Anthropic SSE should expose a clean id."""
|
||||
base = "call_3e9417b7925e49aca9a71dc1885e"
|
||||
sig = "CiIBDDnWx"
|
||||
combined = f"{base}{THOUGHT_SIGNATURE_SEPARATOR}{sig}"
|
||||
choices = [
|
||||
StreamingChoices(
|
||||
finish_reason=None,
|
||||
index=0,
|
||||
delta=Delta(
|
||||
provider_specific_fields=None,
|
||||
content=None,
|
||||
role="assistant",
|
||||
function_call=None,
|
||||
tool_calls=[
|
||||
ChatCompletionDeltaToolCall(
|
||||
id=combined,
|
||||
function=Function(
|
||||
arguments='{"a": 17, "b": 25}', name="add_numbers"
|
||||
),
|
||||
type="function",
|
||||
index=0,
|
||||
)
|
||||
],
|
||||
audio=None,
|
||||
),
|
||||
logprobs=None,
|
||||
)
|
||||
]
|
||||
|
||||
(
|
||||
block_type,
|
||||
content_block_start,
|
||||
) = LiteLLMAnthropicMessagesAdapter()._translate_streaming_openai_chunk_to_anthropic_content_block(
|
||||
choices=choices
|
||||
)
|
||||
|
||||
assert block_type == "tool_use"
|
||||
assert content_block_start["id"] == base
|
||||
assert content_block_start["name"] == "add_numbers"
|
||||
assert content_block_start["input"] == {}
|
||||
assert content_block_start["provider_specific_fields"]["signature"] == sig
|
||||
|
||||
|
||||
def test_translate_streaming_openai_chunk_to_anthropic_thinking_content_block():
|
||||
choices = [
|
||||
StreamingChoices(
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue