Skip to content

Commit

Permalink
Python: Improve Bedrock service setup docs (#10772)
Browse files Browse the repository at this point in the history
### Motivation and Context

<!-- Thank you for your contribution to the semantic-kernel repo!
Please help reviewers and future users, providing the following
information:
  1. Why is this change required?
  2. What problem does it solve?
  3. What scenario does it contribute to?
  4. If it fixes an open issue, please link to the issue here.
-->
Closing: #9910

### Description

<!-- Describe your changes, the overall approach, the underlying design.
These notes will help understanding how your code works. Thanks! -->
1. Add detailed instructions on how to setup one's environment to use
Bedrock services, including models and agents.
2. Re-enable bedrock integration tests.
3. Some other cleanups.

### Contribution Checklist

<!-- Before submitting this PR, please make sure: -->

- [x] The code builds clean without any errors or warnings
- [x] The PR follows the [SK Contribution
Guidelines](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md)
and the [pre-submission formatting
script](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md#development-scripts)
raises no violations
- [x] All unit tests pass, and I have added new tests where possible
- [x] I didn't break anyone 😄
  • Loading branch information
TaoChenOSU authored Mar 4, 2025
1 parent 75453a1 commit 8fd6da2
Showing 12 changed files with 122 additions and 33 deletions.
1 change: 1 addition & 0 deletions python/.cspell.json
Original file line number Diff line number Diff line change
@@ -51,6 +51,7 @@
"nopep",
"NOSQL",
"ollama",
"Onnx",
"onyourdatatest",
"OPENAI",
"opentelemetry",
31 changes: 31 additions & 0 deletions python/samples/concepts/agents/bedrock_agent/README.md
Original file line number Diff line number Diff line change
@@ -5,6 +5,37 @@
1. You need to have an AWS account and [access to the foundation models](https://docs.aws.amazon.com/bedrock/latest/userguide/model-access-permissions.html)
2. [AWS CLI installed](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) and [configured](https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html#configuration)

### Configuration

Follow this [guide](https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html#configuration) to configure your environment to use the Bedrock API.

Please configure the `aws_access_key_id`, `aws_secret_access_key`, and `region` otherwise you will need to create custom clients for the services. For example:

```python
runtime_client=boto.client(
"bedrock-runtime",
aws_access_key_id="your_access_key",
aws_secret_access_key="your_secret_key",
region_name="your_region",
[...other parameters you may need...]
)
client=boto.client(
"bedrock",
aws_access_key_id="your_access_key",
aws_secret_access_key="your_secret_key",
region_name="your_region",
[...other parameters you may need...]
)

bedrock_agent = BedrockAgent.create_and_prepare_agent(
name="your_agent_name",
instructions="your_instructions",
runtime_client=runtime_client,
client=client,
[...other parameters you may need...]
)
```

## Samples

| Sample | Description |
8 changes: 5 additions & 3 deletions python/samples/concepts/setup/ALL_SETTINGS.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
## AI Service Settings used across SK:
# Semantic Kernel Settings

## AI Service Settings used across SK

| Provider | Service | Constructor Settings | Environment Variable | Required? | Settings Class |
| --- | --- | --- | --- | --- | --- |
@@ -36,7 +38,7 @@
| Onnx | [OnnxGenAIChatCompletion](../../../semantic_kernel/connectors/ai/onnx/services/onnx_gen_ai_chat_completion.py) | template, <br> ai_model_path | N/A, <br> ONNX_GEN_AI_CHAT_MODEL_FOLDER | Yes, <br> Yes | [OnnxGenAISettings](../../../semantic_kernel/connectors/ai/onnx/onnx_gen_ai_settings.py) |
| | [OnnxGenAITextCompletion](../../../semantic_kernel/connectors/ai/onnx/services/onnx_gen_ai_text_completion.py) | ai_model_path | ONNX_GEN_AI_TEXT_MODEL_FOLDER | Yes | |

## Memory Service Settings used across SK:
## Memory Service Settings used across SK

| Provider | Service | Constructor Settings | Environment Variable | Required? | Settings Class |
| --- | --- | --- | --- | --- | --- |
@@ -49,7 +51,7 @@
| Redis | [RedisMemoryService](../../../semantic_kernel/connectors/memory/redis/redis_memory_store.py) | connection_string | REDIS_CONNECTION_STRING | Yes | [RedisSettings](../../../semantic_kernel/connectors/memory/redis/redis_settings.py) |
| Weaviate | [WeaviateMemoryService](../../../semantic_kernel/connectors/memory/weaviate/weaviate_memory_store.py) | url, <br> api_key, <br> use_embed | WEAVIATE_URL, <br> WEAVIATE_API_KEY, <br> WEAVIATE_USE_EMBED | No, <br> No, <br> No | [WeaviateSettings](../../../semantic_kernel/connectors/memory/weaviate/weaviate_settings.py) |

## Other settings used:
## Other settings used

| Provider | Service | Constructor Settings | Environment Variable | Required? | Settings Class |
| --- | --- | --- | --- | --- | --- |
2 changes: 1 addition & 1 deletion python/semantic_kernel/connectors/ai/README.md
Original file line number Diff line number Diff line change
@@ -3,14 +3,14 @@
This directory contains the implementation of the AI connectors (aka AI services) that are used to interact with AI models.

Depending on the modality, the AI connector can inherit from one of the following classes:

- [`ChatCompletionClientBase`](./chat_completion_client_base.py) for chat completion tasks.
- [`TextCompletionClientBase`](./text_completion_client_base.py) for text completion tasks.
- [`AudioToTextClientBase`](./audio_to_text_client_base.py) for audio to text tasks.
- [`TextToAudioClientBase`](./text_to_audio_client_base.py) for text to audio tasks.
- [`TextToImageClientBase`](./text_to_image_client_base.py) for text to image tasks.
- [`EmbeddingGeneratorBase`](./embeddings/embedding_generator_base.py) for text embedding tasks.


All base clients inherit from the [`AIServiceClientBase`](../../services/ai_service_client_base.py) class.

## Existing AI connectors
21 changes: 21 additions & 0 deletions python/semantic_kernel/connectors/ai/bedrock/README.md
Original file line number Diff line number Diff line change
@@ -11,6 +11,27 @@

Follow this [guide](https://boto3.amazonaws.com/v1/documentation/api/latest/guide/quickstart.html#configuration) to configure your environment to use the Bedrock API.

Please configure the `aws_access_key_id`, `aws_secret_access_key`, and `region` otherwise you will need to create custom clients for the services. For example:

```python
runtime_client=boto.client(
"bedrock-runtime",
aws_access_key_id="your_access_key",
aws_secret_access_key="your_secret_key",
region_name="your_region",
[...other parameters you may need...]
)
client=boto.client(
"bedrock",
aws_access_key_id="your_access_key",
aws_secret_access_key="your_secret_key",
region_name="your_region",
[...other parameters you may need...]
)

bedrock_chat_completion_service = BedrockChatCompletion(runtime_client=runtime_client, client=client)
```

## Supports

### Region
Original file line number Diff line number Diff line change
@@ -4,6 +4,8 @@
from functools import partial
from typing import Any, ClassVar

import boto3

from semantic_kernel.kernel_pydantic import KernelBaseModel
from semantic_kernel.utils.async_utils import run_in_executor

@@ -19,6 +21,26 @@ class BedrockBase(KernelBaseModel, ABC):
# Client: Use for model management
bedrock_client: Any

def __init__(
self,
*,
runtime_client: Any | None = None,
client: Any | None = None,
**kwargs: Any,
) -> None:
"""Initialize the Amazon Bedrock Base Class.
Args:
runtime_client: The Amazon Bedrock runtime client to use.
client: The Amazon Bedrock client to use.
**kwargs: Additional keyword arguments.
"""
super().__init__(
bedrock_runtime_client=runtime_client or boto3.client("bedrock-runtime"),
bedrock_client=client or boto3.client("bedrock"),
**kwargs,
)

async def get_foundation_model_info(self, model_id: str) -> dict[str, Any]:
"""Get the foundation model information."""
response = await run_in_executor(
Original file line number Diff line number Diff line change
@@ -5,8 +5,6 @@
from functools import partial
from typing import TYPE_CHECKING, Any, ClassVar

import boto3

if sys.version_info >= (3, 12):
from typing import override # pragma: no cover
else:
@@ -95,8 +93,8 @@ def __init__(
super().__init__(
ai_model_id=bedrock_settings.chat_model_id,
service_id=service_id or bedrock_settings.chat_model_id,
bedrock_runtime_client=runtime_client or boto3.client("bedrock-runtime"),
bedrock_client=client or boto3.client("bedrock"),
runtime_client=runtime_client,
client=client,
)

# region Overriding base class methods
Original file line number Diff line number Diff line change
@@ -6,7 +6,6 @@
from functools import partial
from typing import TYPE_CHECKING, Any

import boto3
from pydantic import ValidationError

if sys.version_info >= (3, 12):
@@ -73,8 +72,8 @@ def __init__(
super().__init__(
ai_model_id=bedrock_settings.text_model_id,
service_id=service_id or bedrock_settings.text_model_id,
bedrock_runtime_client=runtime_client or boto3.client("bedrock-runtime"),
bedrock_client=client or boto3.client("bedrock"),
runtime_client=runtime_client,
client=client,
)

# region Overriding base class methods
Original file line number Diff line number Diff line change
@@ -6,7 +6,6 @@
from functools import partial
from typing import TYPE_CHECKING, Any

import boto3
from numpy import array, ndarray
from pydantic import ValidationError

@@ -70,8 +69,8 @@ def __init__(
super().__init__(
ai_model_id=bedrock_settings.embedding_model_id,
service_id=service_id or bedrock_settings.embedding_model_id,
bedrock_runtime_client=runtime_client or boto3.client("bedrock-runtime"),
bedrock_client=client or boto3.client("bedrock"),
runtime_client=runtime_client,
client=client,
)

@override
28 changes: 19 additions & 9 deletions python/tests/integration/completions/chat_completion_test_base.py
Original file line number Diff line number Diff line change
@@ -67,8 +67,6 @@
["ONNX_GEN_AI_CHAT_MODEL_FOLDER"], raise_if_not_set=False
) # Tests are optional for ONNX
anthropic_setup: bool = is_service_setup_for_testing(["ANTHROPIC_API_KEY", "ANTHROPIC_CHAT_MODEL_ID"])
# When testing Bedrock, after logging into AWS CLI this has been set, so we can use it to check if the service is setup
bedrock_setup: bool = is_service_setup_for_testing(["AWS_DEFAULT_REGION"], raise_if_not_set=False)


# A mock plugin that contains a function that returns a complex object.
@@ -90,7 +88,9 @@ class ChatCompletionTestBase(CompletionTestBase):
"""Base class for testing completion services."""

@override
@pytest.fixture(scope="function")
@pytest.fixture(
scope="function"
) # This needs to be scoped to function to avoid resources getting cleaned up after each test
def services(self) -> dict[str, tuple[ServiceType | None, type[PromptExecutionSettings] | None]]:
azure_openai_setup = True
azure_openai_settings = AzureOpenAISettings.create()
@@ -152,27 +152,27 @@ def services(self) -> dict[str, tuple[ServiceType | None, type[PromptExecutionSe
OnnxGenAIPromptExecutionSettings,
),
"bedrock_amazon_titan": (
BedrockChatCompletion(model_id="amazon.titan-text-premier-v1:0") if bedrock_setup else None,
self._try_create_bedrock_chat_completion_client("amazon.titan-text-premier-v1:0"),
BedrockChatPromptExecutionSettings,
),
"bedrock_ai21labs": (
BedrockChatCompletion(model_id="ai21.jamba-1-5-mini-v1:0") if bedrock_setup else None,
self._try_create_bedrock_chat_completion_client("ai21.jamba-1-5-mini-v1:0"),
BedrockChatPromptExecutionSettings,
),
"bedrock_anthropic_claude": (
BedrockChatCompletion(model_id="anthropic.claude-3-5-sonnet-20240620-v1:0") if bedrock_setup else None,
self._try_create_bedrock_chat_completion_client("anthropic.claude-3-5-sonnet-20240620-v1:0"),
BedrockChatPromptExecutionSettings,
),
"bedrock_cohere_command": (
BedrockChatCompletion(model_id="cohere.command-r-v1:0") if bedrock_setup else None,
self._try_create_bedrock_chat_completion_client("cohere.command-r-v1:0"),
BedrockChatPromptExecutionSettings,
),
"bedrock_meta_llama": (
BedrockChatCompletion(model_id="meta.llama3-70b-instruct-v1:0") if bedrock_setup else None,
self._try_create_bedrock_chat_completion_client("meta.llama3-70b-instruct-v1:0"),
BedrockChatPromptExecutionSettings,
),
"bedrock_mistralai": (
BedrockChatCompletion(model_id="mistral.mistral-small-2402-v1:0") if bedrock_setup else None,
self._try_create_bedrock_chat_completion_client("mistral.mistral-small-2402-v1:0"),
BedrockChatPromptExecutionSettings,
),
}
@@ -218,3 +218,13 @@ async def get_chat_completion_response(
if parts:
return sum(parts[1:], parts[0])
raise AssertionError("No response")

def _try_create_bedrock_chat_completion_client(self, model_id: str) -> BedrockChatCompletion | None:
try:
return BedrockChatCompletion(model_id=model_id)
except Exception as ex:
from conftest import logger

logger.warning(ex)
# Returning None so that the test that uses this service will be skipped
return None
2 changes: 0 additions & 2 deletions python/tests/integration/completions/test_chat_completions.py
Original file line number Diff line number Diff line change
@@ -18,7 +18,6 @@
from tests.integration.completions.chat_completion_test_base import (
ChatCompletionTestBase,
anthropic_setup,
bedrock_setup,
mistral_ai_setup,
ollama_setup,
onnx_setup,
@@ -190,7 +189,6 @@ class Reasoning(KernelBaseModel):
ChatMessageContent(role=AuthorRole.USER, items=[TextContent(text="How are you today?")]),
],
{},
marks=pytest.mark.skipif(not bedrock_setup, reason="Bedrock Environment Variables not set"),
id="bedrock_amazon_titan_text_input",
),
pytest.param(
24 changes: 16 additions & 8 deletions python/tests/integration/completions/test_text_completion.py
Original file line number Diff line number Diff line change
@@ -45,7 +45,6 @@
onnx_setup: bool = is_service_setup_for_testing(
["ONNX_GEN_AI_TEXT_MODEL_FOLDER"], raise_if_not_set=False
) # Tests are optional for ONNX
bedrock_setup = is_service_setup_for_testing(["AWS_DEFAULT_REGION"], raise_if_not_set=False)

pytestmark = pytest.mark.parametrize(
"service_id, execution_settings_kwargs, inputs, kwargs",
@@ -138,7 +137,6 @@
{},
["Repeat the word Hello once"],
{},
marks=pytest.mark.skipif(not bedrock_setup, reason="Not setup"),
id="bedrock_amazon_titan_text_completion",
),
pytest.param(
@@ -255,27 +253,27 @@ def services(self) -> dict[str, tuple[ServiceType | None, type[PromptExecutionSe
# Amazon Bedrock supports models from multiple providers but requests to and responses from the models are
# inconsistent. So we need to test each model separately.
"bedrock_amazon_titan": (
BedrockTextCompletion(model_id="amazon.titan-text-premier-v1:0") if bedrock_setup else None,
self._try_create_bedrock_text_completion_client("amazon.titan-text-premier-v1:0"),
BedrockTextPromptExecutionSettings,
),
"bedrock_anthropic_claude": (
BedrockTextCompletion(model_id="anthropic.claude-v2") if bedrock_setup else None,
self._try_create_bedrock_text_completion_client("anthropic.claude-v2"),
BedrockTextPromptExecutionSettings,
),
"bedrock_cohere_command": (
BedrockTextCompletion(model_id="cohere.command-text-v14") if bedrock_setup else None,
self._try_create_bedrock_text_completion_client("cohere.command-text-v14"),
BedrockTextPromptExecutionSettings,
),
"bedrock_ai21labs": (
BedrockTextCompletion(model_id="ai21.j2-mid-v1") if bedrock_setup else None,
self._try_create_bedrock_text_completion_client("ai21.j2-mid-v1"),
BedrockTextPromptExecutionSettings,
),
"bedrock_meta_llama": (
BedrockTextCompletion(model_id="meta.llama3-70b-instruct-v1:0") if bedrock_setup else None,
self._try_create_bedrock_text_completion_client("meta.llama3-70b-instruct-v1:0"),
BedrockTextPromptExecutionSettings,
),
"bedrock_mistralai": (
BedrockTextCompletion(model_id="mistral.mistral-7b-instruct-v0:2") if bedrock_setup else None,
self._try_create_bedrock_text_completion_client("mistral.mistral-7b-instruct-v0:2"),
BedrockTextPromptExecutionSettings,
),
}
@@ -373,3 +371,13 @@ async def _test_helper(
name="text completions",
)
self.evaluate(response)

def _try_create_bedrock_text_completion_client(self, model_id: str) -> BedrockTextCompletion | None:
try:
return BedrockTextCompletion(model_id=model_id)
except Exception as ex:
from conftest import logger

logger.warning(ex)
# Returning None so that the test that uses this service will be skipped
return None

0 comments on commit 8fd6da2

Please sign in to comment.