mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-15 09:12:18 +00:00
fix(snowflake): answer 400 for user parts Cortex Claude cannot take, and link an https pdf with a pdf format as a document
This commit is contained in:
parent
98812f15ad
commit
490922d8a9
4 changed files with 67 additions and 0 deletions
|
|
@ -954,6 +954,11 @@ def create_anthropic_image_param(
|
|||
return _anthropic_content_element_factory(
|
||||
convert_to_anthropic_image_obj(openai_image_url=base64_url, format=format)
|
||||
)
|
||||
elif format is not None and format.partition(";")[0].strip() == "application/pdf":
|
||||
return AnthropicMessagesDocumentParam(
|
||||
type="document",
|
||||
source=AnthropicContentParamSourceUrl(type="url", url=image_url),
|
||||
)
|
||||
else:
|
||||
# HTTPS URL - pass directly for regular Anthropic
|
||||
return AnthropicMessagesImageParam(
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ Ref: https://docs.snowflake.com/en/user-guide/snowflake-cortex/cortex-rest-api
|
|||
"""
|
||||
|
||||
from collections.abc import Mapping, Sequence
|
||||
from itertools import chain
|
||||
from typing import TYPE_CHECKING, Any, Final, Protocol, TypedDict
|
||||
|
||||
import httpx
|
||||
|
|
@ -18,6 +19,7 @@ import litellm
|
|||
from litellm.exceptions import BadRequestError
|
||||
from litellm.litellm_core_utils.prompt_templates.factory import anthropic_messages_pt
|
||||
from litellm.litellm_core_utils.prompt_templates.image_handling import async_inline_remote_media
|
||||
from litellm.litellm_core_utils.prompt_templates.mid_conversation_system import message_field, parts_of
|
||||
from litellm.litellm_core_utils.url_utils import join_api_base
|
||||
from litellm.llms.anthropic.chat.handler import ModelResponseIterator as AnthropicStreamParser
|
||||
from litellm.llms.anthropic.chat.transformation import AnthropicConfig
|
||||
|
|
@ -98,6 +100,20 @@ def is_snowflake_claude_model(model: str) -> bool:
|
|||
return any(name.startswith(p) for p in _CLAUDE_MODEL_PREFIXES)
|
||||
|
||||
|
||||
_CORTEX_CLAUDE_USER_PART_TYPES: Final = frozenset({"text", "image_url", "document", "file"})
|
||||
|
||||
|
||||
def _user_parts(message: AllMessageValues) -> tuple[object, ...]:
|
||||
return parts_of(message.get("content")) if message["role"] == "user" else ()
|
||||
|
||||
|
||||
def _untranslatable_user_part_types(messages: Sequence[AllMessageValues]) -> frozenset[str]:
|
||||
part_types: Final = frozenset(
|
||||
str(message_field(part, "type")) for part in chain.from_iterable(map(_user_parts, messages))
|
||||
)
|
||||
return part_types - _CORTEX_CLAUDE_USER_PART_TYPES
|
||||
|
||||
|
||||
def _clean_input_schema(schema: object) -> object:
|
||||
return {key: value for key, value in schema.items() if key != "$schema"} if isinstance(schema, Mapping) else schema
|
||||
|
||||
|
|
@ -207,6 +223,13 @@ class SnowflakeConfig(SnowflakeBaseConfig, OpenAIGPTConfig):
|
|||
) -> tuple[list[AnthropicSystemMessageContent] | None, list[AllAnthropicPassThroughMessageValues]]:
|
||||
"""Cortex's /messages endpoint speaks Anthropic's dialect, so the shared translators own the shape."""
|
||||
conversation_messages: Final = list(messages)
|
||||
untranslatable: Final = _untranslatable_user_part_types(conversation_messages)
|
||||
if untranslatable and not drop_params:
|
||||
raise SnowflakeException(
|
||||
status_code=400,
|
||||
message=f"Snowflake Cortex Claude cannot take {', '.join(sorted(untranslatable))} parts in a user "
|
||||
"message. Remove them, or pass drop_params=True to drop them",
|
||||
)
|
||||
system: Final = [
|
||||
part
|
||||
for part in AnthropicConfig().translate_system_message(messages=conversation_messages)
|
||||
|
|
|
|||
|
|
@ -9436,3 +9436,16 @@ def test_create_anthropic_image_param_sends_a_downloaded_pdf_as_a_document(monke
|
|||
"type": "document",
|
||||
"source": {"type": "base64", "media_type": "application/pdf", "data": base64.b64encode(pdf_bytes).decode()},
|
||||
}
|
||||
|
||||
|
||||
def test_create_anthropic_image_param_links_an_https_pdf_as_a_document_only_when_format_says_pdf():
|
||||
url = "https://example.com/report.pdf"
|
||||
|
||||
assert create_anthropic_image_param({"url": url, "format": "application/pdf"}) == {
|
||||
"type": "document",
|
||||
"source": {"type": "url", "url": url},
|
||||
}
|
||||
assert create_anthropic_image_param({"url": url, "format": "image/png"}) == {
|
||||
"type": "image",
|
||||
"source": {"type": "url", "url": url},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -744,6 +744,32 @@ class TestSnowflakeCortexClaudeFixes:
|
|||
)
|
||||
assert body["system"] == [{"type": "text", "text": "be terse"}]
|
||||
|
||||
_AUDIO_TURN = (
|
||||
{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{"type": "text", "text": "hear"},
|
||||
{"type": "input_audio", "input_audio": {"data": "UklGRg==", "format": "wav"}},
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
def test_user_part_cortex_claude_cannot_take_is_a_400_naming_it(self):
|
||||
with pytest.raises(SnowflakeException) as exc_info:
|
||||
self._transform(list(self._AUDIO_TURN))
|
||||
assert exc_info.value.status_code == 400
|
||||
assert "input_audio" in str(exc_info.value)
|
||||
|
||||
def test_drop_params_drops_a_user_part_cortex_claude_cannot_take(self):
|
||||
body = SnowflakeConfig().transform_request(
|
||||
model="snowflake/claude-sonnet-4-6",
|
||||
messages=list(self._AUDIO_TURN),
|
||||
optional_params={},
|
||||
litellm_params={"drop_params": True},
|
||||
headers={},
|
||||
)
|
||||
assert body["messages"] == [{"role": "user", "content": [{"type": "text", "text": "hear"}]}]
|
||||
|
||||
def test_unknown_charset_on_a_text_attachment_is_a_400(self):
|
||||
with pytest.raises(SnowflakeException) as exc_info:
|
||||
self._transform(
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue