amazon-bedrock-haystack 6.2.1__tar.gz → 6.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. {amazon_bedrock_haystack-6.2.1 → amazon_bedrock_haystack-6.3.0}/PKG-INFO +2 -2
  2. {amazon_bedrock_haystack-6.2.1 → amazon_bedrock_haystack-6.3.0}/pyproject.toml +1 -1
  3. {amazon_bedrock_haystack-6.2.1 → amazon_bedrock_haystack-6.3.0}/src/haystack_integrations/components/generators/amazon_bedrock/chat/utils.py +38 -18
  4. {amazon_bedrock_haystack-6.2.1 → amazon_bedrock_haystack-6.3.0}/tests/test_chat_generator.py +27 -2
  5. {amazon_bedrock_haystack-6.2.1 → amazon_bedrock_haystack-6.3.0}/tests/test_chat_generator_utils.py +47 -0
  6. {amazon_bedrock_haystack-6.2.1 → amazon_bedrock_haystack-6.3.0}/.gitignore +0 -0
  7. {amazon_bedrock_haystack-6.2.1 → amazon_bedrock_haystack-6.3.0}/CHANGELOG.md +0 -0
  8. {amazon_bedrock_haystack-6.2.1 → amazon_bedrock_haystack-6.3.0}/LICENSE.txt +0 -0
  9. {amazon_bedrock_haystack-6.2.1 → amazon_bedrock_haystack-6.3.0}/README.md +0 -0
  10. {amazon_bedrock_haystack-6.2.1 → amazon_bedrock_haystack-6.3.0}/examples/bedrock_ranker_example.py +0 -0
  11. {amazon_bedrock_haystack-6.2.1 → amazon_bedrock_haystack-6.3.0}/examples/chatgenerator_example.py +0 -0
  12. {amazon_bedrock_haystack-6.2.1 → amazon_bedrock_haystack-6.3.0}/examples/embedders_generator_with_rag_example.py +0 -0
  13. {amazon_bedrock_haystack-6.2.1 → amazon_bedrock_haystack-6.3.0}/examples/s3_downloader_example.py +0 -0
  14. {amazon_bedrock_haystack-6.2.1 → amazon_bedrock_haystack-6.3.0}/pydoc/config_docusaurus.yml +0 -0
  15. {amazon_bedrock_haystack-6.2.1 → amazon_bedrock_haystack-6.3.0}/src/haystack_integrations/common/amazon_bedrock/__init__.py +0 -0
  16. {amazon_bedrock_haystack-6.2.1 → amazon_bedrock_haystack-6.3.0}/src/haystack_integrations/common/amazon_bedrock/errors.py +0 -0
  17. {amazon_bedrock_haystack-6.2.1 → amazon_bedrock_haystack-6.3.0}/src/haystack_integrations/common/amazon_bedrock/utils.py +0 -0
  18. {amazon_bedrock_haystack-6.2.1 → amazon_bedrock_haystack-6.3.0}/src/haystack_integrations/common/py.typed +0 -0
  19. {amazon_bedrock_haystack-6.2.1 → amazon_bedrock_haystack-6.3.0}/src/haystack_integrations/common/s3/__init__.py +0 -0
  20. {amazon_bedrock_haystack-6.2.1 → amazon_bedrock_haystack-6.3.0}/src/haystack_integrations/common/s3/errors.py +0 -0
  21. {amazon_bedrock_haystack-6.2.1 → amazon_bedrock_haystack-6.3.0}/src/haystack_integrations/common/s3/utils.py +0 -0
  22. {amazon_bedrock_haystack-6.2.1 → amazon_bedrock_haystack-6.3.0}/src/haystack_integrations/components/downloaders/py.typed +0 -0
  23. {amazon_bedrock_haystack-6.2.1 → amazon_bedrock_haystack-6.3.0}/src/haystack_integrations/components/downloaders/s3/__init__.py +0 -0
  24. {amazon_bedrock_haystack-6.2.1 → amazon_bedrock_haystack-6.3.0}/src/haystack_integrations/components/downloaders/s3/s3_downloader.py +0 -0
  25. {amazon_bedrock_haystack-6.2.1 → amazon_bedrock_haystack-6.3.0}/src/haystack_integrations/components/embedders/amazon_bedrock/__init__.py +0 -0
  26. {amazon_bedrock_haystack-6.2.1 → amazon_bedrock_haystack-6.3.0}/src/haystack_integrations/components/embedders/amazon_bedrock/document_embedder.py +0 -0
  27. {amazon_bedrock_haystack-6.2.1 → amazon_bedrock_haystack-6.3.0}/src/haystack_integrations/components/embedders/amazon_bedrock/document_image_embedder.py +0 -0
  28. {amazon_bedrock_haystack-6.2.1 → amazon_bedrock_haystack-6.3.0}/src/haystack_integrations/components/embedders/amazon_bedrock/text_embedder.py +0 -0
  29. {amazon_bedrock_haystack-6.2.1 → amazon_bedrock_haystack-6.3.0}/src/haystack_integrations/components/embedders/py.typed +0 -0
  30. {amazon_bedrock_haystack-6.2.1 → amazon_bedrock_haystack-6.3.0}/src/haystack_integrations/components/generators/amazon_bedrock/__init__.py +0 -0
  31. {amazon_bedrock_haystack-6.2.1 → amazon_bedrock_haystack-6.3.0}/src/haystack_integrations/components/generators/amazon_bedrock/adapters.py +0 -0
  32. {amazon_bedrock_haystack-6.2.1 → amazon_bedrock_haystack-6.3.0}/src/haystack_integrations/components/generators/amazon_bedrock/chat/__init__.py +0 -0
  33. {amazon_bedrock_haystack-6.2.1 → amazon_bedrock_haystack-6.3.0}/src/haystack_integrations/components/generators/amazon_bedrock/chat/chat_generator.py +0 -0
  34. {amazon_bedrock_haystack-6.2.1 → amazon_bedrock_haystack-6.3.0}/src/haystack_integrations/components/generators/amazon_bedrock/generator.py +0 -0
  35. {amazon_bedrock_haystack-6.2.1 → amazon_bedrock_haystack-6.3.0}/src/haystack_integrations/components/generators/py.typed +0 -0
  36. {amazon_bedrock_haystack-6.2.1 → amazon_bedrock_haystack-6.3.0}/src/haystack_integrations/components/rankers/amazon_bedrock/__init__.py +0 -0
  37. {amazon_bedrock_haystack-6.2.1 → amazon_bedrock_haystack-6.3.0}/src/haystack_integrations/components/rankers/amazon_bedrock/ranker.py +0 -0
  38. {amazon_bedrock_haystack-6.2.1 → amazon_bedrock_haystack-6.3.0}/src/haystack_integrations/components/rankers/py.typed +0 -0
  39. {amazon_bedrock_haystack-6.2.1 → amazon_bedrock_haystack-6.3.0}/tests/__init__.py +0 -0
  40. {amazon_bedrock_haystack-6.2.1 → amazon_bedrock_haystack-6.3.0}/tests/conftest.py +0 -0
  41. {amazon_bedrock_haystack-6.2.1 → amazon_bedrock_haystack-6.3.0}/tests/test_document_embedder.py +0 -0
  42. {amazon_bedrock_haystack-6.2.1 → amazon_bedrock_haystack-6.3.0}/tests/test_document_image_embedder.py +0 -0
  43. {amazon_bedrock_haystack-6.2.1 → amazon_bedrock_haystack-6.3.0}/tests/test_files/apple.jpg +0 -0
  44. {amazon_bedrock_haystack-6.2.1 → amazon_bedrock_haystack-6.3.0}/tests/test_files/haystack-logo.png +0 -0
  45. {amazon_bedrock_haystack-6.2.1 → amazon_bedrock_haystack-6.3.0}/tests/test_files/sample_pdf_1.pdf +0 -0
  46. {amazon_bedrock_haystack-6.2.1 → amazon_bedrock_haystack-6.3.0}/tests/test_generator.py +0 -0
  47. {amazon_bedrock_haystack-6.2.1 → amazon_bedrock_haystack-6.3.0}/tests/test_ranker.py +0 -0
  48. {amazon_bedrock_haystack-6.2.1 → amazon_bedrock_haystack-6.3.0}/tests/test_s3_downloader.py +0 -0
  49. {amazon_bedrock_haystack-6.2.1 → amazon_bedrock_haystack-6.3.0}/tests/test_text_embedder.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: amazon-bedrock-haystack
