@@ -23,7 +23,73 @@ def _tools_to_anthropic(tools: Sequence[core.tools.ToolLike]) -> list[dict[str,
2323 ]
2424
2525
26- def _messages_to_anthropic (
26+ def _file_part_to_anthropic (part : core .messages .FilePart ) -> dict [str , Any ]:
27+ """Convert a :class:`FilePart` to an Anthropic content block.
28+
29+ * ``image/*`` → ``{"type": "image", "source": ...}``
30+ * ``application/pdf`` → ``{"type": "document", "source": ...}``
31+ * ``text/plain`` → ``{"type": "document", "source": {"type": "text", ...}}``
32+ * anything else → ``ValueError``
33+ """
34+ mt = part .media_type
35+
36+ if mt .startswith ("image/" ):
37+ media_type = "image/jpeg" if mt == "image/*" else mt
38+ if isinstance (part .data , str ) and core .media .data .is_url (part .data ):
39+ return {
40+ "type" : "image" ,
41+ "source" : {"type" : "url" , "url" : part .data },
42+ }
43+ return {
44+ "type" : "image" ,
45+ "source" : {
46+ "type" : "base64" ,
47+ "media_type" : media_type ,
48+ "data" : core .media .data .data_to_base64 (part .data ),
49+ },
50+ }
51+
52+ if mt == "application/pdf" :
53+ if isinstance (part .data , str ) and core .media .data .is_url (part .data ):
54+ return {
55+ "type" : "document" ,
56+ "source" : {"type" : "url" , "url" : part .data },
57+ }
58+ return {
59+ "type" : "document" ,
60+ "source" : {
61+ "type" : "base64" ,
62+ "media_type" : "application/pdf" ,
63+ "data" : core .media .data .data_to_base64 (part .data ),
64+ },
65+ }
66+
67+ if mt == "text/plain" :
68+ # Anthropic accepts text documents with source.type="text"
69+ if isinstance (part .data , bytes ):
70+ text_data = part .data .decode ("utf-8" )
71+ elif core .media .data .is_url (part .data ):
72+ return {
73+ "type" : "document" ,
74+ "source" : {"type" : "url" , "url" : part .data },
75+ }
76+ else :
77+ import base64 as _b64
78+
79+ text_data = _b64 .b64decode (part .data ).decode ("utf-8" )
80+ return {
81+ "type" : "document" ,
82+ "source" : {
83+ "type" : "text" ,
84+ "media_type" : "text/plain" ,
85+ "data" : text_data ,
86+ },
87+ }
88+
89+ raise ValueError (f"Unsupported media type for Anthropic: { mt } " )
90+
91+
92+ async def _messages_to_anthropic (
2793 messages : list [core .messages .Message ],
2894) -> tuple [str | None , list [dict [str , Any ]]]:
2995 """Convert internal messages to Anthropic API format.
@@ -89,12 +155,21 @@ def _messages_to_anthropic(
89155 result .append ({"role" : "assistant" , "content" : content })
90156 if tool_results :
91157 result .append ({"role" : "user" , "content" : tool_results })
92- else :
93- # User messages
94- content_text = "" .join (
95- p .text for p in msg .parts if isinstance (p , core .messages .TextPart )
96- )
97- result .append ({"role" : "user" , "content" : content_text })
158+ elif msg .role == "user" :
159+ has_files = any (isinstance (p , core .messages .FilePart ) for p in msg .parts )
160+ if not has_files :
161+ content_text = "" .join (
162+ p .text for p in msg .parts if isinstance (p , core .messages .TextPart )
163+ )
164+ result .append ({"role" : "user" , "content" : content_text })
165+ else :
166+ user_content : list [dict [str , Any ]] = []
167+ for p in msg .parts :
168+ if isinstance (p , core .messages .TextPart ):
169+ user_content .append ({"type" : "text" , "text" : p .text })
170+ elif isinstance (p , core .messages .FilePart ):
171+ user_content .append (_file_part_to_anthropic (p ))
172+ result .append ({"role" : "user" , "content" : user_content })
98173
99174 # Merge consecutive same-role messages (e.g. synthetic user(tool_result)
100175 # followed by a real user message).
@@ -162,7 +237,7 @@ async def stream_events(
162237 output_type : type [pydantic .BaseModel ] | None = None ,
163238 ) -> AsyncGenerator [core .llm .StreamEvent ]:
164239 """Yield raw stream events from Anthropic API."""
165- system_prompt , anthropic_messages = _messages_to_anthropic (messages )
240+ system_prompt , anthropic_messages = await _messages_to_anthropic (messages )
166241 anthropic_tools = _tools_to_anthropic (tools ) if tools else None
167242
168243 kwargs : dict [str , Any ] = {
0 commit comments