This commit is contained in:
Azhar 2026-10-19 15:49:47 +00:00 • committed by GitHub
commit 2d1563a1ed
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 92 additions and 1 deletions

View file

@ -520,8 +520,25 @@ class LiteLLMAnthropicMessagesAdapter:
self._add_prompt_cache_breakpoint_if_present(content, image_obj)
)
elif content.get("type") == "document":
# Convert Anthropic document format (PDF, etc.) to OpenAI format
# Convert Anthropic document format to OpenAI format.
# Only image/* media types can be forwarded as image_url;
# non-image documents (e.g. application/pdf) are not
# accepted by image-only backends and must be rejected
# with a clear error rather than silently producing a
# malformed request.
source = content.get("source", {})
media_type = source.get("media_type", "") if isinstance(source, dict) else ""
if media_type and not str(media_type).startswith("image/"):
raise litellm.BadRequestError(
message=(
f"Document content block with media_type='{media_type}' cannot be "
"forwarded to this model as an image_url. Only image/* documents "
"are supported. For PDF documents, convert the pages to images "
"before sending."
),
model=model,
llm_provider="anthropic",
)
openai_image_url = self._translate_anthropic_image_to_openai(cast(dict, source))
if openai_image_url:

View file

@ -5235,3 +5235,77 @@ def test_eager_input_streaming_tool_reaches_bedrock_converse_as_beta():
assert data["additionalModelRequestFields"]["anthropic_beta"] == ["fine-grained-tool-streaming-2025-05-14"]
assert data["toolConfig"]["tools"][0]["toolSpec"]["inputSchema"]["json"] == EAGER_INPUT_SCHEMA
def test_document_pdf_raises_bad_request_error() -> None:
"""
Regression test for https://github.com/BerriAI/litellm/issues/46117
An Anthropic document block with media_type='application/pdf' must raise
BadRequestError when translated to OpenAI format, not silently produce a
malformed image_url with a PDF MIME type that the downstream backend rejects.
"""
adapter = LiteLLMAnthropicMessagesAdapter()
messages = [
AnthropicMessagesUserMessageParam(
role="user",
content=[
{
"type": "document",
"source": {
"type": "base64",
"media_type": "application/pdf",
"data": "JVBERi0xLjQK", # minimal synthetic PDF header
},
},
{"type": "text", "text": "Summarize this document."},
],
)
]
with pytest.raises(litellm.BadRequestError) as exc_info:
adapter.translate_anthropic_messages_to_openai(
messages=messages,
model="chatgpt/gpt-6.1-sol",
)
assert "application/pdf" in str(exc_info.value)
def test_document_image_jpeg_passes_through() -> None:
"""
An Anthropic document block with media_type='image/jpeg' must still be
translated to image_url without raising an error.
"""
adapter = LiteLLMAnthropicMessagesAdapter()
messages = [
AnthropicMessagesUserMessageParam(
role="user",
content=[
{
"type": "document",
"source": {
"type": "base64",
"media_type": "image/jpeg",
"data": "/9j/4AAQSkZJRgAB", # minimal JPEG header
},
},
{"type": "text", "text": "Describe this image."},
],
)
]
# Should not raise
result = adapter.translate_anthropic_messages_to_openai(
messages=messages,
model="chatgpt/gpt-6.1-sol",
)
# The image document should be translated to an image_url content block
user_msg = next(m for m in result if m.get("role") == "user")
content = user_msg.get("content", [])
image_blocks = [c for c in content if isinstance(c, dict) and c.get("type") == "image_url"]
assert len(image_blocks) == 1
url = image_blocks[0]["image_url"]["url"]
assert url.startswith("data:image/jpeg;base64,")