3
- Version: 6.2.1
3
+ Version: 6.3.0
4
4
  Summary: An integration of AWS S3 and Bedrock as a Downloader and Generator components.
5
5
  Project-URL: Documentation, https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/amazon_bedrock#readme
6
6
  Project-URL: Issues, https://github.com/deepset-ai/haystack-core-integrations/issues
@@ -20,7 +20,7 @@ Classifier: Programming Language :: Python :: Implementation :: PyPy
20
20
  Requires-Python: >=3.10
21
21
  Requires-Dist: aioboto3>=14.0.0
22
22
  Requires-Dist: boto3>=1.28.57
23
- Requires-Dist: haystack-ai>=2.22.0
23
+ Requires-Dist: haystack-ai>=2.23.0
24
24
  Description-Content-Type: text/markdown
25
25
 
26
26
  # amazon-bedrock-haystack
@@ -22,7 +22,7 @@ classifiers = [
22
22
  "Programming Language :: Python :: Implementation :: CPython",
23
23
  "Programming Language :: Python :: Implementation :: PyPy",
24
24
  ]
25
- dependencies = ["haystack-ai>=2.22.0", "boto3>=1.28.57", "aioboto3>=14.0.0"]
25
+ dependencies = ["haystack-ai>=2.23.0", "boto3>=1.28.57", "aioboto3>=14.0.0"]
26
26
 
