11from datetime import datetime , timezone
2- from typing import Any , ClassVar , Dict , List , Literal , Optional , Tuple , Union
2+ from typing import Any , ClassVar , Dict , List , Literal , Optional , Tuple , Union , cast , get_args
33
44from haystack import component , default_from_dict , default_to_dict , logging
55from haystack .components .generators .utils import _convert_streaming_chunks_to_chat_message
6- from haystack .dataclasses .chat_message import ChatMessage , ChatRole , ToolCall , ToolCallResult
6+ from haystack .dataclasses .chat_message import ChatMessage , ChatRole , TextContent , ToolCall , ToolCallResult
7+ from haystack .dataclasses .image_content import ImageContent
78from haystack .dataclasses .streaming_chunk import (
89 AsyncStreamingCallbackT ,
910 ComponentInfo ,
2627
2728from anthropic import Anthropic , AsyncAnthropic
2829from anthropic .resources .messages .messages import Message , RawMessageStreamEvent , Stream
29- from anthropic .types import MessageParam , TextBlockParam , ToolParam , ToolResultBlockParam , ToolUseBlockParam
30+ from anthropic .types import (
31+ ImageBlockParam ,
32+ MessageParam ,
33+ TextBlockParam ,
34+ ToolParam ,
35+ ToolResultBlockParam ,
36+ ToolUseBlockParam ,
37+ )
3038
3139logger = logging .getLogger (__name__ )
3240
3341
42+ # See https://docs.anthropic.com/en/api/messages for supported formats
43+ ImageFormat = Literal ["image/jpeg" , "image/png" , "image/gif" , "image/webp" ]
44+ IMAGE_SUPPORTED_FORMATS : list [ImageFormat ] = list (get_args (ImageFormat ))
45+
46+
3447# Mapping from Anthropic stop reasons to Haystack FinishReason values
3548FINISH_REASON_MAPPING : Dict [str , FinishReason ] = {
3649 "end_turn" : "stop" ,
4457
4558def _update_anthropic_message_with_tool_call_results (
4659 tool_call_results : List [ToolCallResult ],
47- content : List [Union [TextBlockParam , ToolUseBlockParam , ToolResultBlockParam ]],
60+ content : List [Union [TextBlockParam , ToolUseBlockParam , ToolResultBlockParam , ImageBlockParam ]],
4861) -> None :
4962 """
5063 Update an Anthropic message content list with tool call results.
@@ -119,13 +132,39 @@ def _convert_messages_to_anthropic_format(
119132 i += 1
120133 continue
121134
122- content : List [Union [TextBlockParam , ToolUseBlockParam , ToolResultBlockParam ]] = []
123-
124- if message .texts and message .texts [0 ]:
125- text_block = TextBlockParam (type = "text" , text = message .texts [0 ])
126- if cache_control :
127- text_block ["cache_control" ] = cache_control
128- content .append (text_block )
135+ content : List [Union [TextBlockParam , ToolUseBlockParam , ToolResultBlockParam , ImageBlockParam ]] = []
136+
137+ # Handle multimodal content (text and images) preserving order
138+ for part in message ._content :
139+ if isinstance (part , TextContent ) and part .text :
140+ text_block = TextBlockParam (type = "text" , text = part .text )
141+ if cache_control :
142+ text_block ["cache_control" ] = cache_control
143+ content .append (text_block )
144+ elif isinstance (part , ImageContent ):
145+ if not message .is_from (ChatRole .USER ):
146+ msg = "Image content is only supported for user messages"
147+ raise ValueError (msg )
148+
149+ if part .mime_type not in IMAGE_SUPPORTED_FORMATS :
150+ supported_formats = ", " .join (IMAGE_SUPPORTED_FORMATS )
151+ msg = (
152+ f"Unsupported image format: { part .mime_type } . "
153+ f"Anthropic supports the following formats: { supported_formats } "
154+ )
155+ raise ValueError (msg )
156+
157+ image_block = ImageBlockParam (
158+ type = "image" ,
159+ source = {
160+ "type" : "base64" ,
161+ "media_type" : cast (ImageFormat , part .mime_type ),
162+ "data" : part .base64_image ,
163+ },
164+ )
165+ if cache_control :
166+ image_block ["cache_control" ] = cache_control
167+ content .append (image_block )
129168
130169 if message .tool_calls :
131170 tool_use_blocks = _convert_tool_calls_to_anthropic_format (message .tool_calls )
@@ -148,7 +187,10 @@ def _convert_messages_to_anthropic_format(
148187 blk ["cache_control" ] = cache_control
149188
150189 if not content :
151- msg = "A `ChatMessage` must contain at least one `TextContent`, `ToolCall`, or `ToolCallResult`."
190+ msg = (
191+ "A `ChatMessage` must contain at least one `TextContent`, `ImageContent`, "
192+ "`ToolCall`, or `ToolCallResult`."
193+ )
152194 raise ValueError (msg )
153195
154196 # Anthropic only supports assistant and user roles in messages. User role is also used for tool messages.
@@ -170,7 +212,7 @@ class AnthropicChatGenerator:
170212 Completes chats using Anthropic's large language models (LLMs).
171213
172214 It uses [ChatMessage](https://docs.haystack.deepset.ai/docs/data-classes#chatmessage)
173- format in input and output.
215+ format in input and output. Supports multimodal inputs including text and images.
174216
175217 You can customize how the text is generated by passing parameters to the
176218 Anthropic API. Use the `**generation_kwargs` argument when you initialize
@@ -182,18 +224,41 @@ class AnthropicChatGenerator:
182224
183225 Usage example:
184226 ```python
185- from haystack_integrations.components.generators.anthropic import AnthropicChatGenerator
227+ from haystack_integrations.components.generators.anthropic import (
228+ AnthropicChatGenerator,
229+ )
186230 from haystack.dataclasses import ChatMessage
187231
188- generator = AnthropicChatGenerator(model="claude-sonnet-4-20250514",
189- generation_kwargs={
190- "max_tokens": 1000,
191- "temperature": 0.7,
192- })
193-
194- messages = [ChatMessage.from_system("You are a helpful, respectful and honest assistant"),
195- ChatMessage.from_user("What's Natural Language Processing?")]
232+ generator = AnthropicChatGenerator(
233+ model="claude-sonnet-4-20250514",
234+ generation_kwargs={
235+ "max_tokens": 1000,
236+ "temperature": 0.7,
237+ },
238+ )
239+
240+ messages = [
241+ ChatMessage.from_system(
242+ "You are a helpful, respectful and honest assistant"
243+ ),
244+ ChatMessage.from_user("What's Natural Language Processing?"),
245+ ]
196246 print(generator.run(messages=messages))
247+ ```
248+
249+ Usage example with images:
250+ ```python
251+ from haystack.dataclasses import ChatMessage, ImageContent
252+
253+ image_content = ImageContent.from_file_path("path/to/image.jpg")
254+ messages = [
255+ ChatMessage.from_user(
256+ content_parts=["What's in this image?", image_content]
257+ )
258+ ]
259+ generator = AnthropicChatGenerator()
260+ result = generator.run(messages)
261+ ```
197262 """
198263
199264 # The parameters that can be passed to the Anthropic API https://docs.anthropic.com/claude/reference/messages_post
0 commit comments