amazon-bedrock-haystack 6.3.0__tar.gz → 6.4.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.3.0 → amazon_bedrock_haystack-6.4.0}/CHANGELOG.md +13 -0
  2. {amazon_bedrock_haystack-6.3.0 → amazon_bedrock_haystack-6.4.0}/PKG-INFO +1 -1
  3. {amazon_bedrock_haystack-6.3.0 → amazon_bedrock_haystack-6.4.0}/src/haystack_integrations/components/downloaders/s3/s3_downloader.py +6 -10
  4. {amazon_bedrock_haystack-6.3.0 → amazon_bedrock_haystack-6.4.0}/src/haystack_integrations/components/embedders/amazon_bedrock/document_embedder.py +6 -10
  5. {amazon_bedrock_haystack-6.3.0 → amazon_bedrock_haystack-6.4.0}/src/haystack_integrations/components/embedders/amazon_bedrock/document_image_embedder.py +6 -17
  6. {amazon_bedrock_haystack-6.3.0 → amazon_bedrock_haystack-6.4.0}/src/haystack_integrations/components/embedders/amazon_bedrock/text_embedder.py +6 -10
  7. {amazon_bedrock_haystack-6.3.0 → amazon_bedrock_haystack-6.4.0}/src/haystack_integrations/components/generators/amazon_bedrock/chat/chat_generator.py +24 -11
  8. {amazon_bedrock_haystack-6.3.0 → amazon_bedrock_haystack-6.4.0}/src/haystack_integrations/components/generators/amazon_bedrock/chat/utils.py +61 -12
  9. {amazon_bedrock_haystack-6.3.0 → amazon_bedrock_haystack-6.4.0}/src/haystack_integrations/components/generators/amazon_bedrock/generator.py +6 -10
  10. {amazon_bedrock_haystack-6.3.0 → amazon_bedrock_haystack-6.4.0}/src/haystack_integrations/components/rankers/amazon_bedrock/ranker.py +6 -10
  11. {amazon_bedrock_haystack-6.3.0 → amazon_bedrock_haystack-6.4.0}/tests/test_chat_generator.py +28 -0
  12. {amazon_bedrock_haystack-6.3.0 → amazon_bedrock_haystack-6.4.0}/tests/test_chat_generator_utils.py +177 -15
  13. {amazon_bedrock_haystack-6.3.0 → amazon_bedrock_haystack-6.4.0}/.gitignore +0 -0
  14. {amazon_bedrock_haystack-6.3.0 → amazon_bedrock_haystack-6.4.0}/LICENSE.txt +0 -0
  15. {amazon_bedrock_haystack-6.3.0 → amazon_bedrock_haystack-6.4.0}/README.md +0 -0
  16. {amazon_bedrock_haystack-6.3.0 → amazon_bedrock_haystack-6.4.0}/examples/bedrock_ranker_example.py +0 -0
  17. {amazon_bedrock_haystack-6.3.0 → amazon_bedrock_haystack-6.4.0}/examples/chatgenerator_example.py +0 -0
  18. {amazon_bedrock_haystack-6.3.0 → amazon_bedrock_haystack-6.4.0}/examples/embedders_generator_with_rag_example.py +0 -0
  19. {amazon_bedrock_haystack-6.3.0 → amazon_bedrock_haystack-6.4.0}/examples/s3_downloader_example.py +0 -0
  20. {amazon_bedrock_haystack-6.3.0 → amazon_bedrock_haystack-6.4.0}/pydoc/config_docusaurus.yml +0 -0
  21. {amazon_bedrock_haystack-6.3.0 → amazon_bedrock_haystack-6.4.0}/pyproject.toml +0 -0
  22. {amazon_bedrock_haystack-6.3.0 → amazon_bedrock_haystack-6.4.0}/src/haystack_integrations/common/amazon_bedrock/__init__.py +0 -0
  23. {amazon_bedrock_haystack-6.3.0 → amazon_bedrock_haystack-6.4.0}/src/haystack_integrations/common/amazon_bedrock/errors.py +0 -0
  24. {amazon_bedrock_haystack-6.3.0 → amazon_bedrock_haystack-6.4.0}/src/haystack_integrations/common/amazon_bedrock/utils.py +0 -0
  25. {amazon_bedrock_haystack-6.3.0 → amazon_bedrock_haystack-6.4.0}/src/haystack_integrations/common/py.typed +0 -0
  26. {amazon_bedrock_haystack-6.3.0 → amazon_bedrock_haystack-6.4.0}/src/haystack_integrations/common/s3/__init__.py +0 -0
  27. {amazon_bedrock_haystack-6.3.0 → amazon_bedrock_haystack-6.4.0}/src/haystack_integrations/common/s3/errors.py +0 -0
  28. {amazon_bedrock_haystack-6.3.0 → amazon_bedrock_haystack-6.4.0}/src/haystack_integrations/common/s3/utils.py +0 -0
  29. {amazon_bedrock_haystack-6.3.0 → amazon_bedrock_haystack-6.4.0}/src/haystack_integrations/components/downloaders/py.typed +0 -0
  30. {amazon_bedrock_haystack-6.3.0 → amazon_bedrock_haystack-6.4.0}/src/haystack_integrations/components/downloaders/s3/__init__.py +0 -0
  31. {amazon_bedrock_haystack-6.3.0 → amazon_bedrock_haystack-6.4.0}/src/haystack_integrations/components/embedders/amazon_bedrock/__init__.py +0 -0
  32. {amazon_bedrock_haystack-6.3.0 → amazon_bedrock_haystack-6.4.0}/src/haystack_integrations/components/embedders/py.typed +0 -0
  33. {amazon_bedrock_haystack-6.3.0 → amazon_bedrock_haystack-6.4.0}/src/haystack_integrations/components/generators/amazon_bedrock/__init__.py +0 -0
  34. {amazon_bedrock_haystack-6.3.0 → amazon_bedrock_haystack-6.4.0}/src/haystack_integrations/components/generators/amazon_bedrock/adapters.py +0 -0
  35. {amazon_bedrock_haystack-6.3.0 → amazon_bedrock_haystack-6.4.0}/src/haystack_integrations/components/generators/amazon_bedrock/chat/__init__.py +0 -0
  36. {amazon_bedrock_haystack-6.3.0 → amazon_bedrock_haystack-6.4.0}/src/haystack_integrations/components/generators/py.typed +0 -0
  37. {amazon_bedrock_haystack-6.3.0 → amazon_bedrock_haystack-6.4.0}/src/haystack_integrations/components/rankers/amazon_bedrock/__init__.py +0 -0
  38. {amazon_bedrock_haystack-6.3.0 → amazon_bedrock_haystack-6.4.0}/src/haystack_integrations/components/rankers/py.typed +0 -0
  39. {amazon_bedrock_haystack-6.3.0 → amazon_bedrock_haystack-6.4.0}/tests/__init__.py +0 -0
  40. {amazon_bedrock_haystack-6.3.0 → amazon_bedrock_haystack-6.4.0}/tests/conftest.py +0 -0
  41. {amazon_bedrock_haystack-6.3.0 → amazon_bedrock_haystack-6.4.0}/tests/test_document_embedder.py +0 -0
  42. {amazon_bedrock_haystack-6.3.0 → amazon_bedrock_haystack-6.4.0}/tests/test_document_image_embedder.py +0 -0
  43. {amazon_bedrock_haystack-6.3.0 → amazon_bedrock_haystack-6.4.0}/tests/test_files/apple.jpg +0 -0
  44. {amazon_bedrock_haystack-6.3.0 → amazon_bedrock_haystack-6.4.0}/tests/test_files/haystack-logo.png +0 -0
  45. {amazon_bedrock_haystack-6.3.0 → amazon_bedrock_haystack-6.4.0}/tests/test_files/sample_pdf_1.pdf +0 -0
  46. {amazon_bedrock_haystack-6.3.0 → amazon_bedrock_haystack-6.4.0}/tests/test_generator.py +0 -0
  47. {amazon_bedrock_haystack-6.3.0 → amazon_bedrock_haystack-6.4.0}/tests/test_ranker.py +0 -0
  48. {amazon_bedrock_haystack-6.3.0 → amazon_bedrock_haystack-6.4.0}/tests/test_s3_downloader.py +0 -0
  49. {amazon_bedrock_haystack-6.3.0 → amazon_bedrock_haystack-6.4.0}/tests/test_text_embedder.py +0 -0
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## [integrations/amazon_bedrock-v6.3.0] - 2026-01-28
4
+
5
+ ### 🌀 Miscellaneous
6
+
7
+ - Feat: Bedrock - support images in tool results (#2783)
8
+
9
+ ## [integrations/amazon_bedrock-v6.2.1] - 2026-01-15
10
+
11
+ ### 🐛 Bug Fixes
12
+
13
+ - None value handling of flattened generation kwargs for AmazonBedrockChatGenerator (#2752)
14
+
15
+
3
16
  ## [integrations/amazon_bedrock-v6.2.0] - 2026-01-13
4
17
 
5
18
  ### 🚀 Features
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: amazon-bedrock-haystack
3
- Version: 6.3.0
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
@@ -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,16 @@ 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}
61
66
 
62
67
 
63
68
  def _convert_image_content_to_bedrock_format(image_content: ImageContent) -> dict[str, Any]:
@@ -181,20 +186,23 @@ def _repair_tool_result_messages(bedrock_formatted_messages: list[dict[str, Any]
181
186
  original_idx = None
182
187
  for tool_call_id in tool_call_ids:
183
188
  for idx, tool_result in tool_result_messages:
184
- 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]
185
190
  for content in tool_result_contents:
186
- if content["toolResult"]["toolUseId"] == tool_call_id:
191
+ if "toolResult" in content and content["toolResult"]["toolUseId"] == tool_call_id:
187
192
  regrouped_tool_result.append(content)
188
193
  # Keep track of the original index of the last tool result message
189
194
  original_idx = idx
195
+ elif "cachePoint" in content and content not in regrouped_tool_result:
196
+ regrouped_tool_result.append(content)
197
+
190
198
  if regrouped_tool_result and original_idx is not None:
191
199
  repaired_tool_result_prompts.append((original_idx, {"role": "user", "content": regrouped_tool_result}))
192
200
 
193
201
  # Remove the tool result messages from bedrock_formatted_messages
194
202
  bedrock_formatted_messages_minus_tool_results: list[tuple[int, Any]] = []
195
203
  for idx, msg in enumerate(bedrock_formatted_messages):
196
- # Assumes the content of tool result messages only contains 'toolResult': {...} objects (e.g. no 'text')
197
- 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"]):
198
206
  bedrock_formatted_messages_minus_tool_results.append((idx, msg))
199
207
 
200
208
  # Add the repaired tool result messages and sort to maintain the correct order
@@ -251,6 +259,32 @@ def _format_text_image_message(message: ChatMessage) -> dict[str, Any]:
251
259
  return {"role": message.role.value, "content": bedrock_content_blocks}
252
260
 
253
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
+
254
288
  def _format_messages(messages: list[ChatMessage]) -> tuple[list[dict[str, Any]], list[dict[str, Any]]]:
255
289
  """
256
290
  Format a list of Haystack ChatMessages to the format expected by Bedrock API.
@@ -264,21 +298,30 @@ def _format_messages(messages: list[ChatMessage]) -> tuple[list[dict[str, Any]],
264
298
  non_system_messages is a list of properly formatted message dictionaries.
265
299
  """
266
300
  # Separate system messages, tool calls, and tool results
267
- system_prompts = []
301
+ system_prompts: list[dict[str, Any]] = []
268
302
  bedrock_formatted_messages = []
269
303
  for msg in messages:
304
+ cache_point = _validate_and_format_cache_point(msg.meta.get("cachePoint"))
270
305
  if msg.is_from(ChatRole.SYSTEM):
271
306
  # Assuming system messages can only contain text
272
307
  # Don't need to track idx since system_messages are handled separately
273
308
  system_prompts.append({"text": msg.text})
274
- elif msg.tool_calls:
275
- 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)
276
315
  elif msg.tool_call_results:
277
- bedrock_formatted_messages.append(_format_tool_result_message(msg))
316
+ formatted_msg = _format_tool_result_message(msg)
278
317
  else:
279
- 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)
280
322
 
281
323
  repaired_bedrock_formatted_messages = _repair_tool_result_messages(bedrock_formatted_messages)
324
+
282
325
  return system_prompts, repaired_bedrock_formatted_messages
283
326
 
284
327
 
@@ -310,6 +353,9 @@ def _parse_completion_response(response_body: dict[str, Any], model: str) -> lis
310
353
  "prompt_tokens": response_body.get("usage", {}).get("inputTokens", 0),
311
354
  "completion_tokens": response_body.get("usage", {}).get("outputTokens", 0),
312
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", {}),
313
359
  },
314
360
  }
315
361
  # guardrail trace
@@ -461,6 +507,9 @@ def _convert_event_to_streaming_chunk(
461
507
  "prompt_tokens": usage.get("inputTokens", 0),
462
508
  "completion_tokens": usage.get("outputTokens", 0),
463
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", {}),
464
513
  }
465
514
  if "trace" in event_meta:
466
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]:
@@ -39,6 +39,10 @@ MODELS_TO_TEST_WITH_THINKING = [
39
39
  "us.anthropic.claude-sonnet-4-20250514-v1:0",
40
40
  ]
41
41
 
42
+ MODELS_TO_TEST_WITH_PROMPT_CACHING = [
43
+ "amazon.nova-micro-v1:0" # cheap, fast model
44
+ ]
45
+
42
46
 
43
47
  def hello_world():
44
48
  return "Hello, World!"
@@ -164,6 +168,7 @@ class TestAmazonBedrockChatGenerator:
164
168
  "boto3_config": boto3_config,
165
169
  "tools": None,
166
170
  "guardrail_config": {"guardrailIdentifier": "test", "guardrailVersion": "test"},
171
+ "tools_cachepoint_config": None,
167
172
  },
168
173
  }
169
174
 
@@ -298,6 +303,7 @@ class TestAmazonBedrockChatGenerator:
298
303
  }
299
304
  ],
300
305
  "guardrail_config": None,
306
+ "tools_cachepoint_config": None,
301
307
  },
302
308
  }
303
309
  },
@@ -945,6 +951,28 @@ class TestAmazonBedrockChatGeneratorInference:
945
951
  assert "trace" in results["replies"][0].meta
946
952
  assert "guardrail" in results["replies"][0].meta["trace"]
947
953
 
954
+ @pytest.mark.parametrize("streaming_callback", [None, print_streaming_chunk])
955
+ @pytest.mark.parametrize("model_name", MODELS_TO_TEST_WITH_PROMPT_CACHING)
956
+ def test_prompt_caching_live_run_with_user_message(self, model_name, streaming_callback):
957
+ generator = AmazonBedrockChatGenerator(model=model_name, streaming_callback=streaming_callback)
958
+
959
+ system_message = ChatMessage.from_system("Always respond with: 'Life is beautiful' (and nothing else).")
960
+
961
+ user_message = ChatMessage.from_user(
962
+ "User message that should be long enough to cache. " * 100, meta={"cachePoint": {"type": "default"}}
963
+ )
964
+ messages = [system_message, user_message]
965
+ result = generator.run(messages=messages)
966
+
967
+ assert "replies" in result
968
+ assert len(result["replies"]) == 1
969
+ usage = result["replies"][0].meta["usage"]
970
+
971
+ # tests run in parallel based on the workflow matrix, so this request should either hit the cache (read tokens)
972
+ # or populate it (write tokens)
973
+ assert usage["cache_read_input_tokens"] > 1000 or usage["cache_write_input_tokens"] > 1000
974
+ assert "cache_details" in usage
975
+
948
976
  @pytest.mark.parametrize("model_name", [MODELS_TO_TEST_WITH_TOOLS[0]]) # just one model is enough
949
977
  def test_pipeline_with_amazon_bedrock_chat_generator(self, model_name, tools):
950
978
  """
@@ -22,6 +22,7 @@ from haystack_integrations.components.generators.amazon_bedrock.chat.utils impor
22
22
  _format_tools,
23
23
  _parse_completion_response,
24
24
  _parse_streaming_response,
25
+ _validate_and_format_cache_point,
25
26
  _validate_guardrail_config,
26
27
  )
27
28
 
@@ -59,7 +60,7 @@ def tools():
59
60
 
60
61
  class TestAmazonBedrockChatGeneratorUtils:
61
62
  def test_format_tools(self, tools):
62
- formatted_tool = _format_tools(tools)
63
+ formatted_tool = _format_tools(tools, tools_cachepoint_config={"type": "default"})
63
64
  assert formatted_tool == {
64
65
  "tools": [
65
66
  {
@@ -84,7 +85,8 @@ class TestAmazonBedrockChatGeneratorUtils:
84
85
  },
85
86
  }
86
87
  },
87
- ]
88
+ {"cachePoint": {"type": "default"}},
89
+ ],
88
90
  }
89
91
 
90
92
  def test_format_messages(self):
@@ -121,6 +123,52 @@ class TestAmazonBedrockChatGeneratorUtils:
121
123
  {"role": "assistant", "content": [{"text": "The weather in Paris is sunny and 25°C."}]},
122
124
  ]
123
125
 
126
+ def test_format_messages_with_cache_point(self):
127
+ meta = {"cachePoint": {"type": "default"}}
128
+
129
+ messages = [
130
+ ChatMessage.from_system("\\nYou are a helpful assistant, be super brief in your responses.", meta=meta),
131
+ ChatMessage.from_user("What is the weather in Paris?", meta=meta),
132
+ ChatMessage.from_assistant(
133
+ tool_calls=[ToolCall(id="123", tool_name="weather", arguments={"city": "Paris"})], meta=meta
134
+ ),
135
+ ChatMessage.from_tool(
136
+ tool_result="Sunny and 25°C",
137
+ origin=ToolCall(id="123", tool_name="weather", arguments={"city": "Paris"}),
138
+ meta=meta,
139
+ ),
140
+ ChatMessage.from_assistant("The weather in Paris is sunny and 25°C.", meta=meta),
141
+ ]
142
+ formatted_system_prompts, formatted_messages = _format_messages(messages)
143
+ assert formatted_system_prompts == [
144
+ {"text": "\\nYou are a helpful assistant, be super brief in your responses."},
145
+ {"cachePoint": {"type": "default"}},
146
+ ]
147
+ assert formatted_messages == [
148
+ {
149
+ "role": "user",
150
+ "content": [{"text": "What is the weather in Paris?"}, {"cachePoint": {"type": "default"}}],
151
+ },
152
+ {
153
+ "role": "assistant",
154
+ "content": [
155
+ {"toolUse": {"toolUseId": "123", "name": "weather", "input": {"city": "Paris"}}},
156
+ {"cachePoint": {"type": "default"}},
157
+ ],
158
+ },
159
+ {
160
+ "role": "user",
161
+ "content": [
162
+ {"toolResult": {"toolUseId": "123", "content": [{"text": "Sunny and 25°C"}]}},
163
+ {"cachePoint": {"type": "default"}},
164
+ ],
165
+ },
166
+ {
167
+ "role": "assistant",
168
+ "content": [{"text": "The weather in Paris is sunny and 25°C."}, {"cachePoint": {"type": "default"}}],
169
+ },
170
+ ]
171
+
124
172
  def test_format_messages_tool_result_with_image(self):
125
173
  base64_image = (
126
174
  "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg=="
@@ -420,7 +468,14 @@ class TestAmazonBedrockChatGeneratorUtils:
420
468
  text_response = {
421
469
  "output": {"message": {"role": "assistant", "content": [{"text": "This is a test response"}]}},
422
470
  "stopReason": "end_turn",
423
- "usage": {"inputTokens": 10, "outputTokens": 20, "totalTokens": 30},
471
+ "usage": {
472
+ "inputTokens": 10,
473
+ "outputTokens": 20,
474
+ "totalTokens": 30,
475
+ "cacheReadInputTokens": 1000,
476
+ "cacheWriteInputTokens": 0,
477
+ "cacheDetails": {},
478
+ },
424
479
  }
425
480
 
426
481
  replies = _parse_completion_response(text_response, model)
@@ -430,7 +485,14 @@ class TestAmazonBedrockChatGeneratorUtils:
430
485
  assert replies[0].meta == {
431
486
  "model": model,
432
487
  "finish_reason": "stop",
433
- "usage": {"prompt_tokens": 10, "completion_tokens": 20, "total_tokens": 30},
488
+ "usage": {
489
+ "prompt_tokens": 10,
490
+ "completion_tokens": 20,
491
+ "total_tokens": 30,
492
+ "cache_read_input_tokens": 1000,
493
+ "cache_write_input_tokens": 0,
494
+ "cache_details": {},
495
+ },
434
496
  "index": 0,
435
497
  }
436
498
 
@@ -457,7 +519,14 @@ class TestAmazonBedrockChatGeneratorUtils:
457
519
  assert replies[0].meta == {
458
520
  "model": model,
459
521
  "finish_reason": "tool_calls",
460
- "usage": {"prompt_tokens": 15, "completion_tokens": 25, "total_tokens": 40},
522
+ "usage": {
523
+ "prompt_tokens": 15,
524
+ "completion_tokens": 25,
525
+ "total_tokens": 40,
526
+ "cache_read_input_tokens": 0,
527
+ "cache_write_input_tokens": 0,
528
+ "cache_details": {},
529
+ },
461
530
  "index": 0,
462
531
  }
463
532
 
@@ -488,7 +557,14 @@ class TestAmazonBedrockChatGeneratorUtils:
488
557
  assert replies[0].meta == {
489
558
  "model": model,
490
559
  "finish_reason": "stop",
491
- "usage": {"prompt_tokens": 25, "completion_tokens": 35, "total_tokens": 60},
560
+ "usage": {
561
+ "prompt_tokens": 25,
562
+ "completion_tokens": 35,
563
+ "total_tokens": 60,
564
+ "cache_read_input_tokens": 0,
565
+ "cache_write_input_tokens": 0,
566
+ "cache_details": {},
567
+ },
492
568
  "index": 0,
493
569
  }
494
570
 
@@ -556,7 +632,14 @@ class TestAmazonBedrockChatGeneratorUtils:
556
632
  "model": "anthropic.claude-3-5-sonnet-20240620-v1:0",
557
633
  "index": 0,
558
634
  "finish_reason": "tool_calls",
559
- "usage": {"prompt_tokens": 366, "completion_tokens": 134, "total_tokens": 500},
635
+ "usage": {
636
+ "prompt_tokens": 366,
637
+ "completion_tokens": 134,
638
+ "total_tokens": 500,
639
+ "cache_read_input_tokens": 0,
640
+ "cache_write_input_tokens": 0,
641
+ "cache_details": {},
642
+ },
560
643
  },
561
644
  )
562
645
  assert replies[0] == expected_message
@@ -610,6 +693,7 @@ class TestAmazonBedrockChatGeneratorUtils:
610
693
  "totalTokens": 558,
611
694
  "cacheReadInputTokens": 0,
612
695
  "cacheWriteInputTokens": 0,
696
+ "cacheDetails": {},
613
697
  },
614
698
  "metrics": {"latencyMs": 4811},
615
699
  }
@@ -646,7 +730,14 @@ class TestAmazonBedrockChatGeneratorUtils:
646
730
  "model": "arn:aws:bedrock:us-east-1::inference-profile/us.anthropic.claude-3-7-sonnet-20250219-v1:0",
647
731
  "index": 0,
648
732
  "finish_reason": "tool_calls",
649
- "usage": {"prompt_tokens": 412, "completion_tokens": 146, "total_tokens": 558},
733
+ "usage": {
734
+ "prompt_tokens": 412,
735
+ "completion_tokens": 146,
736
+ "total_tokens": 558,
737
+ "cache_read_input_tokens": 0,
738
+ "cache_write_input_tokens": 0,
739
+ "cache_details": {},
740
+ },
650
741
  },
651
742
  )
652
743
  assert replies[0] == expected_message
@@ -712,7 +803,14 @@ class TestAmazonBedrockChatGeneratorUtils:
712
803
  "model": model,
713
804
  "finish_reason": "content_filter",
714
805
  "index": 0,
715
- "usage": {"prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0},
806
+ "usage": {
807
+ "prompt_tokens": 0,
808
+ "completion_tokens": 0,
809
+ "total_tokens": 0,
810
+ "cache_read_input_tokens": 0,
811
+ "cache_write_input_tokens": 0,
812
+ "cache_details": {},
813
+ },
716
814
  "trace": trace,
717
815
  }
718
816
 
@@ -784,7 +882,14 @@ class TestAmazonBedrockChatGeneratorUtils:
784
882
  "model": model,
785
883
  "index": 0,
786
884
  "finish_reason": "tool_calls",
787
- "usage": {"prompt_tokens": 364, "completion_tokens": 71, "total_tokens": 435},
885
+ "usage": {
886
+ "prompt_tokens": 364,
887
+ "completion_tokens": 71,
888
+ "total_tokens": 435,
889
+ "cache_read_input_tokens": 0,
890
+ "cache_write_input_tokens": 0,
891
+ "cache_details": {},
892
+ },
788
893
  },
789
894
  )
790
895
  ]
@@ -852,7 +957,14 @@ class TestAmazonBedrockChatGeneratorUtils:
852
957
  meta={
853
958
  "model": model,
854
959
  "received_at": ANY,
855
- "usage": {"prompt_tokens": 364, "completion_tokens": 71, "total_tokens": 435},
960
+ "usage": {
961
+ "prompt_tokens": 364,
962
+ "completion_tokens": 71,
963
+ "total_tokens": 435,
964
+ "cache_read_input_tokens": 0,
965
+ "cache_write_input_tokens": 0,
966
+ "cache_details": {},
967
+ },
856
968
  },
857
969
  component_info=c_info,
858
970
  ),
@@ -976,7 +1088,14 @@ class TestAmazonBedrockChatGeneratorUtils:
976
1088
  "model": "arn:aws:bedrock:us-east-1::inference-profile/us.anthropic.claude-sonnet-4-20250514-v1:0",
977
1089
  "index": 0,
978
1090
  "finish_reason": "tool_calls",
979
- "usage": {"prompt_tokens": 412, "completion_tokens": 104, "total_tokens": 516},
1091
+ "usage": {
1092
+ "prompt_tokens": 412,
1093
+ "completion_tokens": 104,
1094
+ "total_tokens": 516,
1095
+ "cache_read_input_tokens": 0,
1096
+ "cache_write_input_tokens": 0,
1097
+ "cache_details": {},
1098
+ },
980
1099
  "completion_start_time": ANY,
981
1100
  },
982
1101
  )
@@ -1062,7 +1181,14 @@ class TestAmazonBedrockChatGeneratorUtils:
1062
1181
  "model": model,
1063
1182
  "index": 0,
1064
1183
  "finish_reason": "stop",
1065
- "usage": {"prompt_tokens": 461, "completion_tokens": 138, "total_tokens": 599},
1184
+ "usage": {
1185
+ "prompt_tokens": 461,
1186
+ "completion_tokens": 138,
1187
+ "total_tokens": 599,
1188
+ "cache_read_input_tokens": 0,
1189
+ "cache_write_input_tokens": 0,
1190
+ "cache_details": {},
1191
+ },
1066
1192
  "completion_start_time": ANY,
1067
1193
  },
1068
1194
  )
@@ -1140,7 +1266,14 @@ class TestAmazonBedrockChatGeneratorUtils:
1140
1266
  "model": "anthropic.claude-3-5-sonnet-20240620-v1:0",
1141
1267
  "index": 0,
1142
1268
  "finish_reason": "tool_calls",
1143
- "usage": {"prompt_tokens": 366, "completion_tokens": 83, "total_tokens": 449},
1269
+ "usage": {
1270
+ "prompt_tokens": 366,
1271
+ "completion_tokens": 83,
1272
+ "total_tokens": 449,
1273
+ "cache_read_input_tokens": 0,
1274
+ "cache_write_input_tokens": 0,
1275
+ "cache_details": {},
1276
+ },
1144
1277
  "completion_start_time": ANY,
1145
1278
  },
1146
1279
  )
@@ -1216,7 +1349,14 @@ class TestAmazonBedrockChatGeneratorUtils:
1216
1349
  "model": model,
1217
1350
  "index": 0,
1218
1351
  "finish_reason": "content_filter",
1219
- "usage": {"prompt_tokens": 0, "completion_tokens": 0, "total_tokens": 0},
1352
+ "usage": {
1353
+ "prompt_tokens": 0,
1354
+ "completion_tokens": 0,
1355
+ "total_tokens": 0,
1356
+ "cache_read_input_tokens": 0,
1357
+ "cache_write_input_tokens": 0,
1358
+ "cache_details": {},
1359
+ },
1220
1360
  "trace": trace,
1221
1361
  },
1222
1362
  )
@@ -1450,3 +1590,25 @@ class TestAmazonBedrockChatGeneratorUtils:
1450
1590
  },
1451
1591
  streaming=False,
1452
1592
  )
1593
+
1594
+ def test_validate_and_format_cache_point(self):
1595
+ cache_point = _validate_and_format_cache_point(None)
1596
+ assert cache_point is None
1597
+
1598
+ cache_point = _validate_and_format_cache_point({})
1599
+ assert cache_point is None
1600
+
1601
+ cache_point = _validate_and_format_cache_point({"type": "default"})
1602
+ assert cache_point == {"cachePoint": {"type": "default"}}
1603
+
1604
+ cache_point = _validate_and_format_cache_point({"type": "default", "ttl": "5m"})
1605
+ assert cache_point == {"cachePoint": {"type": "default", "ttl": "5m"}}
1606
+
1607
+ with pytest.raises(ValueError, match=r"Cache point must have a 'type' key with value 'default'."):
1608
+ _validate_and_format_cache_point({"invalid": "config"})
1609
+
1610
+ with pytest.raises(ValueError, match=r"Cache point must have a 'type' key with value 'default'."):
1611
+ _validate_and_format_cache_point({"type": "invalid"})
1612
+
1613
+ with pytest.raises(ValueError, match=r"Cache point can only contain 'type' and 'ttl' keys."):
1614
+ _validate_and_format_cache_point({"type": "default", "invalid": "config"})