mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-08 22:21:35 +00:00
fix(anthropic): translate tool_result document blocks in the /v1/messages bridge
(cherry picked from commit 17845b4fb0)
This commit is contained in:
parent
4b281e7892
commit
f0ee067aaa
2 changed files with 74 additions and 3 deletions
|
|
@ -433,7 +433,7 @@ class LiteLLMAnthropicMessagesAdapter:
|
|||
content_items = list(content.get("content", []))
|
||||
|
||||
# Single-item text keeps the backward-compatible string format; a single
|
||||
# image becomes a structured image_url part
|
||||
# image or document becomes a structured image_url part
|
||||
if len(content_items) == 1:
|
||||
c = content_items[0]
|
||||
if isinstance(c, str):
|
||||
|
|
@ -453,7 +453,7 @@ class LiteLLMAnthropicMessagesAdapter:
|
|||
)
|
||||
self._add_cache_control_if_applicable(content, tool_result, model)
|
||||
tool_message_list.append(tool_result)
|
||||
elif c.get("type") == "image":
|
||||
elif c.get("type") in ("image", "document"):
|
||||
image_part = self._tool_result_image_part(c.get("source"))
|
||||
tool_result = ChatCompletionToolMessage(
|
||||
role="tool",
|
||||
|
|
@ -481,7 +481,7 @@ class LiteLLMAnthropicMessagesAdapter:
|
|||
text=c.get("text", ""),
|
||||
)
|
||||
)
|
||||
elif c.get("type") == "image":
|
||||
elif c.get("type") in ("image", "document"):
|
||||
image_part = self._tool_result_image_part(c.get("source"))
|
||||
if image_part:
|
||||
combined_content_parts.append(image_part)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import base64
|
||||
from typing import Any, cast
|
||||
|
||||
import pytest
|
||||
|
|
@ -11,6 +12,7 @@ from litellm.litellm_core_utils.prompt_templates.common_utils import (
|
|||
)
|
||||
from litellm.litellm_core_utils.prompt_templates.factory import (
|
||||
THOUGHT_SIGNATURE_SEPARATOR,
|
||||
_bedrock_converse_messages_pt,
|
||||
)
|
||||
from litellm.llms.anthropic.experimental_pass_through.adapters.transformation import (
|
||||
OPENAI_MAX_TOOL_NAME_LENGTH,
|
||||
|
|
@ -3830,6 +3832,75 @@ def test_tool_result_plain_text_unchanged_by_openai_transform():
|
|||
assert _image_urls_in_user_messages(result) == []
|
||||
|
||||
|
||||
TOOL_RESULT_PDF_B64 = base64.b64encode(b"%PDF-1.4 minimal regression fixture").decode()
|
||||
|
||||
|
||||
def _base64_pdf_block():
|
||||
return {
|
||||
"type": "document",
|
||||
"source": {"type": "base64", "media_type": "application/pdf", "data": TOOL_RESULT_PDF_B64},
|
||||
}
|
||||
|
||||
|
||||
def test_tool_result_single_document_kept_as_pdf_data_url():
|
||||
adapter = LiteLLMAnthropicMessagesAdapter()
|
||||
translated = adapter.translate_anthropic_messages_to_openai(
|
||||
messages=[
|
||||
_anthropic_tool_use_turn("toolu_01"),
|
||||
_anthropic_tool_result_turn({"toolu_01": [_base64_pdf_block()]}),
|
||||
]
|
||||
)
|
||||
|
||||
tool_messages = [m for m in translated if m.get("role") == "tool"]
|
||||
assert len(tool_messages) == 1
|
||||
assert tool_messages[0]["content"] == [
|
||||
{
|
||||
"type": "image_url",
|
||||
"image_url": {"url": f"data:application/pdf;base64,{TOOL_RESULT_PDF_B64}"},
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
def test_tool_result_text_and_document_reach_bedrock_converse_tool_result():
|
||||
"""Claude Code >= 2.1.245 sends Read-tool PDF output as a document block inside
|
||||
tool_result; dropping it left bedrock converse models blind to the PDF content."""
|
||||
adapter = LiteLLMAnthropicMessagesAdapter()
|
||||
translated = adapter.translate_anthropic_messages_to_openai(
|
||||
messages=[
|
||||
AnthropicMessagesUserMessageParam(role="user", content="Read pong.pdf"),
|
||||
_anthropic_tool_use_turn("toolu_01"),
|
||||
_anthropic_tool_result_turn(
|
||||
{
|
||||
"toolu_01": [
|
||||
{"type": "text", "text": "PDF file read: pong.pdf (579 bytes)"},
|
||||
_base64_pdf_block(),
|
||||
]
|
||||
}
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
converse_messages = _bedrock_converse_messages_pt(
|
||||
messages=translated,
|
||||
model="anthropic.claude-haiku-4-5-20251001-v1:0",
|
||||
llm_provider="bedrock_converse",
|
||||
)
|
||||
|
||||
tool_results = [
|
||||
block["toolResult"]
|
||||
for message in converse_messages
|
||||
for block in message["content"]
|
||||
if "toolResult" in block
|
||||
]
|
||||
assert len(tool_results) == 1
|
||||
documents = [part["document"] for part in tool_results[0]["content"] if "document" in part]
|
||||
assert len(documents) == 1
|
||||
assert documents[0]["format"] == "pdf"
|
||||
assert documents[0]["source"]["bytes"] == TOOL_RESULT_PDF_B64
|
||||
texts = [part["text"] for part in tool_results[0]["content"] if "text" in part]
|
||||
assert texts == ["PDF file read: pong.pdf (579 bytes)"]
|
||||
|
||||
|
||||
def test_translate_anthropic_to_openai_carries_prompt_cache_breakpoint_on_system_and_user_blocks():
|
||||
explicit = {"mode": "explicit"}
|
||||
openai_request, _ = LiteLLMAnthropicMessagesAdapter().translate_anthropic_to_openai(
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue