amazon-bedrock-haystack 6.2.1__py3-none-any.whl → 6.4.0__py3-none-any.whl

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: amazon-bedrock-haystack
3
- Version: 6.2.1
3
+ Version: 6.4.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
@@ -7,23 +7,23 @@ haystack_integrations/common/s3/errors.py,sha256=BrTDLdhQvAuQutyg35cFyP5h8PNkDEi
7
7
  haystack_integrations/common/s3/utils.py,sha256=lrPTc4r0l9Bm6dACxCDgvcbDPPtuxjU6nzTDFw4g2Fs,4707
8
8
  haystack_integrations/components/downloaders/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
9
  haystack_integrations/components/downloaders/s3/__init__.py,sha256=2BOd3_N0kGqRJGH-ENrTJqOqzqHryRYaSuNqpLYKMFo,179
10
- haystack_integrations/components/downloaders/s3/s3_downloader.py,sha256=xRvQMXEUi7ysOTX3ylC3qw9t0GU2JzGGt7DQEu5Eu8s,12475
10
+ haystack_integrations/components/downloaders/s3/s3_downloader.py,sha256=LQw-rIvC1q-Cc9mZkgn-KzBS9Z6W9S8roiigxCNAmpE,12011
11
11
  haystack_integrations/components/embedders/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
12
  haystack_integrations/components/embedders/amazon_bedrock/__init__.py,sha256=7GlhHJ4jFHCxq5QN5losGuGtrGNjvEx2dSQvEYD2yG0,408
13
- haystack_integrations/components/embedders/amazon_bedrock/document_embedder.py,sha256=s3BxQL_EuSAs9IFHn5qf-4eKelg1lDn6ij7YTvBDVpg,13359
14
- haystack_integrations/components/embedders/amazon_bedrock/document_image_embedder.py,sha256=qBUo4P2Ccw6ltRXHdrB1HuUo8-9GQLDcuO7VDfBORlQ,16175
15
- haystack_integrations/components/embedders/amazon_bedrock/text_embedder.py,sha256=RE4rdnUZOm0PL_uf2HTLmsstA2hdMB0Rpz9Ga3VWlF4,9315
13
+ haystack_integrations/components/embedders/amazon_bedrock/document_embedder.py,sha256=OG3RcsVPDEA3bJAlpotgXoOD3PfrBeTjTF8Q0N5RKSI,12895
14
+ haystack_integrations/components/embedders/amazon_bedrock/document_image_embedder.py,sha256=TSAnekrEFZ5MX5bWSoHZlYulSh-aKFRvZwyyKfbS0G8,15577
15
+ haystack_integrations/components/embedders/amazon_bedrock/text_embedder.py,sha256=Z5-PUtyiE1_7tgDAj6CqxUVI09WwmwJavfGJ-pO9GlA,8851
16
16
  haystack_integrations/components/generators/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
17
  haystack_integrations/components/generators/amazon_bedrock/__init__.py,sha256=lv4NouIVm78YavUssWQrHHP_81u-7j21qW8v1kZMJPQ,284
18
18
  haystack_integrations/components/generators/amazon_bedrock/adapters.py,sha256=4-gIWfw70hGaXMNS30UbJFNFCWK2KG6RMnT_4z5RHxc,19625
19
- haystack_integrations/components/generators/amazon_bedrock/generator.py,sha256=jz9hT3ZMJtUsQnyeCa1sbeEf7e37t3EktQLAT8J2qpM,14901
19
+ haystack_integrations/components/generators/amazon_bedrock/generator.py,sha256=p52Fq63lzp6UkAygBfiZqFigOT-XOrIZX1F7BiHoAAI,14437
20
20
  haystack_integrations/components/generators/amazon_bedrock/chat/__init__.py,sha256=6GZ8Y3Lw0rLOsOAqi6Tu5mZC977UzQvgDxKpOWr8IQw,110
21
- haystack_integrations/components/generators/amazon_bedrock/chat/chat_generator.py,sha256=C2hB0Wg_oxSnvPAIrTB4eHqjn8a732HfhbhYmS9eGjc,27731
22
- haystack_integrations/components/generators/amazon_bedrock/chat/utils.py,sha256=juOaTi8DuXr-i68zzjCnYoc5eNxO1DxcTI9fcNfg4rE,27214
21
+ haystack_integrations/components/generators/amazon_bedrock/chat/chat_generator.py,sha256=F6GTX021cHar3WoaTp2gY9Wqx5JSdv_1rFmP5iX9szM,28437
22
+ haystack_integrations/components/generators/amazon_bedrock/chat/utils.py,sha256=2tbyU0I1tQ6FjpqegVlr97c3zzkvOBSS1-Cx5dKJ-2w,30334
23
23
  haystack_integrations/components/rankers/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
24
  haystack_integrations/components/rankers/amazon_bedrock/__init__.py,sha256=mJQKShAP5AfZvfKQisSh7kfKu6RIXzsYdk4eqMtcaEk,75
25
- haystack_integrations/components/rankers/amazon_bedrock/ranker.py,sha256=L-1AzmsWR8PYb8CEJsfoYK9MBrE2qXFjenfiiGxa6iw,11769
26
- amazon_bedrock_haystack-6.2.1.dist-info/METADATA,sha256=Fr8kZsEebhQpC-g0uj6-h6cUMv0ZIi5dwTYq2HZSt7w,2179
27
- amazon_bedrock_haystack-6.2.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
28
- amazon_bedrock_haystack-6.2.1.dist-info/licenses/LICENSE.txt,sha256=B05uMshqTA74s-0ltyHKI6yoPfJ3zYgQbvcXfDVGFf8,10280
29
- amazon_bedrock_haystack-6.2.1.dist-info/RECORD,,
25
+ haystack_integrations/components/rankers/amazon_bedrock/ranker.py,sha256=CozmHnmLX2bfAXNyCS29ofIzCs-oIF436opsUdipBXI,11305
26
+ amazon_bedrock_haystack-6.4.0.dist-info/METADATA,sha256=UXrT6hxyNx-0UUAprInGPIVAMgDWLDMyiNQcA8ADn0o,2179
27
+ amazon_bedrock_haystack-6.4.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
28
+ amazon_bedrock_haystack-6.4.0.dist-info/licenses/LICENSE.txt,sha256=B05uMshqTA74s-0ltyHKI6yoPfJ3zYgQbvcXfDVGFf8,10280
29
+ amazon_bedrock_haystack-6.4.0.dist-info/RECORD,,
@@ -11,7 +11,7 @@ from typing import Any
11
11
  from botocore.config import Config
12
12
  from haystack import component, default_from_dict, default_to_dict, logging
13
13
  from haystack.dataclasses import Document
14
- from haystack.utils.auth import Secret, deserialize_secrets_inplace
14
+ from haystack.utils.auth import Secret
15
15
  from haystack.utils.callable_serialization import deserialize_callable, serialize_callable
16
16
 
17
17
  from haystack_integrations.common.amazon_bedrock.utils import get_aws_session
@@ -233,11 +233,11 @@ class S3Downloader:
233
233
 
234
234
  return default_to_dict(
235
235
  self,
236
- aws_access_key_id=self.aws_access_key_id.to_dict() if self.aws_access_key_id else None,
237
- aws_secret_access_key=self.aws_secret_access_key.to_dict() if self.aws_secret_access_key else None,
238
- aws_session_token=self.aws_session_token.to_dict() if self.aws_session_token else None,
239
- aws_region_name=self.aws_region_name.to_dict() if self.aws_region_name else None,
240
- aws_profile_name=self.aws_profile_name.to_dict() if self.aws_profile_name else None,
236
+ aws_access_key_id=self.aws_access_key_id,
237
+ aws_secret_access_key=self.aws_secret_access_key,
238
+ aws_session_token=self.aws_session_token,
239
+ aws_region_name=self.aws_region_name,
240
+ aws_profile_name=self.aws_profile_name,
241
241
  file_root_path=str(self.file_root_path),
242
242
  max_workers=self.max_workers,
243
243
  max_cache_size=self.max_cache_size,
@@ -260,8 +260,4 @@ class S3Downloader:
260
260
  data["init_parameters"]["s3_key_generation_function"] = deserialize_callable(
261
261
  s3_key_generation_function_name
262
262
  )
263
- deserialize_secrets_inplace(
264
- data["init_parameters"],
265
- ["aws_access_key_id", "aws_secret_access_key", "aws_session_token", "aws_region_name", "aws_profile_name"],
266
- )
267
263
  return default_from_dict(cls, data)
@@ -6,7 +6,7 @@ from botocore.config import Config
6
6
  from botocore.exceptions import ClientError
7
7
  from haystack import component, default_from_dict, default_to_dict, logging
8
8
  from haystack.dataclasses import Document
9
- from haystack.utils.auth import Secret, deserialize_secrets_inplace
9
+ from haystack.utils.auth import Secret
10
10
  from tqdm import tqdm
11
11
 
12
12
  from haystack_integrations.common.amazon_bedrock.errors import (
@@ -257,11 +257,11 @@ class AmazonBedrockDocumentEmbedder:
257
257
  """
258
258
  return default_to_dict(
259
259
  self,
260
- aws_access_key_id=self.aws_access_key_id.to_dict() if self.aws_access_key_id else None,
261
- aws_secret_access_key=self.aws_secret_access_key.to_dict() if self.aws_secret_access_key else None,
262
- aws_session_token=self.aws_session_token.to_dict() if self.aws_session_token else None,
263
- aws_region_name=self.aws_region_name.to_dict() if self.aws_region_name else None,
264
- aws_profile_name=self.aws_profile_name.to_dict() if self.aws_profile_name else None,
260
+ aws_access_key_id=self.aws_access_key_id,
261
+ aws_secret_access_key=self.aws_secret_access_key,
262
+ aws_session_token=self.aws_session_token,
263
+ aws_region_name=self.aws_region_name,
264
+ aws_profile_name=self.aws_profile_name,
265
265
  model=self.model,
266
266
  batch_size=self.batch_size,
267
267
  progress_bar=self.progress_bar,
@@ -281,8 +281,4 @@ class AmazonBedrockDocumentEmbedder:
281
281
  :returns:
282
282
  Deserialized component.
283
283
  """
284
- deserialize_secrets_inplace(
285
- data["init_parameters"],
286
- ["aws_access_key_id", "aws_secret_access_key", "aws_session_token", "aws_region_name", "aws_profile_name"],
287
- )
288
284
  return default_from_dict(cls, data)
@@ -16,7 +16,7 @@ from haystack.components.converters.image.image_utils import (
16
16
  _PDFPageInfo,
17
17
  )
18
18
  from haystack.dataclasses import ByteStream
19
- from haystack.utils.auth import Secret, deserialize_secrets_inplace
19
+ from haystack.utils.auth import Secret
20
20
  from tqdm import tqdm
21
21
 
22
22
  from haystack_integrations.common.amazon_bedrock.errors import (
@@ -178,11 +178,11 @@ class AmazonBedrockDocumentImageEmbedder:
178
178
  file_path_meta_field=self.file_path_meta_field,
179
179
  root_path=self.root_path,
180
180
  model=self.model,
181
- aws_access_key_id=self.aws_access_key_id.to_dict() if self.aws_access_key_id else None,
182
- aws_secret_access_key=self.aws_secret_access_key.to_dict() if self.aws_secret_access_key else None,
183
- aws_session_token=self.aws_session_token.to_dict() if self.aws_session_token else None,
184
- aws_region_name=self.aws_region_name.to_dict() if self.aws_region_name else None,
185
- aws_profile_name=self.aws_profile_name.to_dict() if self.aws_profile_name else None,
181
+ aws_access_key_id=self.aws_access_key_id,
182
+ aws_secret_access_key=self.aws_secret_access_key,
183
+ aws_session_token=self.aws_session_token,
184
+ aws_region_name=self.aws_region_name,
185
+ aws_profile_name=self.aws_profile_name,
186
186
  progress_bar=self.progress_bar,
187
187
  boto3_config=self.boto3_config,
188
188
  image_size=self.image_size,
@@ -200,17 +200,6 @@ class AmazonBedrockDocumentImageEmbedder:
200
200
  :returns:
201
201
  Deserialized component.
202
202
  """
203
- init_params = data["init_parameters"]
204
- deserialize_secrets_inplace(
205
- init_params,
206
- keys=[
207
- "aws_access_key_id",
208
- "aws_secret_access_key",
209
- "aws_session_token",
210
- "aws_region_name",
211
- "aws_profile_name",
212
- ],
213
- )
214
203
  return default_from_dict(cls, data)
215
204
 
216
205
  @component.output_types(documents=list[Document])
@@ -4,7 +4,7 @@ from typing import Any
4
4
  from botocore.config import Config
5
5
  from botocore.exceptions import ClientError
6
6
  from haystack import component, default_from_dict, default_to_dict, logging
7
- from haystack.utils.auth import Secret, deserialize_secrets_inplace
7
+ from haystack.utils.auth import Secret
8
8
 
9
9
  from haystack_integrations.common.amazon_bedrock.errors import (
10
10
  AmazonBedrockConfigurationError,
@@ -180,11 +180,11 @@ class AmazonBedrockTextEmbedder:
180
180
  """
181
181
  return default_to_dict(
182
182
  self,
183
- aws_access_key_id=self.aws_access_key_id.to_dict() if self.aws_access_key_id else None,
184
- aws_secret_access_key=self.aws_secret_access_key.to_dict() if self.aws_secret_access_key else None,
185
- aws_session_token=self.aws_session_token.to_dict() if self.aws_session_token else None,
186
- aws_region_name=self.aws_region_name.to_dict() if self.aws_region_name else None,
187
- aws_profile_name=self.aws_profile_name.to_dict() if self.aws_profile_name else None,
183
+ aws_access_key_id=self.aws_access_key_id,
184
+ aws_secret_access_key=self.aws_secret_access_key,
185
+ aws_session_token=self.aws_session_token,
186
+ aws_region_name=self.aws_region_name,
187
+ aws_profile_name=self.aws_profile_name,
188
188
  model=self.model,
189
189
  boto3_config=self.boto3_config,
190
190
  **self.kwargs,
@@ -200,8 +200,4 @@ class AmazonBedrockTextEmbedder:
200
200
  :returns:
201
201
  Deserialized component.
202
202
  """
203
- deserialize_secrets_inplace(
204
- data["init_parameters"],
205
- ["aws_access_key_id", "aws_secret_access_key", "aws_session_token", "aws_region_name", "aws_profile_name"],
206
- )
207
203
  return default_from_dict(cls, data)
@@ -13,7 +13,7 @@ from haystack.tools import (
13
13
  flatten_tools_or_toolsets,
14
14
  serialize_tools_or_toolset,
15
15
  )
16
- from haystack.utils.auth import Secret, deserialize_secrets_inplace
16
+ from haystack.utils.auth import Secret
17
17
  from haystack.utils.callable_serialization import deserialize_callable, serialize_callable
18
18
 
19
19
  from haystack_integrations.common.amazon_bedrock.errors import (
@@ -27,6 +27,7 @@ from haystack_integrations.components.generators.amazon_bedrock.chat.utils impor
27
27
  _parse_completion_response,
28
28
  _parse_streaming_response,
29
29
  _parse_streaming_response_async,
30
+ _validate_and_format_cache_point,
30
31
  _validate_guardrail_config,
31
32
  )
32
33
 
@@ -142,6 +143,12 @@ class AmazonBedrockChatGenerator:
142
143
  and `aws_region_name` as environment variables or pass them as
143
144
  [Secret](https://docs.haystack.deepset.ai/docs/secret-management) arguments. Make sure the region you set
144
145
  supports Amazon Bedrock.
146
+
147
+ This component supports prompt caching. You can use the `tools_cachepoint_config` parameter to configure the cache
148
+ point for tools.
149
+ To cache messages, you can use the `cachePoint` key in `ChatMessage.meta` attribute.
150
+ Example: `ChatMessage.from_user("Long message...", meta={"cachePoint": {"type": "default"}})`
151
+ For more information, see the [Amazon Bedrock documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/prompt-caching.html).
145
152
  """
146
153
 
147
154
  def __init__(
@@ -160,6 +167,7 @@ class AmazonBedrockChatGenerator:
160
167
  tools: ToolsType | None = None,
161
168
  *,
162
169
  guardrail_config: dict[str, str] | None = None,
170
+ tools_cachepoint_config: dict[str, str] | None = None,
163
171
  ) -> None:
164
172
  """
165
173
  Initializes the `AmazonBedrockChatGenerator` with the provided parameters. The parameters are passed to the
@@ -201,6 +209,10 @@ class AmazonBedrockChatGenerator:
201
209
  See the
202
210
  [Guardrails Streaming documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/guardrails-streaming.html)
203
211
  for more information.
212
+ :param tools_cachepoint_config: Optional configuration to use prompt caching for tools.
213
+ The dictionary must match the
214
+ [CachePointBlock schema](https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_CachePointBlock.html).
215
+ Example: `{"type": "default", "ttl": "5m"}`
204
216
 
205
217
 
206
218
  :raises ValueError: If the model name is empty or None.
@@ -225,6 +237,10 @@ class AmazonBedrockChatGenerator:
225
237
  _validate_guardrail_config(guardrail_config=guardrail_config, streaming=streaming_callback is not None)
226
238
  self.guardrail_config = guardrail_config
227
239
 
240
+ self.tools_cachepoint_config = (
241
+ _validate_and_format_cache_point(tools_cachepoint_config) if tools_cachepoint_config else None
242
+ )
243
+
228
244
  def resolve_secret(secret: Secret | None) -> str | None:
229
245
  return secret.resolve_value() if secret else None
230
246
 
@@ -299,17 +315,18 @@ class AmazonBedrockChatGenerator:
299
315
  callback_name = serialize_callable(self.streaming_callback) if self.streaming_callback else None
300
316
  return default_to_dict(
301
317
  self,
302
- aws_access_key_id=self.aws_access_key_id.to_dict() if self.aws_access_key_id else None,
303
- aws_secret_access_key=self.aws_secret_access_key.to_dict() if self.aws_secret_access_key else None,
304
- aws_session_token=self.aws_session_token.to_dict() if self.aws_session_token else None,
305
- aws_region_name=self.aws_region_name.to_dict() if self.aws_region_name else None,
306
- aws_profile_name=self.aws_profile_name.to_dict() if self.aws_profile_name else None,
318
+ aws_access_key_id=self.aws_access_key_id,
319
+ aws_secret_access_key=self.aws_secret_access_key,
320
+ aws_session_token=self.aws_session_token,
321
+ aws_region_name=self.aws_region_name,
322
+ aws_profile_name=self.aws_profile_name,
307
323
  model=self.model,
308
324
  generation_kwargs=self.generation_kwargs,
309
325
  streaming_callback=callback_name,
310
326
  boto3_config=self.boto3_config,
311
327
  tools=serialize_tools_or_toolset(self.tools),
312
328
  guardrail_config=self.guardrail_config,
329
+ tools_cachepoint_config=self.tools_cachepoint_config,
313
330
  )
314
331
 
315
332
  @classmethod
@@ -331,10 +348,6 @@ class AmazonBedrockChatGenerator:
331
348
  serialized_callback_handler = init_params.get("streaming_callback")
332
349
  if serialized_callback_handler:
333
350
  data["init_parameters"]["streaming_callback"] = deserialize_callable(serialized_callback_handler)
334
- deserialize_secrets_inplace(
335
- data["init_parameters"],
336
- ["aws_access_key_id", "aws_secret_access_key", "aws_session_token", "aws_region_name", "aws_profile_name"],
337
- )
338
351
  deserialize_tools_or_toolset_inplace(data["init_parameters"], key="tools")
339
352
  return default_from_dict(cls, data)
340
353
 
@@ -389,7 +402,7 @@ class AmazonBedrockChatGenerator:
389
402
  tool_config = merged_kwargs.pop("toolConfig", None)
390
403
  if flattened_tools:
391
404
  # Format Haystack tools to Bedrock format
392
- tool_config = _format_tools(flattened_tools)
405
+ tool_config = _format_tools(flattened_tools, tools_cachepoint_config=self.tools_cachepoint_config)
393
406
 
394
407
  # Any remaining kwargs go to additionalModelRequestFields
395
408
  additional_fields = merged_kwargs if merged_kwargs else None
@@ -40,7 +40,9 @@ FINISH_REASON_MAPPING: dict[str, FinishReason] = {
40
40
 
41
41
 
42
42
  # Haystack to Bedrock util methods
43
- def _format_tools(tools: list[Tool] | None = None) -> dict[str, Any] | None:
43
+ def _format_tools(
44
+ tools: list[Tool] | None = None, tools_cachepoint_config: dict[str, dict[str, str]] | None = None
45
+ ) -> dict[str, Any] | None:
44
46
  """
45
47
  Format Haystack Tool(s) to Amazon Bedrock toolConfig format.
46
48
 
@@ -51,13 +53,34 @@ def _format_tools(tools: list[Tool] | None = None) -> dict[str, Any] | None:
51
53
  if not tools:
52
54
  return None
53
55
 
54
- tool_specs = []
56
+ tool_specs: list[dict[str, Any]] = []
55
57
  for tool in tools:
56
58
  tool_specs.append(
57
59
  {"toolSpec": {"name": tool.name, "description": tool.description, "inputSchema": {"json": tool.parameters}}}
58
60
  )
59
61
 
60
- return {"tools": tool_specs} if tool_specs else None
62
+ if tools_cachepoint_config:
63
+ tool_specs.append({"cachePoint": tools_cachepoint_config})
64
+
65
+ return {"tools": tool_specs}
66
+
67
+
68
+ def _convert_image_content_to_bedrock_format(image_content: ImageContent) -> dict[str, Any]:
69
+ """
70
+ Convert a Haystack ImageContent to Bedrock format.
71
+ """
72
+
73
+ image_format = image_content.mime_type.split("/")[-1] if image_content.mime_type else None
74
+ if image_format not in IMAGE_SUPPORTED_FORMATS:
75
+ err_msg = (
76
+ f"Unsupported image format: {image_format}. "
77
+ f"Bedrock supports the following image formats: {IMAGE_SUPPORTED_FORMATS}"
78
+ )
79
+ raise ValueError(err_msg)
80
+
81
+ source = {"bytes": base64.b64decode(image_content.base64_image)}
82
+
83
+ return {"image": {"format": image_format, "source": source}}
61
84
 
62
85
 
63
86
  def _format_tool_call_message(tool_call_message: ChatMessage) -> dict[str, Any]:
@@ -94,19 +117,30 @@ def _format_tool_result_message(tool_call_result_message: ChatMessage) -> dict[s
94
117
  """
95
118
  # Assuming tool call result messages will only contain tool results
96
119
  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}]
120
+ for tool_call_result in tool_call_result_message.tool_call_results:
121
+ if isinstance(tool_call_result.result, str):
122
+ try:
123
+ json_result = json.loads(tool_call_result.result)
124
+ content = [{"json": json_result}]
125
+ except json.JSONDecodeError:
126
+ content = [{"text": tool_call_result.result}]
127
+ elif isinstance(tool_call_result.result, list):
128
+ content = []
129
+ for item in tool_call_result.result:
130
+ if isinstance(item, TextContent):
131
+ content.append({"text": item.text})
132
+ elif isinstance(item, ImageContent):
133
+ content.append(_convert_image_content_to_bedrock_format(item))
134
+ else:
135
+ err_msg = "Unsupported content type in tool call result"
136
+ raise ValueError(err_msg)
103
137
 
104
138
  tool_results.append(
105
139
  {
106
140
  "toolResult": {
107
- "toolUseId": result.origin.id,
141
+ "toolUseId": tool_call_result.origin.id,
108
142
  "content": content,
109
- **({"status": "error"} if result.error else {}),
143
+ **({"status": "error"} if tool_call_result.error else {}),
110
144
  }
111
145
  }
112
146
  )
@@ -152,20 +186,23 @@ def _repair_tool_result_messages(bedrock_formatted_messages: list[dict[str, Any]
152
186
  original_idx = None
153
187
  for tool_call_id in tool_call_ids:
154
188
  for idx, tool_result in tool_result_messages:
155
- tool_result_contents = [c for c in tool_result["content"] if "toolResult" in c]
189
+ tool_result_contents = [c for c in tool_result["content"] if "toolResult" in c or "cachePoint" in c]
156
190
  for content in tool_result_contents:
157
- if content["toolResult"]["toolUseId"] == tool_call_id:
191
+ if "toolResult" in content and content["toolResult"]["toolUseId"] == tool_call_id:
158
192
  regrouped_tool_result.append(content)
159
193
  # Keep track of the original index of the last tool result message
160
194
  original_idx = idx
195
+ elif "cachePoint" in content and content not in regrouped_tool_result:
196
+ regrouped_tool_result.append(content)
197
+
161
198
  if regrouped_tool_result and original_idx is not None:
162
199
  repaired_tool_result_prompts.append((original_idx, {"role": "user", "content": regrouped_tool_result}))
163
200
 
164
201
  # Remove the tool result messages from bedrock_formatted_messages
165
202
  bedrock_formatted_messages_minus_tool_results: list[tuple[int, Any]] = []
166
203
  for idx, msg in enumerate(bedrock_formatted_messages):
167
- # Assumes the content of tool result messages only contains 'toolResult': {...} objects (e.g. no 'text')
168
- if msg.get("content") and "toolResult" not in msg["content"][0]:
204
+ # Filter out messages that contain toolResult (they are handled by repaired_tool_result_prompts)
205
+ if msg.get("content") and not any("toolResult" in c for c in msg["content"]):
169
206
  bedrock_formatted_messages_minus_tool_results.append((idx, msg))
170
207
 
171
208
  # Add the repaired tool result messages and sort to maintain the correct order
@@ -217,20 +254,37 @@ def _format_text_image_message(message: ChatMessage) -> dict[str, Any]:
217
254
  if message.is_from(ChatRole.ASSISTANT):
218
255
  err_msg = "Image content is not supported for assistant messages"
219
256
  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}})
257
+ bedrock_content_blocks.append(_convert_image_content_to_bedrock_format(part))
230
258
 
231
259
  return {"role": message.role.value, "content": bedrock_content_blocks}
232
260
 
233
261
 
262
+ def _validate_and_format_cache_point(cache_point: dict[str, str] | None) -> dict[str, dict[str, str]] | None:
263
+ """
264
+ Validate and format a cache point dictionary.
265
+
266
+ Schema available at https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_CachePointBlock.html
267
+
268
+ :param cache_point: Cache point dictionary to validate and format.
269
+ :returns: Dictionary in Bedrock cachePoint format or None if no cache point is provided.
270
+ :raises ValueError: If cache point is not valid.
271
+ """
272
+ if not cache_point:
273
+ return None
274
+
275
+ if "type" not in cache_point or cache_point["type"] != "default":
276
+ err_msg = "Cache point must have a 'type' key with value 'default'."
277
+ raise ValueError(err_msg)
278
+ if not set(cache_point).issubset({"type", "ttl"}):
279
+ err_msg = "Cache point can only contain 'type' and 'ttl' keys."
280
+ raise ValueError(err_msg)
281
+ if "ttl" in cache_point and cache_point["ttl"] not in ("5m", "1h"):
282
+ err_msg = "Cache point 'ttl' must be one of '5m', '1h'."
283
+ raise ValueError(err_msg)
284
+
285
+ return {"cachePoint": cache_point}
286
+
287
+
234
288
  def _format_messages(messages: list[ChatMessage]) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]:
235
289
  """
236
290
  Format a list of Haystack ChatMessages to the format expected by Bedrock API.
@@ -244,21 +298,30 @@ def _format_messages(messages: list[ChatMessage]) -> tuple[list[dict[str, Any]],
244
298
  non_system_messages is a list of properly formatted message dictionaries.
245
299
  """
246
300
  # Separate system messages, tool calls, and tool results
247
- system_prompts = []
301
+ system_prompts: list[dict[str, Any]] = []
248
302
  bedrock_formatted_messages = []
249
303
  for msg in messages:
304
+ cache_point = _validate_and_format_cache_point(msg.meta.get("cachePoint"))
250
305
  if msg.is_from(ChatRole.SYSTEM):
251
306
  # Assuming system messages can only contain text
252
307
  # Don't need to track idx since system_messages are handled separately
253
308
  system_prompts.append({"text": msg.text})
254
- elif msg.tool_calls:
255
- bedrock_formatted_messages.append(_format_tool_call_message(msg))
309
+ if cache_point:
310
+ system_prompts.append(cache_point)
311
+ continue
312
+
313
+ if msg.tool_calls:
314
+ formatted_msg = _format_tool_call_message(msg)
256
315
  elif msg.tool_call_results:
257
- bedrock_formatted_messages.append(_format_tool_result_message(msg))
316
+ formatted_msg = _format_tool_result_message(msg)
258
317
  else:
259
- bedrock_formatted_messages.append(_format_text_image_message(msg))
318
+ formatted_msg = _format_text_image_message(msg)
319
+ if cache_point:
320
+ formatted_msg["content"].append(cache_point)
321
+ bedrock_formatted_messages.append(formatted_msg)
260
322
 
261
323
  repaired_bedrock_formatted_messages = _repair_tool_result_messages(bedrock_formatted_messages)
324
+
262
325
  return system_prompts, repaired_bedrock_formatted_messages
263
326
 
264
327
 
@@ -290,6 +353,9 @@ def _parse_completion_response(response_body: dict[str, Any], model: str) -> lis
290
353
  "prompt_tokens": response_body.get("usage", {}).get("inputTokens", 0),
291
354
  "completion_tokens": response_body.get("usage", {}).get("outputTokens", 0),
292
355
  "total_tokens": response_body.get("usage", {}).get("totalTokens", 0),
356
+ "cache_read_input_tokens": response_body.get("usage", {}).get("cacheReadInputTokens", 0),
357
+ "cache_write_input_tokens": response_body.get("usage", {}).get("cacheWriteInputTokens", 0),
358
+ "cache_details": response_body.get("usage", {}).get("CacheDetails", {}),
293
359
  },
294
360
  }
295
361
  # guardrail trace
@@ -441,6 +507,9 @@ def _convert_event_to_streaming_chunk(
441
507
  "prompt_tokens": usage.get("inputTokens", 0),
442
508
  "completion_tokens": usage.get("outputTokens", 0),
443
509
  "total_tokens": usage.get("totalTokens", 0),
510
+ "cache_read_input_tokens": usage.get("cacheReadInputTokens", 0),
511
+ "cache_write_input_tokens": usage.get("cacheWriteInputTokens", 0),
512
+ "cache_details": usage.get("cacheDetails", {}),
444
513
  }
445
514
  if "trace" in event_meta:
446
515
  chunk_meta["trace"] = event_meta["trace"]
@@ -8,7 +8,7 @@ from botocore.config import Config
8
8
  from botocore.exceptions import ClientError
9
9
  from haystack import component, default_from_dict, default_to_dict, logging
10
10
  from haystack.dataclasses import StreamingChunk
11
- from haystack.utils import Secret, deserialize_callable, deserialize_secrets_inplace, serialize_callable
11
+ from haystack.utils import Secret, deserialize_callable, serialize_callable
12
12
 
13
13
  from haystack_integrations.common.amazon_bedrock.errors import (
14
14
  AmazonBedrockConfigurationError,
@@ -287,11 +287,11 @@ class AmazonBedrockGenerator:
287
287
  callback_name = serialize_callable(self.streaming_callback) if self.streaming_callback else None
288
288
  return default_to_dict(
289
289
  self,
290
- aws_access_key_id=self.aws_access_key_id.to_dict() if self.aws_access_key_id else None,
291
- aws_secret_access_key=self.aws_secret_access_key.to_dict() if self.aws_secret_access_key else None,
292
- aws_session_token=self.aws_session_token.to_dict() if self.aws_session_token else None,
293
- aws_region_name=self.aws_region_name.to_dict() if self.aws_region_name else None,
294
- aws_profile_name=self.aws_profile_name.to_dict() if self.aws_profile_name else None,
290
+ aws_access_key_id=self.aws_access_key_id,
291
+ aws_secret_access_key=self.aws_secret_access_key,
292
+ aws_session_token=self.aws_session_token,
293
+ aws_region_name=self.aws_region_name,
294
+ aws_profile_name=self.aws_profile_name,
295
295
  model=self.model,
296
296
  max_length=self.max_length,
297
297
  streaming_callback=callback_name,
@@ -310,10 +310,6 @@ class AmazonBedrockGenerator:
310
310
  :returns:
311
311
  Deserialized component.
312
312
  """
313
- deserialize_secrets_inplace(
314
- data["init_parameters"],
315
- ["aws_access_key_id", "aws_secret_access_key", "aws_session_token", "aws_region_name", "aws_profile_name"],
316
- )
317
313
  init_params = data.get("init_parameters", {})
318
314
  serialized_callback_handler = init_params.get("streaming_callback")
319
315
  if serialized_callback_handler:
@@ -3,7 +3,7 @@ from typing import Any
3
3
 
4
4
  from botocore.exceptions import ClientError
5
5
  from haystack import Document, component, default_from_dict, default_to_dict, logging
6
- from haystack.utils import Secret, deserialize_secrets_inplace
6
+ from haystack.utils import Secret
7
7
 
8
8
  from haystack_integrations.common.amazon_bedrock.errors import (
9
9
  AmazonBedrockConfigurationError,
@@ -133,11 +133,11 @@ class AmazonBedrockRanker:
133
133
  return default_to_dict(
134
134
  self,
135
135
  model=self.model_name,
136
- aws_access_key_id=self.aws_access_key_id.to_dict() if self.aws_access_key_id else None,
137
- aws_secret_access_key=self.aws_secret_access_key.to_dict() if self.aws_secret_access_key else None,
138
- aws_session_token=self.aws_session_token.to_dict() if self.aws_session_token else None,
139
- aws_region_name=self.aws_region_name.to_dict() if self.aws_region_name else None,
140
- aws_profile_name=self.aws_profile_name.to_dict() if self.aws_profile_name else None,
136
+ aws_access_key_id=self.aws_access_key_id,
137
+ aws_secret_access_key=self.aws_secret_access_key,
138
+ aws_session_token=self.aws_session_token,
139
+ aws_region_name=self.aws_region_name,
140
+ aws_profile_name=self.aws_profile_name,
141
141
  top_k=self.top_k,
142
142
  max_chunks_per_doc=self.max_chunks_per_doc,
143
143
  meta_fields_to_embed=self.meta_fields_to_embed,
@@ -154,10 +154,6 @@ class AmazonBedrockRanker:
154
154
  :returns:
155
155
  The deserialized component.
156
156
  """
157
- deserialize_secrets_inplace(
158
- data["init_parameters"],
159
- ["aws_access_key_id", "aws_secret_access_key", "aws_session_token", "aws_region_name", "aws_profile_name"],
160
- )
161
157
  return default_from_dict(cls, data)
162
158
 
163
159
  def _prepare_bedrock_input_docs(self, documents: list[Document]) -> list[str]: