diff --git a/integrations/amazon_bedrock/CHANGELOG.md b/integrations/amazon_bedrock/CHANGELOG.md index 1068e870a..8e4350423 100644 --- a/integrations/amazon_bedrock/CHANGELOG.md +++ b/integrations/amazon_bedrock/CHANGELOG.md @@ -1,27 +1,45 @@ # Changelog +## [integrations/amazon_bedrock-v1.1.1] - 2024-12-03 + +### πŸ› Bug Fixes + +- AmazonBedrockChatGenerator with Claude raises moot warning for stream… (#1205) +- Allow passing boto3 config to all AWS Bedrock classes (#1166) + +### 🧹 Chores + +- Fix linting/isort (#1215) + +### πŸŒ€ Miscellaneous + +- Chore: use class methods to create `ChatMessage` (#1222) + ## [integrations/amazon_bedrock-v1.1.0] - 2024-10-23 ### 🚜 Refactor - Avoid downloading tokenizer if `truncate` is `False` (#1152) -### βš™οΈ Miscellaneous Tasks +### βš™οΈ CI - Adopt uv as installer (#1142) + ## [integrations/amazon_bedrock-v1.0.5] - 2024-10-17 ### πŸš€ Features - Add prefixes to supported model patterns to allow cross region model ids (#1127) + ## [integrations/amazon_bedrock-v1.0.4] - 2024-10-16 ### πŸ› Bug Fixes - Avoid bedrock read timeout (add boto3_config param) (#1135) + ## [integrations/amazon_bedrock-v1.0.3] - 2024-10-04 ### πŸ› Bug Fixes @@ -33,10 +51,14 @@ - Remove usage of deprecated `ChatMessage.to_openai_format` (#1007) -### βš™οΈ Miscellaneous Tasks +### 🧹 Chores - Update ruff linting scripts and settings (#1105) +### πŸŒ€ Miscellaneous + +- Modify regex to allow cross-region inference in bedrock (#1120) + ## [integrations/amazon_bedrock-v1.0.1] - 2024-08-19 ### πŸš€ Features @@ -47,6 +69,7 @@ - Normalising ChatGenerators output (#973) + ## [integrations/amazon_bedrock-v1.0.0] - 2024-08-12 ### 🚜 Refactor @@ -57,13 +80,14 @@ - Do not retry tests in `hatch run test` command (#954) + ## [integrations/amazon_bedrock-v0.10.0] - 2024-08-12 ### πŸ› Bug Fixes - Support streaming_callback param in amazon bedrock generators (#927) -### Docs +### πŸŒ€ Miscellaneous - Update AmazonBedrockChatGenerator docstrings (#949) - Update AmazonBedrockGenerator docstrings (#956) @@ -75,11 +99,19 @@ - Use non-gated tokenizer as fallback for mistral in AmazonBedrockChatGenerator (#843) - Made truncation optional for BedrockGenerator (#833) -### βš™οΈ Miscellaneous Tasks +### βš™οΈ CI - Retry tests to reduce flakyness (#836) + +### 🧹 Chores + - Update ruff invocation to include check parameter (#853) +### πŸŒ€ Miscellaneous + +- Ci: install `pytest-rerunfailures` where needed; add retry config to `test-cov` script (#845) +- Add meta deprecration warning (#910) + ## [integrations/amazon_bedrock-v0.9.0] - 2024-06-14 ### πŸš€ Features @@ -96,8 +128,18 @@ - Max_tokens typo in Mistral Chat (#740) +### πŸŒ€ Miscellaneous + +- Chore: change the pydoc renderer class (#718) +- Adding support of "amazon.titan-embed-text-v2:0" (#735) + ## [integrations/amazon_bedrock-v0.7.1] - 2024-04-24 +### πŸŒ€ Miscellaneous + +- Chore: add license classifiers (#680) +- Fix: Fix streaming_callback serialization in AmazonBedrockChatGenerator (#685) + ## [integrations/amazon_bedrock-v0.7.0] - 2024-04-16 ### πŸš€ Features @@ -108,6 +150,11 @@ - Disable-class-def (#556) +### πŸŒ€ Miscellaneous + +- Remove references to Python 3.7 (#601) +- [Bedrock] Added Amazon Bedrock examples (#635) + ## [integrations/amazon_bedrock-v0.6.0] - 2024-03-11 ### πŸš€ Features @@ -119,6 +166,10 @@ - Small consistency improvements (#536) - Review integrations bedrock (#550) +### πŸŒ€ Miscellaneous + +- Docs updates + two additional unit tests (#513) + ## [integrations/amazon_bedrock-v0.5.1] - 2024-02-22 ### πŸš€ Features @@ -129,20 +180,27 @@ - Fix order of API docs (#447) -This PR will also push the docs to Readme - ### πŸ“š Documentation - Update category slug (#442) -### βš™οΈ Miscellaneous Tasks +### 🧹 Chores - Update Amazon Bedrock integration to use new generic callable (de)serializers for their callback handlers (#452) - Use `serialize_callable` instead of `serialize_callback_handler` in Bedrock (#459) +### πŸŒ€ Miscellaneous + +- Amazon bedrock: generate api docs (#326) +- Adopt Secret to Amazon Bedrock (#416) +- Bedrock - remove `supports` method (#456) +- Bedrock refactoring (#455) +- Bedrock Text Embedder (#466) +- Bedrock Document Embedder (#468) + ## [integrations/amazon_bedrock-v0.3.0] - 2024-01-30 -### βš™οΈ Miscellaneous Tasks +### 🧹 Chores - [**breaking**] Rename `model_name` to `model` in `AmazonBedrockGenerator` (#220) - Amazon Bedrock subproject refactoring (#293) @@ -150,4 +208,8 @@ This PR will also push the docs to Readme ## [integrations/amazon_bedrock-v0.1.0] - 2024-01-03 +### πŸŒ€ Miscellaneous + +- [Amazon Bedrock] Add AmazonBedrockGenerator (#153) + diff --git a/integrations/amazon_bedrock/src/haystack_integrations/components/embedders/amazon_bedrock/document_embedder.py b/integrations/amazon_bedrock/src/haystack_integrations/components/embedders/amazon_bedrock/document_embedder.py index f2906c00d..f15601f57 100755 --- a/integrations/amazon_bedrock/src/haystack_integrations/components/embedders/amazon_bedrock/document_embedder.py +++ b/integrations/amazon_bedrock/src/haystack_integrations/components/embedders/amazon_bedrock/document_embedder.py @@ -2,6 +2,7 @@ import logging from typing import Any, Dict, List, Literal, Optional +from botocore.config import Config from botocore.exceptions import ClientError from haystack import component, default_from_dict, default_to_dict from haystack.dataclasses import Document @@ -73,6 +74,7 @@ def __init__( progress_bar: bool = True, meta_fields_to_embed: Optional[List[str]] = None, embedding_separator: str = "\n", + boto3_config: Optional[Dict[str, Any]] = None, **kwargs, ): """ @@ -98,6 +100,7 @@ def __init__( to keep the logs clean. :param meta_fields_to_embed: List of meta fields that should be embedded along with the Document text. :param embedding_separator: Separator used to concatenate the meta fields to the Document text. + :param boto3_config: The configuration for the boto3 client. :param kwargs: Additional parameters to pass for model inference. For example, `input_type` and `truncate` for Cohere models. :raises ValueError: If the model is not supported. @@ -110,6 +113,19 @@ def __init__( ) raise ValueError(msg) + self.model = model + self.aws_access_key_id = aws_access_key_id + self.aws_secret_access_key = aws_secret_access_key + self.aws_session_token = aws_session_token + self.aws_region_name = aws_region_name + self.aws_profile_name = aws_profile_name + self.batch_size = batch_size + self.progress_bar = progress_bar + self.meta_fields_to_embed = meta_fields_to_embed or [] + self.embedding_separator = embedding_separator + self.boto3_config = boto3_config + self.kwargs = kwargs + def resolve_secret(secret: Optional[Secret]) -> Optional[str]: return secret.resolve_value() if secret else None @@ -121,7 +137,10 @@ def resolve_secret(secret: Optional[Secret]) -> Optional[str]: aws_region_name=resolve_secret(aws_region_name), aws_profile_name=resolve_secret(aws_profile_name), ) - self._client = session.client("bedrock-runtime") + config: Optional[Config] = None + if self.boto3_config: + config = Config(**self.boto3_config) + self._client = session.client("bedrock-runtime", config=config) except Exception as exception: msg = ( "Could not connect to Amazon Bedrock. Make sure the AWS environment is configured correctly. " @@ -129,18 +148,6 @@ def resolve_secret(secret: Optional[Secret]) -> Optional[str]: ) raise AmazonBedrockConfigurationError(msg) from exception - self.model = model - self.aws_access_key_id = aws_access_key_id - self.aws_secret_access_key = aws_secret_access_key - self.aws_session_token = aws_session_token - self.aws_region_name = aws_region_name - self.aws_profile_name = aws_profile_name - self.batch_size = batch_size - self.progress_bar = progress_bar - self.meta_fields_to_embed = meta_fields_to_embed or [] - self.embedding_separator = embedding_separator - self.kwargs = kwargs - def _prepare_texts_to_embed(self, documents: List[Document]) -> List[str]: """ Prepare the texts to embed by concatenating the Document text with the metadata fields to embed. @@ -269,6 +276,7 @@ def to_dict(self) -> Dict[str, Any]: progress_bar=self.progress_bar, meta_fields_to_embed=self.meta_fields_to_embed, embedding_separator=self.embedding_separator, + boto3_config=self.boto3_config, **self.kwargs, ) diff --git a/integrations/amazon_bedrock/src/haystack_integrations/components/embedders/amazon_bedrock/text_embedder.py b/integrations/amazon_bedrock/src/haystack_integrations/components/embedders/amazon_bedrock/text_embedder.py index 0cceda92f..0acd51da5 100755 --- a/integrations/amazon_bedrock/src/haystack_integrations/components/embedders/amazon_bedrock/text_embedder.py +++ b/integrations/amazon_bedrock/src/haystack_integrations/components/embedders/amazon_bedrock/text_embedder.py @@ -2,6 +2,7 @@ import logging from typing import Any, Dict, List, Literal, Optional +from botocore.config import Config from botocore.exceptions import ClientError from haystack import component, default_from_dict, default_to_dict from haystack.utils.auth import Secret, deserialize_secrets_inplace @@ -62,6 +63,7 @@ def __init__( aws_session_token: Optional[Secret] = Secret.from_env_var("AWS_SESSION_TOKEN", strict=False), # noqa: B008 aws_region_name: Optional[Secret] = Secret.from_env_var("AWS_DEFAULT_REGION", strict=False), # noqa: B008 aws_profile_name: Optional[Secret] = Secret.from_env_var("AWS_PROFILE", strict=False), # noqa: B008 + boto3_config: Optional[Dict[str, Any]] = None, **kwargs, ): """ @@ -81,6 +83,7 @@ def __init__( :param aws_session_token: AWS session token. :param aws_region_name: AWS region name. :param aws_profile_name: AWS profile name. + :param boto3_config: The configuration for the boto3 client. :param kwargs: Additional parameters to pass for model inference. For example, `input_type` and `truncate` for Cohere models. :raises ValueError: If the model is not supported. @@ -92,6 +95,15 @@ def __init__( ) raise ValueError(msg) + self.model = model + self.aws_access_key_id = aws_access_key_id + self.aws_secret_access_key = aws_secret_access_key + self.aws_session_token = aws_session_token + self.aws_region_name = aws_region_name + self.aws_profile_name = aws_profile_name + self.boto3_config = boto3_config + self.kwargs = kwargs + def resolve_secret(secret: Optional[Secret]) -> Optional[str]: return secret.resolve_value() if secret else None @@ -103,7 +115,10 @@ def resolve_secret(secret: Optional[Secret]) -> Optional[str]: aws_region_name=resolve_secret(aws_region_name), aws_profile_name=resolve_secret(aws_profile_name), ) - self._client = session.client("bedrock-runtime") + config: Optional[Config] = None + if self.boto3_config: + config = Config(**self.boto3_config) + self._client = session.client("bedrock-runtime", config=config) except Exception as exception: msg = ( "Could not connect to Amazon Bedrock. Make sure the AWS environment is configured correctly. " @@ -111,14 +126,6 @@ def resolve_secret(secret: Optional[Secret]) -> Optional[str]: ) raise AmazonBedrockConfigurationError(msg) from exception - self.model = model - self.aws_access_key_id = aws_access_key_id - self.aws_secret_access_key = aws_secret_access_key - self.aws_session_token = aws_session_token - self.aws_region_name = aws_region_name - self.aws_profile_name = aws_profile_name - self.kwargs = kwargs - @component.output_types(embedding=List[float]) def run(self, text: str): """Embeds the input text using the Amazon Bedrock model. @@ -185,6 +192,7 @@ def to_dict(self) -> Dict[str, Any]: aws_region_name=self.aws_region_name.to_dict() if self.aws_region_name else None, aws_profile_name=self.aws_profile_name.to_dict() if self.aws_profile_name else None, model=self.model, + boto3_config=self.boto3_config, **self.kwargs, ) diff --git a/integrations/amazon_bedrock/src/haystack_integrations/components/generators/amazon_bedrock/chat/chat_generator.py b/integrations/amazon_bedrock/src/haystack_integrations/components/generators/amazon_bedrock/chat/chat_generator.py index 6bb3cc301..183198bce 100644 --- a/integrations/amazon_bedrock/src/haystack_integrations/components/generators/amazon_bedrock/chat/chat_generator.py +++ b/integrations/amazon_bedrock/src/haystack_integrations/components/generators/amazon_bedrock/chat/chat_generator.py @@ -3,6 +3,7 @@ import re from typing import Any, Callable, ClassVar, Dict, List, Optional, Type +from botocore.config import Config from botocore.exceptions import ClientError from haystack import component, default_from_dict, default_to_dict from haystack.dataclasses import ChatMessage, StreamingChunk @@ -77,6 +78,7 @@ def __init__( stop_words: Optional[List[str]] = None, streaming_callback: Optional[Callable[[StreamingChunk], None]] = None, truncate: Optional[bool] = True, + boto3_config: Optional[Dict[str, Any]] = None, ): """ Initializes the `AmazonBedrockChatGenerator` with the provided parameters. The parameters are passed to the @@ -110,6 +112,11 @@ def __init__( [StreamingChunk](https://docs.haystack.deepset.ai/docs/data-classes#streamingchunk) object and switches the streaming mode on. :param truncate: Whether to truncate the prompt messages or not. + :param boto3_config: The configuration for the boto3 client. + + :raises ValueError: If the model name is empty or None. + :raises AmazonBedrockConfigurationError: If the AWS environment is not configured correctly or the model is + not supported. """ if not model: msg = "'model' cannot be None or empty string" @@ -120,7 +127,10 @@ def __init__( self.aws_session_token = aws_session_token self.aws_region_name = aws_region_name self.aws_profile_name = aws_profile_name + self.stop_words = stop_words or [] + self.streaming_callback = streaming_callback self.truncate = truncate + self.boto3_config = boto3_config # get the model adapter for the given model model_adapter_cls = self.get_model_adapter(model=model) @@ -141,7 +151,10 @@ def resolve_secret(secret: Optional[Secret]) -> Optional[str]: aws_region_name=resolve_secret(aws_region_name), aws_profile_name=resolve_secret(aws_profile_name), ) - self.client = session.client("bedrock-runtime") + config: Optional[Config] = None + if self.boto3_config: + config = Config(**self.boto3_config) + self.client = session.client("bedrock-runtime", config=config) except Exception as exception: msg = ( "Could not connect to Amazon Bedrock. Make sure the AWS environment is configured correctly. " @@ -149,9 +162,6 @@ def resolve_secret(secret: Optional[Secret]) -> Optional[str]: ) raise AmazonBedrockConfigurationError(msg) from exception - self.stop_words = stop_words or [] - self.streaming_callback = streaming_callback - @component.output_types(replies=List[ChatMessage]) def run( self, @@ -256,6 +266,7 @@ def to_dict(self) -> Dict[str, Any]: generation_kwargs=self.model_adapter.generation_kwargs, streaming_callback=callback_name, truncate=self.truncate, + boto3_config=self.boto3_config, ) @classmethod diff --git a/integrations/amazon_bedrock/tests/test_chat_generator.py b/integrations/amazon_bedrock/tests/test_chat_generator.py index 22594af2c..8d6a5c3ee 100644 --- a/integrations/amazon_bedrock/tests/test_chat_generator.py +++ b/integrations/amazon_bedrock/tests/test_chat_generator.py @@ -1,7 +1,7 @@ import json import logging import os -from typing import Optional, Type +from typing import Any, Dict, Optional, Type from unittest.mock import MagicMock, patch import pytest @@ -26,7 +26,16 @@ ] -def test_to_dict(mock_boto3_session): +@pytest.mark.parametrize( + "boto3_config", + [ + None, + { + "read_timeout": 1000, + }, + ], +) +def test_to_dict(mock_boto3_session: Any, boto3_config: Optional[Dict[str, Any]]): """ Test that the to_dict method returns the correct dictionary without aws credentials """ @@ -34,6 +43,7 @@ def test_to_dict(mock_boto3_session): model="anthropic.claude-v2", generation_kwargs={"temperature": 0.7}, streaming_callback=print_streaming_chunk, + boto3_config=boto3_config, ) expected_dict = { "type": KLASS, @@ -48,13 +58,23 @@ def test_to_dict(mock_boto3_session): "stop_words": [], "streaming_callback": "haystack.components.generators.utils.print_streaming_chunk", "truncate": True, + "boto3_config": boto3_config, }, } assert generator.to_dict() == expected_dict -def test_from_dict(mock_boto3_session): +@pytest.mark.parametrize( + "boto3_config", + [ + None, + { + "read_timeout": 1000, + }, + ], +) +def test_from_dict(mock_boto3_session: Any, boto3_config: Optional[Dict[str, Any]]): """ Test that the from_dict method returns the correct object """ @@ -71,12 +91,14 @@ def test_from_dict(mock_boto3_session): "generation_kwargs": {"temperature": 0.7}, "streaming_callback": "haystack.components.generators.utils.print_streaming_chunk", "truncate": True, + "boto3_config": boto3_config, }, } ) assert generator.model == "anthropic.claude-v2" assert generator.model_adapter.generation_kwargs == {"temperature": 0.7} assert generator.streaming_callback == print_streaming_chunk + assert generator.boto3_config == boto3_config def test_default_constructor(mock_boto3_session, set_env_variables): diff --git a/integrations/amazon_bedrock/tests/test_document_embedder.py b/integrations/amazon_bedrock/tests/test_document_embedder.py index 9856c97bb..05672e9c7 100644 --- a/integrations/amazon_bedrock/tests/test_document_embedder.py +++ b/integrations/amazon_bedrock/tests/test_document_embedder.py @@ -1,4 +1,5 @@ import io +from typing import Any, Dict, Optional from unittest.mock import patch import pytest @@ -66,10 +67,20 @@ def test_connection_error(self, mock_boto3_session): input_type="fake_input_type", ) - def test_to_dict(self, mock_boto3_session): + @pytest.mark.parametrize( + "boto3_config", + [ + None, + { + "read_timeout": 1000, + }, + ], + ) + def test_to_dict(self, mock_boto3_session: Any, boto3_config: Optional[Dict[str, Any]]): embedder = AmazonBedrockDocumentEmbedder( model="cohere.embed-english-v3", input_type="search_document", + boto3_config=boto3_config, ) expected_dict = { @@ -86,12 +97,22 @@ def test_to_dict(self, mock_boto3_session): "progress_bar": True, "meta_fields_to_embed": [], "embedding_separator": "\n", + "boto3_config": boto3_config, }, } assert embedder.to_dict() == expected_dict - def test_from_dict(self, mock_boto3_session): + @pytest.mark.parametrize( + "boto3_config", + [ + None, + { + "read_timeout": 1000, + }, + ], + ) + def test_from_dict(self, mock_boto3_session: Any, boto3_config: Optional[Dict[str, Any]]): data = { "type": TYPE, "init_parameters": { @@ -106,6 +127,7 @@ def test_from_dict(self, mock_boto3_session): "progress_bar": True, "meta_fields_to_embed": [], "embedding_separator": "\n", + "boto3_config": boto3_config, }, } @@ -117,6 +139,7 @@ def test_from_dict(self, mock_boto3_session): assert embedder.progress_bar assert embedder.meta_fields_to_embed == [] assert embedder.embedding_separator == "\n" + assert embedder.boto3_config == boto3_config def test_init_invalid_model(self): with pytest.raises(ValueError): diff --git a/integrations/amazon_bedrock/tests/test_generator.py b/integrations/amazon_bedrock/tests/test_generator.py index be645218e..54b185da5 100644 --- a/integrations/amazon_bedrock/tests/test_generator.py +++ b/integrations/amazon_bedrock/tests/test_generator.py @@ -1,4 +1,4 @@ -from typing import Optional, Type +from typing import Any, Dict, Optional, Type from unittest.mock import MagicMock, call, patch import pytest @@ -17,11 +17,22 @@ ) -def test_to_dict(mock_boto3_session): +@pytest.mark.parametrize( + "boto3_config", + [ + None, + { + "read_timeout": 1000, + }, + ], +) +def test_to_dict(mock_boto3_session: Any, boto3_config: Optional[Dict[str, Any]]): """ Test that the to_dict method returns the correct dictionary without aws credentials """ - generator = AmazonBedrockGenerator(model="anthropic.claude-v2", max_length=99, truncate=False, temperature=10) + generator = AmazonBedrockGenerator( + model="anthropic.claude-v2", max_length=99, truncate=False, temperature=10, boto3_config=boto3_config + ) expected_dict = { "type": "haystack_integrations.components.generators.amazon_bedrock.generator.AmazonBedrockGenerator", @@ -36,14 +47,23 @@ def test_to_dict(mock_boto3_session): "truncate": False, "temperature": 10, "streaming_callback": None, - "boto3_config": None, + "boto3_config": boto3_config, }, } assert generator.to_dict() == expected_dict -def test_from_dict(mock_boto3_session): +@pytest.mark.parametrize( + "boto3_config", + [ + None, + { + "read_timeout": 1000, + }, + ], +) +def test_from_dict(mock_boto3_session: Any, boto3_config: Optional[Dict[str, Any]]): """ Test that the from_dict method returns the correct object """ @@ -58,16 +78,14 @@ def test_from_dict(mock_boto3_session): "aws_profile_name": {"type": "env_var", "env_vars": ["AWS_PROFILE"], "strict": False}, "model": "anthropic.claude-v2", "max_length": 99, - "boto3_config": { - "read_timeout": 1000, - }, + "boto3_config": boto3_config, }, } ) assert generator.max_length == 99 assert generator.model == "anthropic.claude-v2" - assert generator.boto3_config == {"read_timeout": 1000} + assert generator.boto3_config == boto3_config def test_default_constructor(mock_boto3_session, set_env_variables): diff --git a/integrations/amazon_bedrock/tests/test_text_embedder.py b/integrations/amazon_bedrock/tests/test_text_embedder.py index 4f4e92448..2518b5c5f 100644 --- a/integrations/amazon_bedrock/tests/test_text_embedder.py +++ b/integrations/amazon_bedrock/tests/test_text_embedder.py @@ -59,6 +59,7 @@ def test_to_dict(self, mock_boto3_session): "aws_profile_name": {"type": "env_var", "env_vars": ["AWS_PROFILE"], "strict": False}, "model": "cohere.embed-english-v3", "input_type": "search_query", + "boto3_config": None, }, } @@ -76,6 +77,9 @@ def test_from_dict(self, mock_boto3_session): "aws_profile_name": {"type": "env_var", "env_vars": ["AWS_PROFILE"], "strict": False}, "model": "cohere.embed-english-v3", "input_type": "search_query", + "boto3_config": { + "read_timeout": 1000, + }, }, } @@ -83,6 +87,7 @@ def test_from_dict(self, mock_boto3_session): assert embedder.model == "cohere.embed-english-v3" assert embedder.kwargs == {"input_type": "search_query"} + assert embedder.boto3_config == {"read_timeout": 1000} def test_init_invalid_model(self): with pytest.raises(ValueError):