27
27
  [project.urls]
28
28
  Documentation = "https://github.com/deepset-ai/haystack-core-integrations/tree/main/integrations/amazon_bedrock#readme"
@@ -60,6 +60,24 @@ def _format_tools(tools: list[Tool] | None = None) -> dict[str, Any] | None:
60
60
  return {"tools": tool_specs} if tool_specs else None
61
61
 
62
62
 
63
+ def _convert_image_content_to_bedrock_format(image_content: ImageContent) -> dict[str, Any]:
64
+ """
65
+ Convert a Haystack ImageContent to Bedrock format.
66
+ """
67
+
68
+ image_format = image_content.mime_type.split("/")[-1] if image_content.mime_type else None
69
+ if image_format not in IMAGE_SUPPORTED_FORMATS:
70
+ err_msg = (
71
+ f"Unsupported image format: {image_format}. "
72
+ f"Bedrock supports the following image formats: {IMAGE_SUPPORTED_FORMATS}"
73
+ )
74
+ raise ValueError(err_msg)
75
+
76
+ source = {"bytes": base64.b64decode(image_content.base64_image)}
77
+
78
+ return {"image": {"format": image_format, "source": source}}
79
+
80
+
63
81
  def _format_tool_call_message(tool_call_message: ChatMessage) -> dict[str, Any]:
64
82
  """
65
83
  Format a Haystack ChatMessage containing tool calls into Bedrock format.
@@ -94,19 +112,30 @@ def _format_tool_result_message(tool_call_result_message: ChatMessage) -> dict[s
94
112
  """
95
113
  # Assuming tool call result messages will only contain tool results
96
114
  tool_results = []
97
- for result in tool_call_result_message.tool_call_results:
98
- try:
99
- json_result = json.loads(result.result)
100
- content = [{"json": json_result}]
101
- except json.JSONDecodeError:
102
- content = [{"text": result.result}]
115
+ for tool_call_result in tool_call_result_message.tool_call_results:
116
+ if isinstance(tool_call_result.result, str):
117
+ try:
118
+ json_result = json.loads(tool_call_result.result)
119
+ content = [{"json": json_result}]
120
+ except json.JSONDecodeError:
121
+ content = [{"text": tool_call_result.result}]
122
+ elif isinstance(tool_call_result.result, list):
123
+ content = []
124
+ for item in tool_call_result.result:
125
+ if isinstance(item, TextContent):
126
+ content.append({"text": item.text})
127
+ elif isinstance(item, ImageContent):
128
+ content.append(_convert_image_content_to_bedrock_format(item))
129
+ else:
130
+ err_msg = "Unsupported content type in tool call result"
131
+ raise ValueError(err_msg)
103
132
 
104
133
  tool_results.append(
105
134
  {
106
135
  "toolResult": {
107
- "toolUseId": result.origin.id,
136
+ "toolUseId": tool_call_result.origin.id,
108
137
  "content": content,
109
- **({"status": "error"} if result.error else {}),
138
+ **({"status": "error"} if tool_call_result.error else {}),
110
139
  }
111
140
  }
112
141
  )
@@ -217,16 +246,7 @@ def _format_text_image_message(message: ChatMessage) -> dict[str, Any]:
217
246
  if message.is_from(ChatRole.ASSISTANT):
218
247
  err_msg = "Image content is not supported for assistant messages"
219
248
  raise ValueError(err_msg)
220
-
221
- image_format = part.mime_type.split("/")[-1] if part.mime_type else None
222
- if image_format not in IMAGE_SUPPORTED_FORMATS:
223
- err_msg = (
224
- f"Unsupported image format: {image_format}. "
225
- f"Bedrock supports the following image formats: {IMAGE_SUPPORTED_FORMATS}"
226
- )
227
- raise ValueError(err_msg)
228
- source = {"bytes": base64.b64decode(part.base64_image)}
229
- bedrock_content_blocks.append({"image": {"format": image_format, "source": source}})
249
+ bedrock_content_blocks.append(_convert_image_content_to_bedrock_format(part))
230
250
 
231
251
  return {"role": message.role.value, "content": bedrock_content_blocks}
232
252
 
@@ -3,10 +3,11 @@ from typing import Any
3
3
 
4
4
  import pytest
5
5
  from haystack import Pipeline
6
+ from haystack.components.agents import Agent
6
7
  from haystack.components.generators.utils import print_streaming_chunk
7
8
  from haystack.components.tools import ToolInvoker
8
- from haystack.dataclasses import ChatMessage, ChatRole, ImageContent, StreamingChunk, ToolCall
9
- from haystack.tools import Tool, Toolset
9
+ from haystack.dataclasses import ChatMessage, ChatRole, ImageContent, StreamingChunk, TextContent, ToolCall
10
+ from haystack.tools import Tool, Toolset, create_tool_from_function
10
11
 
11
12
  from haystack_integrations.components.generators.amazon_bedrock import AmazonBedrockChatGenerator
12
13
 
@@ -514,6 +515,30 @@ class TestAmazonBedrockChatGeneratorInference:
514
515
  assert first_reply.text
515
516
  assert "apple" in first_reply.text.lower()
516
517
 
518
+ @pytest.mark.parametrize("model_name", MODELS_TO_TEST_WITH_IMAGE_INPUT)
519
+ def test_live_run_agent_with_images_in_tool_result(self, model_name, test_files_path):
520
+ def retrieve_image():
521
+ return [
522
+ TextContent("Here is the retrieved image."),
523
+ ImageContent.from_file_path(test_files_path / "apple.jpg", size=(100, 100)),
524
+ ]
525
+
526
+ image_retriever_tool = create_tool_from_function(
527
+ name="retrieve_image", description="Tool to retrieve an image", function=retrieve_image
528
+ )
529
+ image_retriever_tool.outputs_to_string = {"raw_result": True}
530
+
531
+ agent = Agent(
532
+ chat_generator=AmazonBedrockChatGenerator(model=model_name),
533
+ system_prompt="You are an Agent that can retrieve images and describe them.",
534
+ tools=[image_retriever_tool],
535
+ )
536
+
537
+ user_message = ChatMessage.from_user("Retrieve the image and describe it in max 5 words.")
538
+ result = agent.run(messages=[user_message])
539
+
540
+ assert "apple" in result["last_message"].text.lower()
541
+
517
542
  @pytest.mark.parametrize("model_name", MODELS_TO_TEST)
518
543
  def test_default_inference_with_streaming(self, model_name, chat_messages):
519
544
  streaming_callback_called = False
@@ -9,6 +9,7 @@ from haystack.dataclasses import (
9
9
  ImageContent,
10
10
  ReasoningContent,
11
11
  StreamingChunk,
12
+ TextContent,
12
13
  ToolCall,
13
14
  ToolCallDelta,
14
15
  )
@@ -120,6 +121,52 @@ class TestAmazonBedrockChatGeneratorUtils:
120
121
  {"role": "assistant", "content": [{"text": "The weather in Paris is sunny and 25°C."}]},
121
122
  ]
122
123
 
124
+ def test_format_messages_tool_result_with_image(self):
125
+ base64_image = (
126
+ "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg=="
127
+ )
128
+
129
+ messages = [
130
+ ChatMessage.from_user("Retrieve the image and describe it in max 5 words."),
131
+ ChatMessage.from_assistant(
132
+ tool_calls=[ToolCall(id="123", tool_name="image_retriever", arguments={"query": "random query"})]
133
+ ),
134
+ ChatMessage.from_tool(
135
+ tool_result=[
136
+ TextContent("Here's the retrieved image"),
137
+ ImageContent(base64_image=base64_image, mime_type="image/png"),
138
+ ],
139
+ origin=ToolCall(id="123", tool_name="image_retriever", arguments={"query": "random query"}),
140
+ ),
141
+ ChatMessage.from_assistant("Beautiful landscape with mountains"),
142
+ ]
143
+ formatted_system_prompts, formatted_messages = _format_messages(messages)
144
+ assert formatted_system_prompts == []
145
+ assert formatted_messages == [
146
+ {"role": "user", "content": [{"text": "Retrieve the image and describe it in max 5 words."}]},
147
+ {
148
+ "role": "assistant",
149
+ "content": [
150
+ {"toolUse": {"toolUseId": "123", "name": "image_retriever", "input": {"query": "random query"}}}
151
+ ],
152
+ },
153
+ {
154
+ "role": "user",
155
+ "content": [
156
+ {
157
+ "toolResult": {
158
+ "toolUseId": "123",
159
+ "content": [
160
+ {"text": "Here's the retrieved image"},
161
+ {"image": {"format": "png", "source": {"bytes": base64.b64decode(base64_image)}}},
162
+ ],
163
+ }
164
+ }
165
+ ],
166
+ },
167
+ {"role": "assistant", "content": [{"text": "Beautiful landscape with mountains"}]},
168
+ ]
169
+
123
170
  def test_format_message_thinking(self):
124
171
  assistant_message = ChatMessage.from_assistant(
125
172
  "This is a test message.",