chore: import upstream snapshot with attribution
CodeQL / Analyze (csharp) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:21:23 +08:00
commit b957a53def
5423 changed files with 863745 additions and 0 deletions
@@ -0,0 +1,52 @@
# Copyright (c) Microsoft. All rights reserved.
from abc import ABC
from typing import Any, ClassVar
import boto3
from botocore.config import Config
from semantic_kernel.connectors.ai.bedrock.services.model_provider.bedrock_model_provider import BedrockModelProvider
from semantic_kernel.kernel_pydantic import KernelBaseModel
class BedrockBase(KernelBaseModel, ABC):
"""Amazon Bedrock Service Base Class."""
MODEL_PROVIDER_NAME: ClassVar[str] = "bedrock"
# Amazon Bedrock Clients
# Runtime Client: Use for inference
bedrock_runtime_client: Any
# Client: Use for model management
bedrock_client: Any
bedrock_model_provider: BedrockModelProvider | None = None
def __init__(
self,
*,
runtime_client: Any | None = None,
client: Any | None = None,
bedrock_model_provider: BedrockModelProvider | 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.
bedrock_model_provider: The Bedrock model provider to use.
If not provided, the model provider will be extracted from the model ID.
When using an Application Inference Profile where the model provider is not part
of the model ID, this setting must be provided.
**kwargs: Additional keyword arguments.
"""
config = Config(user_agent_extra="x-client-framework:semantic-kernel")
super().__init__(
bedrock_runtime_client=runtime_client or boto3.client("bedrock-runtime", config=config),
bedrock_client=client or boto3.client("bedrock"),
bedrock_model_provider=bedrock_model_provider,
**kwargs,
)
@@ -0,0 +1,401 @@
# Copyright (c) Microsoft. All rights reserved.
import sys
from collections.abc import AsyncGenerator, Callable
from functools import partial
from typing import TYPE_CHECKING, Any, ClassVar
if sys.version_info >= (3, 12):
from typing import override # pragma: no cover
else:
from typing_extensions import override # pragma: no cover
from pydantic import ValidationError
from semantic_kernel.connectors.ai.bedrock.bedrock_prompt_execution_settings import BedrockChatPromptExecutionSettings
from semantic_kernel.connectors.ai.bedrock.bedrock_settings import BedrockSettings
from semantic_kernel.connectors.ai.bedrock.services.bedrock_base import BedrockBase
from semantic_kernel.connectors.ai.bedrock.services.model_provider.bedrock_model_provider import (
BedrockModelProvider,
get_chat_completion_additional_model_request_fields,
)
from semantic_kernel.connectors.ai.bedrock.services.model_provider.utils import (
MESSAGE_CONVERTERS,
finish_reason_from_bedrock_to_semantic_kernel,
remove_none_recursively,
update_settings_from_function_choice_configuration,
)
from semantic_kernel.connectors.ai.chat_completion_client_base import ChatCompletionClientBase
from semantic_kernel.connectors.ai.completion_usage import CompletionUsage
from semantic_kernel.connectors.ai.function_choice_behavior import FunctionChoiceType
from semantic_kernel.contents.chat_message_content import CMC_ITEM_TYPES, ChatMessageContent
from semantic_kernel.contents.function_call_content import FunctionCallContent
from semantic_kernel.contents.image_content import ImageContent
from semantic_kernel.contents.streaming_chat_message_content import STREAMING_CMC_ITEM_TYPES as STREAMING_ITEM_TYPES
from semantic_kernel.contents.streaming_chat_message_content import StreamingChatMessageContent
from semantic_kernel.contents.streaming_text_content import StreamingTextContent
from semantic_kernel.contents.text_content import TextContent
from semantic_kernel.contents.utils.author_role import AuthorRole
from semantic_kernel.contents.utils.finish_reason import FinishReason
from semantic_kernel.exceptions.service_exceptions import ServiceInitializationError, ServiceInvalidResponseError
from semantic_kernel.utils.async_utils import run_in_executor
from semantic_kernel.utils.telemetry.model_diagnostics.decorators import (
trace_chat_completion,
trace_streaming_chat_completion,
)
if TYPE_CHECKING:
from semantic_kernel.connectors.ai.function_call_choice_configuration import FunctionCallChoiceConfiguration
from semantic_kernel.connectors.ai.prompt_execution_settings import PromptExecutionSettings
from semantic_kernel.contents.chat_history import ChatHistory
class BedrockChatCompletion(BedrockBase, ChatCompletionClientBase):
"""Amazon Bedrock Chat Completion Service."""
SUPPORTS_FUNCTION_CALLING: ClassVar[bool] = True
def __init__(
self,
model_id: str | None = None,
model_provider: BedrockModelProvider | None = None,
service_id: str | None = None,
runtime_client: Any | None = None,
client: Any | None = None,
env_file_path: str | None = None,
env_file_encoding: str | None = None,
) -> None:
"""Initialize the Amazon Bedrock Chat Completion Service.
Args:
model_id: The Amazon Bedrock chat model ID to use.
model_provider: The Bedrock model provider to use.
service_id: The Service ID for the completion service.
runtime_client: The Amazon Bedrock runtime client to use.
client: The Amazon Bedrock client to use.
env_file_path: The path to the .env file.
env_file_encoding: The encoding of the .env file.
"""
try:
bedrock_settings = BedrockSettings(
chat_model_id=model_id,
model_provider=model_provider,
env_file_path=env_file_path,
env_file_encoding=env_file_encoding,
)
except ValidationError as e:
raise ServiceInitializationError("Failed to initialize the Amazon Bedrock Chat Completion Service.") from e
if bedrock_settings.chat_model_id is None:
raise ServiceInitializationError("The Amazon Bedrock Chat Model ID is missing.")
super().__init__(
ai_model_id=bedrock_settings.chat_model_id,
service_id=service_id or bedrock_settings.chat_model_id,
runtime_client=runtime_client,
client=client,
bedrock_model_provider=bedrock_settings.model_provider,
)
# region Overriding base class methods
# Override from AIServiceClientBase
@override
def get_prompt_execution_settings_class(self) -> type["PromptExecutionSettings"]:
return BedrockChatPromptExecutionSettings
@override
@trace_chat_completion(BedrockBase.MODEL_PROVIDER_NAME)
async def _inner_get_chat_message_contents(
self,
chat_history: "ChatHistory",
settings: "PromptExecutionSettings",
) -> list["ChatMessageContent"]:
if not isinstance(settings, BedrockChatPromptExecutionSettings):
settings = self.get_prompt_execution_settings_from_settings(settings)
assert isinstance(settings, BedrockChatPromptExecutionSettings) # nosec
prepared_settings = self._prepare_settings_for_request(chat_history, settings)
response = await self._async_converse(**prepared_settings)
return [self._create_chat_message_content(response)]
@override
@trace_streaming_chat_completion(BedrockBase.MODEL_PROVIDER_NAME)
async def _inner_get_streaming_chat_message_contents(
self,
chat_history: "ChatHistory",
settings: "PromptExecutionSettings",
function_invoke_attempt: int = 0,
) -> AsyncGenerator[list["StreamingChatMessageContent"], Any]:
if not isinstance(settings, BedrockChatPromptExecutionSettings):
settings = self.get_prompt_execution_settings_from_settings(settings)
assert isinstance(settings, BedrockChatPromptExecutionSettings) # nosec
prepared_settings = self._prepare_settings_for_request(chat_history, settings)
response_stream = await self._async_converse_streaming(**prepared_settings)
for event in response_stream.get("stream"):
if "messageStart" in event:
yield [self._parse_message_start_event(event)]
elif "contentBlockStart" in event:
yield [self._parse_content_block_start_event(event)]
elif "contentBlockDelta" in event:
yield [self._parse_content_block_delta_event(event, function_invoke_attempt)]
elif "contentBlockStop" in event:
continue
elif "messageStop" in event:
yield [self._parse_message_stop_event(event)]
elif "metadata" in event:
yield [self._parse_metadata_event(event)]
else:
raise ServiceInvalidResponseError(f"Unknown event type in the response: {event}")
@override
def _update_function_choice_settings_callback(
self,
) -> Callable[["FunctionCallChoiceConfiguration", "PromptExecutionSettings", FunctionChoiceType], None]:
return update_settings_from_function_choice_configuration
@override
def _reset_function_choice_settings(self, settings: "PromptExecutionSettings") -> None:
if hasattr(settings, "tool_choice"):
settings.tool_choice = None
if hasattr(settings, "tools"):
settings.tools = None
@override
def _prepare_chat_history_for_request(
self,
chat_history: "ChatHistory",
role_key: str = "role",
content_key: str = "content",
) -> Any:
messages: list[dict[str, Any]] = []
for message in chat_history.messages:
if message.role == AuthorRole.SYSTEM:
continue
messages.append(MESSAGE_CONVERTERS[message.role](message))
return messages
# endregion
def _prepare_system_messages_for_request(self, chat_history: "ChatHistory") -> Any:
messages: list[dict[str, Any]] = []
for message in chat_history.messages:
if message.role != AuthorRole.SYSTEM:
continue
messages.append(MESSAGE_CONVERTERS[message.role](message))
return messages
def _prepare_settings_for_request(
self,
chat_history: "ChatHistory",
settings: "BedrockChatPromptExecutionSettings",
) -> dict[str, Any]:
"""Prepare the settings for the request.
Settings are prepared based on the syntax shown here:
https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/bedrock-runtime/client/converse.html
Note that Guardrails are not supported.
"""
prepared_settings = {
"modelId": self.ai_model_id,
"messages": self._prepare_chat_history_for_request(chat_history),
"system": self._prepare_system_messages_for_request(chat_history),
"inferenceConfig": remove_none_recursively({
"maxTokens": settings.max_tokens,
"temperature": settings.temperature,
"topP": settings.top_p,
"stopSequences": settings.stop,
}),
"additionalModelRequestFields": get_chat_completion_additional_model_request_fields(
self.ai_model_id, settings, model_provider=self.bedrock_model_provider
),
}
if settings.tools and settings.tool_choice:
prepared_settings["toolConfig"] = {
"tools": settings.tools,
"toolChoice": settings.tool_choice,
}
return prepared_settings
def _create_chat_message_content(self, response: dict[str, Any]) -> ChatMessageContent:
"""Create a chat message content object."""
finish_reason: FinishReason | None = finish_reason_from_bedrock_to_semantic_kernel(response["stopReason"])
usage = CompletionUsage(
prompt_tokens=response["usage"]["inputTokens"],
completion_tokens=response["usage"]["outputTokens"],
)
items: list[CMC_ITEM_TYPES] = []
for content in response["output"]["message"]["content"]:
if "text" in content:
items.append(TextContent(text=content["text"], inner_content=content))
elif "image" in content:
items.append(
ImageContent(
data=content["image"]["source"]["bytes"],
mime_type=content["image"]["source"]["format"],
inner_content=content["image"],
)
)
elif "toolUse" in content:
items.append(
FunctionCallContent(
id=content["toolUse"]["toolUseId"],
name=content["toolUse"]["name"],
arguments=content["toolUse"]["input"],
)
)
else:
raise ServiceInvalidResponseError(f"Unsupported content type in the response: {content}")
return ChatMessageContent(
ai_model_id=self.ai_model_id,
role=AuthorRole.ASSISTANT,
items=items,
inner_content=response,
finish_reason=finish_reason,
metadata={"usage": usage},
)
# region async helper methods
async def _async_converse(self, **kwargs) -> Any:
"""Invoke the model asynchronously."""
return await run_in_executor(
None,
partial(
self.bedrock_runtime_client.converse,
**kwargs,
),
)
async def _async_converse_streaming(self, **kwargs) -> Any:
"""Invoke the model asynchronously."""
return await run_in_executor(
None,
partial(
self.bedrock_runtime_client.converse_stream,
**kwargs,
),
)
# endregion
# region streaming event parsing methods
def _parse_message_start_event(self, event: dict[str, Any]) -> StreamingChatMessageContent:
"""Parse the message start event.
The message start event contains the role of the message.
https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_MessageStartEvent.html
"""
return StreamingChatMessageContent(
ai_model_id=self.ai_model_id,
role=AuthorRole(event["messageStart"]["role"]),
items=[],
choice_index=0,
inner_content=event,
)
def _parse_content_block_start_event(self, event: dict[str, Any]) -> StreamingChatMessageContent:
"""Parse the content block start event.
The content block start event contains tool information.
https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_ContentBlockStartEvent.html
"""
items: list[STREAMING_ITEM_TYPES] = []
if "toolUse" in event["contentBlockStart"]["start"]:
items.append(
FunctionCallContent(
id=event["contentBlockStart"]["start"]["toolUse"]["toolUseId"],
name=event["contentBlockStart"]["start"]["toolUse"]["name"],
index=event["contentBlockStart"]["contentBlockIndex"],
)
)
return StreamingChatMessageContent(
ai_model_id=self.ai_model_id,
role=AuthorRole.ASSISTANT, # Assume the role is always assistant
items=items,
choice_index=0,
inner_content=event,
)
def _parse_content_block_delta_event(
self, event: dict[str, Any], function_invoke_attempt: int
) -> StreamingChatMessageContent:
"""Parse the content block delta event.
The content block delta event contains the completion.
https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_ContentBlockDeltaEvent.html
"""
items: list[STREAMING_ITEM_TYPES] = [
StreamingTextContent(
choice_index=0,
text=event["contentBlockDelta"]["delta"]["text"],
inner_content=event,
)
if "text" in event["contentBlockDelta"]["delta"]
else FunctionCallContent(
arguments=event["contentBlockDelta"]["delta"]["toolUse"]["input"],
inner_content=event,
index=event["contentBlockDelta"]["contentBlockIndex"],
)
]
return StreamingChatMessageContent(
ai_model_id=self.ai_model_id,
role=AuthorRole.ASSISTANT, # Assume the role is always assistant
items=items,
choice_index=0,
inner_content=event,
function_invoke_attempt=function_invoke_attempt,
)
def _parse_message_stop_event(self, event: dict[str, Any]) -> StreamingChatMessageContent:
"""Parse the message stop event.
The message stop event contains the finish reason.
https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_MessageStopEvent.html
"""
metadata = event["messageStop"].get("additionalModelResponseFields", {})
return StreamingChatMessageContent(
ai_model_id=self.ai_model_id,
role=AuthorRole.ASSISTANT, # Assume the role is always assistant
items=[],
choice_index=0,
inner_content=event,
finish_reason=finish_reason_from_bedrock_to_semantic_kernel(event["messageStop"]["stopReason"]),
metadata=metadata,
)
def _parse_metadata_event(self, event: dict[str, Any]) -> StreamingChatMessageContent:
"""Parse the metadata event.
The metadata event contains additional information.
https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_ConverseStreamMetadataEvent.html
"""
usage = CompletionUsage(
prompt_tokens=event["metadata"]["usage"]["inputTokens"],
completion_tokens=event["metadata"]["usage"]["outputTokens"],
)
return StreamingChatMessageContent(
ai_model_id=self.ai_model_id,
role=AuthorRole.ASSISTANT, # Assume the role is always assistant
items=[],
choice_index=0,
inner_content=event,
metadata={"usage": usage},
)
# endregion
@@ -0,0 +1,169 @@
# Copyright (c) Microsoft. All rights reserved.
import json
import sys
from collections.abc import AsyncGenerator
from functools import partial
from typing import TYPE_CHECKING, Any
from pydantic import ValidationError
if sys.version_info >= (3, 12):
from typing import override # pragma: no cover
else:
from typing_extensions import override # pragma: no cover
from semantic_kernel.connectors.ai.bedrock.bedrock_prompt_execution_settings import BedrockTextPromptExecutionSettings
from semantic_kernel.connectors.ai.bedrock.bedrock_settings import BedrockSettings
from semantic_kernel.connectors.ai.bedrock.services.bedrock_base import BedrockBase
from semantic_kernel.connectors.ai.bedrock.services.model_provider.bedrock_model_provider import (
BedrockModelProvider,
get_text_completion_request_body,
parse_streaming_text_completion_response,
parse_text_completion_response,
)
from semantic_kernel.connectors.ai.text_completion_client_base import TextCompletionClientBase
from semantic_kernel.contents.streaming_text_content import StreamingTextContent
from semantic_kernel.contents.text_content import TextContent
from semantic_kernel.exceptions.service_exceptions import ServiceInitializationError
from semantic_kernel.utils.async_utils import run_in_executor
from semantic_kernel.utils.telemetry.model_diagnostics.decorators import (
trace_streaming_text_completion,
trace_text_completion,
)
if TYPE_CHECKING:
from semantic_kernel.connectors.ai.prompt_execution_settings import PromptExecutionSettings
class BedrockTextCompletion(BedrockBase, TextCompletionClientBase):
"""Amazon Bedrock Text Completion Service."""
def __init__(
self,
model_id: str | None = None,
model_provider: BedrockModelProvider | None = None,
service_id: str | None = None,
runtime_client: Any | None = None,
client: Any | None = None,
env_file_path: str | None = None,
env_file_encoding: str | None = None,
) -> None:
"""Initialize the Amazon Bedrock Text Completion Service.
Args:
model_id: The Amazon Bedrock text model ID to use.
model_provider: The Bedrock model provider to use.
service_id: The Service ID for the text completion service.
runtime_client: The Amazon Bedrock runtime client to use.
client: The Amazon Bedrock client to use.
env_file_path: The path to the .env file to load settings from.
env_file_encoding: The encoding of the .env file.
"""
try:
bedrock_settings = BedrockSettings(
text_model_id=model_id,
model_provider=model_provider,
env_file_path=env_file_path,
env_file_encoding=env_file_encoding,
)
except ValidationError as e:
raise ServiceInitializationError("Failed to initialize the Amazon Bedrock Text Completion Service.") from e
if bedrock_settings.text_model_id is None:
raise ServiceInitializationError("The Amazon Bedrock Text Model ID is missing.")
super().__init__(
ai_model_id=bedrock_settings.text_model_id,
service_id=service_id or bedrock_settings.text_model_id,
runtime_client=runtime_client,
client=client,
bedrock_model_provider=bedrock_settings.model_provider,
)
# region Overriding base class methods
# Override from AIServiceClientBase
@override
def get_prompt_execution_settings_class(self) -> type["PromptExecutionSettings"]:
return BedrockTextPromptExecutionSettings
@override
@trace_text_completion(BedrockBase.MODEL_PROVIDER_NAME)
async def _inner_get_text_contents(
self,
prompt: str,
settings: "PromptExecutionSettings",
) -> list[TextContent]:
if not isinstance(settings, BedrockTextPromptExecutionSettings):
settings = self.get_prompt_execution_settings_from_settings(settings)
assert isinstance(settings, BedrockTextPromptExecutionSettings) # nosec
request_body = get_text_completion_request_body(
self.ai_model_id,
prompt,
settings,
model_provider=self.bedrock_model_provider,
)
response_body = await self._async_invoke_model(request_body)
return parse_text_completion_response(
self.ai_model_id,
json.loads(response_body.get("body").read()),
model_provider=self.bedrock_model_provider,
)
@override
@trace_streaming_text_completion(BedrockBase.MODEL_PROVIDER_NAME)
async def _inner_get_streaming_text_contents(
self,
prompt: str,
settings: "PromptExecutionSettings",
) -> AsyncGenerator[list[StreamingTextContent], Any]:
if not isinstance(settings, BedrockTextPromptExecutionSettings):
settings = self.get_prompt_execution_settings_from_settings(settings)
assert isinstance(settings, BedrockTextPromptExecutionSettings) # nosec
request_body = get_text_completion_request_body(
self.ai_model_id,
prompt,
settings,
model_provider=self.bedrock_model_provider,
)
response_stream = await self._async_invoke_model_stream(request_body)
for event in response_stream.get("body"):
chunk = event.get("chunk")
yield [
parse_streaming_text_completion_response(
self.ai_model_id,
json.loads(chunk.get("bytes").decode()),
model_provider=self.bedrock_model_provider,
)
]
# endregion
async def _async_invoke_model(self, request_body: dict) -> Any:
"""Invoke the model asynchronously."""
return await run_in_executor(
None,
partial(
self.bedrock_runtime_client.invoke_model,
body=json.dumps(request_body),
modelId=self.ai_model_id,
accept="application/json",
contentType="application/json",
),
)
async def _async_invoke_model_stream(self, request_body: dict) -> Any:
"""Invoke the model asynchronously and return a response stream."""
return await run_in_executor(
None,
partial(
self.bedrock_runtime_client.invoke_model_with_response_stream,
body=json.dumps(request_body),
modelId=self.ai_model_id,
accept="application/json",
contentType="application/json",
),
)
@@ -0,0 +1,135 @@
# Copyright (c) Microsoft. All rights reserved.
import asyncio
import json
import sys
from functools import partial
from typing import TYPE_CHECKING, Any
from numpy import array, ndarray
from pydantic import ValidationError
if sys.version_info >= (3, 12):
from typing import override # pragma: no cover
else:
from typing_extensions import override # pragma: no cover
from semantic_kernel.connectors.ai.bedrock.bedrock_prompt_execution_settings import (
BedrockEmbeddingPromptExecutionSettings,
)
from semantic_kernel.connectors.ai.bedrock.bedrock_settings import BedrockSettings
from semantic_kernel.connectors.ai.bedrock.services.bedrock_base import BedrockBase
from semantic_kernel.connectors.ai.bedrock.services.model_provider.bedrock_model_provider import (
BedrockModelProvider,
get_text_embedding_request_body,
parse_text_embedding_response,
)
from semantic_kernel.connectors.ai.embedding_generator_base import EmbeddingGeneratorBase
from semantic_kernel.connectors.ai.prompt_execution_settings import PromptExecutionSettings
from semantic_kernel.exceptions.service_exceptions import ServiceInitializationError
from semantic_kernel.utils.async_utils import run_in_executor
if TYPE_CHECKING:
pass
class BedrockTextEmbedding(BedrockBase, EmbeddingGeneratorBase):
"""Amazon Bedrock Text Embedding Service."""
def __init__(
self,
model_id: str | None = None,
model_provider: BedrockModelProvider | None = None,
service_id: str | None = None,
runtime_client: Any | None = None,
client: Any | None = None,
env_file_path: str | None = None,
env_file_encoding: str | None = None,
) -> None:
"""Initialize the Amazon Bedrock Text Embedding Service.
Args:
model_id: The Amazon Bedrock text embedding model ID to use.
model_provider: The Bedrock model provider to use.
service_id: The Service ID for the text embedding service.
runtime_client: The Amazon Bedrock runtime client to use.
client: The Amazon Bedrock client to use.
env_file_path: The path to the .env file to load settings from.
env_file_encoding: The encoding of the .env file.
"""
try:
bedrock_settings = BedrockSettings(
embedding_model_id=model_id,
model_provider=model_provider,
env_file_path=env_file_path,
env_file_encoding=env_file_encoding,
)
except ValidationError as e:
raise ServiceInitializationError("Failed to initialize the Amazon Bedrock Text Embedding Service.") from e
if bedrock_settings.embedding_model_id is None:
raise ServiceInitializationError("The Amazon Bedrock Text Embedding Model ID is missing.")
super().__init__(
ai_model_id=bedrock_settings.embedding_model_id,
service_id=service_id or bedrock_settings.embedding_model_id,
runtime_client=runtime_client,
client=client,
bedrock_model_provider=bedrock_settings.model_provider,
)
@override
async def generate_embeddings(
self,
texts: list[str],
settings: "PromptExecutionSettings | None" = None,
**kwargs: Any,
) -> ndarray:
if not settings:
settings = BedrockEmbeddingPromptExecutionSettings()
elif not isinstance(settings, BedrockEmbeddingPromptExecutionSettings):
settings = self.get_prompt_execution_settings_from_settings(settings)
assert isinstance(settings, BedrockEmbeddingPromptExecutionSettings) # nosec
results = await asyncio.gather(*[
self._async_invoke_model(
get_text_embedding_request_body(
self.ai_model_id,
text,
settings,
model_provider=self.bedrock_model_provider,
)
)
for text in texts
])
return array([
array(
parse_text_embedding_response(
self.ai_model_id,
json.loads(result.get("body").read()),
model_provider=self.bedrock_model_provider,
)
)
for result in results
])
@override
def get_prompt_execution_settings_class(
self,
) -> type["PromptExecutionSettings"]:
"""Get the request settings class."""
return BedrockEmbeddingPromptExecutionSettings
async def _async_invoke_model(self, request_body: dict) -> Any:
"""Invoke the model asynchronously."""
return await run_in_executor(
None,
partial(
self.bedrock_runtime_client.invoke_model,
body=json.dumps(request_body),
modelId=self.ai_model_id,
accept="application/json",
contentType="application/json",
),
)
@@ -0,0 +1,70 @@
# Copyright (c) Microsoft. All rights reserved.
from typing import Any
from semantic_kernel.connectors.ai.bedrock.bedrock_prompt_execution_settings import (
BedrockChatPromptExecutionSettings,
BedrockTextPromptExecutionSettings,
)
from semantic_kernel.connectors.ai.bedrock.services.model_provider.utils import remove_none_recursively
from semantic_kernel.contents.text_content import TextContent
# region Text Completion
def get_text_completion_request_body(prompt: str, settings: BedrockTextPromptExecutionSettings) -> Any:
"""Get the request body for text completion for AI21 Labs models.
https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-jurassic2.html
"""
return remove_none_recursively({
"prompt": prompt,
"temperature": settings.temperature,
"topP": settings.top_p,
"maxTokens": settings.max_tokens,
"stopSequences": settings.stop,
# Extension data
"countPenalty": settings.extension_data.get("countPenalty", None),
"presencePenalty": settings.extension_data.get("presencePenalty", None),
"frequencyPenalty": settings.extension_data.get("frequencyPenalty", None),
})
def parse_text_completion_response(response: dict[str, Any], model_id: str) -> list[TextContent]:
"""Parse the response from text completion for AI21 Labs models."""
return [
TextContent(
ai_model_id=model_id,
text=completion["data"]["text"],
inner_content=completion,
)
for completion in response.get("completions", [])
]
# endregion
# region Chat Completion
def get_chat_completion_additional_model_request_fields(
settings: BedrockChatPromptExecutionSettings,
) -> dict[str, Any] | None:
"""Get the additional model request fields for chat completion for AI21 Labs models.
https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-jamba.html
Note: We are not supporting multiple responses for now.
"""
additional_fields: dict[str, Any] = remove_none_recursively({
"frequency_penalty": settings.extension_data.get("frequency_penalty", None),
"presence_penalty": settings.extension_data.get("presence_penalty", None),
})
if not additional_fields:
return None
return additional_fields
# endregion
@@ -0,0 +1,113 @@
# Copyright (c) Microsoft. All rights reserved.
from typing import Any
from semantic_kernel.connectors.ai.bedrock.bedrock_prompt_execution_settings import (
BedrockChatPromptExecutionSettings,
BedrockEmbeddingPromptExecutionSettings,
BedrockTextPromptExecutionSettings,
)
from semantic_kernel.connectors.ai.bedrock.services.model_provider.utils import remove_none_recursively
from semantic_kernel.connectors.ai.completion_usage import CompletionUsage
from semantic_kernel.contents.streaming_text_content import StreamingTextContent
from semantic_kernel.contents.text_content import TextContent
from semantic_kernel.exceptions.service_exceptions import ServiceInvalidResponseError
# region Text Completion
def get_text_completion_request_body(prompt: str, settings: BedrockTextPromptExecutionSettings) -> Any:
"""Get the request body for text completion for Amazon Titan models.
https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-titan-text.html
"""
return remove_none_recursively({
"inputText": prompt,
"textGenerationConfig": {
"temperature": settings.temperature,
"topP": settings.top_p,
"maxTokenCount": settings.max_tokens,
"stopSequences": settings.stop,
},
})
def parse_text_completion_response(response: dict[str, Any], model_id: str) -> list[TextContent]:
"""Parse the response from text completion for Amazon Titan models."""
prompt_tokens = response.get("inputTextTokenCount")
return [
TextContent(
ai_model_id=model_id,
text=completion["outputText"],
inner_content=completion,
metadata={
"usage": CompletionUsage(
prompt_tokens=prompt_tokens,
completion_tokens=response.get("tokenCount"),
)
},
)
for completion in response.get("results", [])
if "outputText" in completion
]
def parse_streaming_text_completion_response(chunk: dict[str, Any], model_id: str) -> StreamingTextContent:
"""Parse the response from streaming text completion for Amazon Titan models."""
return StreamingTextContent(
choice_index=0,
ai_model_id=model_id,
text=chunk["outputText"],
inner_content=chunk,
metadata={
"usage": CompletionUsage(
prompt_tokens=chunk.get("inputTextTokenCount"),
completion_tokens=chunk.get("totalOutputTextTokenCount"),
)
},
)
# endregion
# region Chat Completion
def get_chat_completion_additional_model_request_fields(
settings: BedrockChatPromptExecutionSettings,
) -> dict[str, Any] | None:
"""Get the additional model request fields for chat completion for Amazon Titan models.
Amazon Titan models do not support additional model request fields.
https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-titan-text.html
"""
return None
# endregion
# region Text Embedding
def get_text_embedding_request_body(text: str, settings: BedrockEmbeddingPromptExecutionSettings) -> dict[str, Any]:
"""Get the request body for text embedding for Amazon Titan models."""
return remove_none_recursively({
"inputText": text,
# Extension data: https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-titan-embed-text.html
"dimensions": settings.extension_data.get("dimensions", None),
"normalize": settings.extension_data.get("normalize", None),
"embeddingTypes": settings.extension_data.get("embeddingTypes", None),
# Extension data: https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-titan-embed-mm.html
"embeddingConfig": settings.extension_data.get("embeddingConfig", None),
})
def parse_text_embedding_response(response: dict[str, Any]) -> list[float]:
"""Parse the response from text embedding for Amazon Titan models."""
if "embedding" not in response or not isinstance(response["embedding"], list):
raise ServiceInvalidResponseError("The response from Amazon Titan model does not contain embeddings.")
return response.get("embedding") # type: ignore
# endregion
@@ -0,0 +1,59 @@
# Copyright (c) Microsoft. All rights reserved.
from typing import Any
from semantic_kernel.connectors.ai.bedrock.bedrock_prompt_execution_settings import (
BedrockChatPromptExecutionSettings,
BedrockTextPromptExecutionSettings,
)
from semantic_kernel.connectors.ai.bedrock.services.model_provider.utils import remove_none_recursively
from semantic_kernel.contents.text_content import TextContent
# region Text Completion
def get_text_completion_request_body(prompt: str, settings: BedrockTextPromptExecutionSettings) -> Any:
"""Get the request body for text completion for Anthropic Claude models.
https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-anthropic-claude-text-completion.html
"""
return remove_none_recursively({
"prompt": f"\n\nHuman:{prompt}\n\nAssistant:",
"temperature": settings.temperature,
"top_p": settings.top_p,
"top_k": settings.top_k,
"max_tokens_to_sample": settings.max_tokens or 200,
"stop_sequences": settings.stop,
})
def parse_text_completion_response(response: dict[str, Any], model_id: str) -> list[TextContent]:
"""Parse the response from text completion for Anthropic Claude models."""
return [
TextContent(
ai_model_id=model_id,
text=response.get("completion", ""),
inner_content=response,
)
]
# endregion
# region Chat Completion
def get_chat_completion_additional_model_request_fields(
settings: BedrockChatPromptExecutionSettings,
) -> dict[str, Any] | None:
"""Get the additional model request fields for chat completion for Anthropic Claude models.
https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-anthropic-claude-messages.html
"""
if settings.top_k is not None:
return {"top_k": settings.top_k}
return None
# endregion
@@ -0,0 +1,101 @@
# Copyright (c) Microsoft. All rights reserved.
from typing import Any
from semantic_kernel.connectors.ai.bedrock.bedrock_prompt_execution_settings import (
BedrockChatPromptExecutionSettings,
BedrockEmbeddingPromptExecutionSettings,
BedrockTextPromptExecutionSettings,
)
from semantic_kernel.connectors.ai.bedrock.services.model_provider.utils import remove_none_recursively
from semantic_kernel.contents.text_content import TextContent
from semantic_kernel.exceptions.service_exceptions import ServiceInvalidResponseError
# region Text Completion
def get_text_completion_request_body(prompt: str, settings: BedrockTextPromptExecutionSettings) -> Any:
"""Get the request body for text completion for Cohere Command models.
https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-cohere-command.html
"""
return remove_none_recursively({
"prompt": prompt,
"temperature": settings.temperature,
"p": settings.top_p,
"k": settings.top_k,
"max_tokens": settings.max_tokens,
"stop_sequences": settings.stop,
# Extension data
"return_likelihoods": settings.extension_data.get("return_likelihoods", "NONE"),
"num_generations": settings.extension_data.get("num_generations", 1),
"logit_bias": settings.extension_data.get("logit_bias", None),
"truncate": settings.extension_data.get("truncate", "NONE"),
})
def parse_text_completion_response(response: dict[str, Any], model_id: str) -> list[TextContent]:
"""Parse the response from text completion for Anthropic Claude models."""
return [
TextContent(
ai_model_id=model_id,
text=generation["text"],
inner_content=generation,
)
for generation in response.get("generations", [])
]
# endregion
# region Chat Completion
def get_chat_completion_additional_model_request_fields(
settings: BedrockChatPromptExecutionSettings,
) -> dict[str, Any] | None:
"""Get the additional model request fields for chat completion for Cohere Command models.
https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-cohere-command-r-plus.html
"""
additional_fields: dict[str, Any] = remove_none_recursively({
"search_queries_only": settings.extension_data.get("search_queries_only", None),
"preamble": settings.extension_data.get("preamble", None),
"prompt_truncation": settings.extension_data.get("prompt_truncation", None),
"frequency_penalty": settings.extension_data.get("frequency_penalty", None),
"presence_penalty": settings.extension_data.get("presence_penalty", None),
"seed": settings.extension_data.get("seed", None),
"return_prompt": settings.extension_data.get("return_prompt", None),
"raw_prompting": settings.extension_data.get("raw_prompting", None),
})
if not additional_fields:
return None
return additional_fields
# endregion
# region Text Embedding
def get_text_embedding_request_body(text: str, settings: BedrockEmbeddingPromptExecutionSettings) -> Any:
"""Get the request body for text embedding for Cohere Command models."""
return remove_none_recursively({
"texts": [text],
"input_type": settings.extension_data.get("input_type", "search_document"),
"truncate": settings.extension_data.get("truncate", None),
"embedding_types": settings.extension_data.get("embedding_types", None),
})
def parse_text_embedding_response(response: dict[str, Any]) -> list[float]:
"""Parse the response from text embedding for Cohere Command models."""
if "embeddings" not in response or not isinstance(response["embeddings"], list) or len(response["embeddings"]) == 0:
raise ServiceInvalidResponseError("The response from Cohere model does not contain embeddings.")
return response.get("embeddings")[0] # type: ignore
# endregion
@@ -0,0 +1,63 @@
# Copyright (c) Microsoft. All rights reserved.
from typing import Any
from semantic_kernel.connectors.ai.bedrock.bedrock_prompt_execution_settings import (
BedrockChatPromptExecutionSettings,
BedrockTextPromptExecutionSettings,
)
from semantic_kernel.connectors.ai.bedrock.services.model_provider.utils import remove_none_recursively
from semantic_kernel.connectors.ai.completion_usage import CompletionUsage
from semantic_kernel.contents.text_content import TextContent
# region Text Completion
def get_text_completion_request_body(prompt: str, settings: BedrockTextPromptExecutionSettings) -> Any:
"""Get the request body for text completion for Meta Llama models.
https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-meta.html
"""
return remove_none_recursively({
"prompt": prompt,
"temperature": settings.temperature,
"topP": settings.top_p,
"max_gen_len": settings.max_tokens,
})
def parse_text_completion_response(response: dict[str, Any], model_id: str) -> list[TextContent]:
"""Parse the response from text completion for Meta Llama models."""
return [
TextContent(
ai_model_id=model_id,
text=response["generation"],
inner_content=response,
metadata={
"usage": CompletionUsage(
prompt_tokens=response.get("prompt_token_count"),
completion_tokens=response.get("completion_token_count"),
)
},
)
]
# endregion
# region Chat Completion
def get_chat_completion_additional_model_request_fields(
settings: BedrockChatPromptExecutionSettings,
) -> dict[str, Any] | None:
"""Get the additional model request fields for chat completion for Meta Llama models.
Meta Llama models do not support additional model request fields.
https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-meta.html
"""
return None
# endregion
@@ -0,0 +1,59 @@
# Copyright (c) Microsoft. All rights reserved.
from typing import Any
from semantic_kernel.connectors.ai.bedrock.bedrock_prompt_execution_settings import (
BedrockChatPromptExecutionSettings,
BedrockTextPromptExecutionSettings,
)
from semantic_kernel.connectors.ai.bedrock.services.model_provider.utils import remove_none_recursively
from semantic_kernel.contents.text_content import TextContent
# region Text Completion
def get_text_completion_request_body(prompt: str, settings: BedrockTextPromptExecutionSettings) -> Any:
"""Get the request body for text completion for Mistral AI models.
https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-mistral-text-completion.html
"""
return remove_none_recursively({
"prompt": f"<s>[INST] {prompt} [/INST]",
"max_tokens": settings.max_tokens,
"stop": settings.stop,
"temperature": settings.temperature,
"top_p": settings.top_p,
"top_k": settings.top_k,
})
def parse_text_completion_response(response: dict[str, Any], model_id: str) -> list[TextContent]:
"""Parse the response from text completion for AI21 Labs models."""
return [
TextContent(
ai_model_id=model_id,
text=output["text"],
inner_content=output,
)
for output in response.get("outputs", [])
]
# endregion
# region Chat Completion
def get_chat_completion_additional_model_request_fields(
settings: BedrockChatPromptExecutionSettings,
) -> dict[str, Any] | None:
"""Get the additional model request fields for chat completion for Mistral AI models.
MMistral AI models do not support additional model request fields.
https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-mistral-chat-completion.html
"""
return None
# endregion
@@ -0,0 +1,172 @@
# Copyright (c) Microsoft. All rights reserved.
from collections.abc import Callable
from enum import Enum
from typing import Any
from semantic_kernel.connectors.ai.bedrock.bedrock_prompt_execution_settings import (
BedrockChatPromptExecutionSettings,
BedrockEmbeddingPromptExecutionSettings,
BedrockTextPromptExecutionSettings,
)
from semantic_kernel.connectors.ai.bedrock.services.model_provider import (
bedrock_ai21_labs,
bedrock_amazon_titan,
bedrock_anthropic_claude,
bedrock_cohere,
bedrock_meta_llama,
bedrock_mistralai,
)
from semantic_kernel.contents.streaming_text_content import StreamingTextContent
from semantic_kernel.contents.text_content import TextContent
class BedrockModelProvider(Enum):
"""Amazon Bedrock Model Provider Enum.
This list contains the providers of all base models available on Amazon Bedrock.
"""
AI21LABS = "ai21"
AMAZON = "amazon"
ANTHROPIC = "anthropic"
COHERE = "cohere"
META = "meta"
MISTRALAI = "mistral"
@classmethod
def to_model_provider(cls, model_id: str) -> "BedrockModelProvider":
"""Convert a model ID to a model provider."""
try:
return next(provider for provider in cls if provider.value in model_id)
except StopIteration:
raise ValueError(f"Model ID {model_id} does not contain a valid model provider name.")
# region Text Completion
TEXT_COMPLETION_REQUEST_BODY_MAPPING: dict[
BedrockModelProvider, Callable[[str, BedrockTextPromptExecutionSettings], Any]
] = {
BedrockModelProvider.AMAZON: bedrock_amazon_titan.get_text_completion_request_body,
BedrockModelProvider.ANTHROPIC: bedrock_anthropic_claude.get_text_completion_request_body,
BedrockModelProvider.COHERE: bedrock_cohere.get_text_completion_request_body,
BedrockModelProvider.AI21LABS: bedrock_ai21_labs.get_text_completion_request_body,
BedrockModelProvider.META: bedrock_meta_llama.get_text_completion_request_body,
BedrockModelProvider.MISTRALAI: bedrock_mistralai.get_text_completion_request_body,
}
TEXT_COMPLETION_RESPONSE_MAPPING: dict[BedrockModelProvider, Callable[[dict[str, Any], str], list[TextContent]]] = {
BedrockModelProvider.AMAZON: bedrock_amazon_titan.parse_text_completion_response,
BedrockModelProvider.ANTHROPIC: bedrock_anthropic_claude.parse_text_completion_response,
BedrockModelProvider.COHERE: bedrock_cohere.parse_text_completion_response,
BedrockModelProvider.AI21LABS: bedrock_ai21_labs.parse_text_completion_response,
BedrockModelProvider.META: bedrock_meta_llama.parse_text_completion_response,
BedrockModelProvider.MISTRALAI: bedrock_mistralai.parse_text_completion_response,
}
STREAMING_TEXT_COMPLETION_RESPONSE_MAPPING: dict[
BedrockModelProvider, Callable[[dict[str, Any], str], StreamingTextContent]
] = {
BedrockModelProvider.AMAZON: bedrock_amazon_titan.parse_streaming_text_completion_response,
}
def get_text_completion_request_body(
model_id: str,
prompt: str,
settings: BedrockTextPromptExecutionSettings,
model_provider: BedrockModelProvider | None = None,
) -> dict:
"""Get the request body for text completion for Amazon Bedrock models."""
model_provider = model_provider or BedrockModelProvider.to_model_provider(model_id)
return TEXT_COMPLETION_REQUEST_BODY_MAPPING[model_provider](prompt, settings)
def parse_text_completion_response(
model_id: str,
response: dict,
model_provider: BedrockModelProvider | None = None,
) -> list[TextContent]:
"""Parse the response from text completion for Amazon Bedrock models."""
model_provider = model_provider or BedrockModelProvider.to_model_provider(model_id)
return TEXT_COMPLETION_RESPONSE_MAPPING[model_provider](response, model_id)
def parse_streaming_text_completion_response(
model_id: str,
chunk: dict,
model_provider: BedrockModelProvider | None = None,
) -> StreamingTextContent:
"""Parse the response from streaming text completion for Amazon Bedrock models."""
model_provider = model_provider or BedrockModelProvider.to_model_provider(model_id)
return STREAMING_TEXT_COMPLETION_RESPONSE_MAPPING[model_provider](chunk, model_id)
# endregion
# region Chat Completion
CHAT_COMPLETION_ADDITIONAL_MODEL_REQUEST_FIELDS_MAPPING: dict[
BedrockModelProvider, Callable[[BedrockChatPromptExecutionSettings], dict[str, Any] | None]
] = {
BedrockModelProvider.AMAZON: bedrock_amazon_titan.get_chat_completion_additional_model_request_fields,
BedrockModelProvider.ANTHROPIC: bedrock_anthropic_claude.get_chat_completion_additional_model_request_fields,
BedrockModelProvider.COHERE: bedrock_cohere.get_chat_completion_additional_model_request_fields,
BedrockModelProvider.AI21LABS: bedrock_ai21_labs.get_chat_completion_additional_model_request_fields,
BedrockModelProvider.META: bedrock_meta_llama.get_chat_completion_additional_model_request_fields,
BedrockModelProvider.MISTRALAI: bedrock_mistralai.get_chat_completion_additional_model_request_fields,
}
def get_chat_completion_additional_model_request_fields(
model_id: str,
settings: BedrockChatPromptExecutionSettings,
model_provider: BedrockModelProvider | None = None,
) -> dict[str, Any] | None:
"""Get the additional model request fields for chat completion for Amazon Bedrock models."""
model_provider = model_provider or BedrockModelProvider.to_model_provider(model_id)
return CHAT_COMPLETION_ADDITIONAL_MODEL_REQUEST_FIELDS_MAPPING[model_provider](settings)
# endregion
# region Text Embedding
TEXT_EMBEDDING_REQUEST_BODY_MAPPING: dict[
BedrockModelProvider, Callable[[str, BedrockEmbeddingPromptExecutionSettings], Any]
] = {
BedrockModelProvider.AMAZON: bedrock_amazon_titan.get_text_embedding_request_body,
BedrockModelProvider.COHERE: bedrock_cohere.get_text_embedding_request_body,
}
TEXT_EMBEDDING_RESPONSE_MAPPING: dict[BedrockModelProvider, Callable[[dict], list[float]]] = {
BedrockModelProvider.AMAZON: bedrock_amazon_titan.parse_text_embedding_response,
BedrockModelProvider.COHERE: bedrock_cohere.parse_text_embedding_response,
}
def get_text_embedding_request_body(
model_id: str,
text: str,
settings: BedrockEmbeddingPromptExecutionSettings,
model_provider: BedrockModelProvider | None = None,
) -> dict:
"""Get the request body for text embedding for Amazon Bedrock models."""
model_provider = model_provider or BedrockModelProvider.to_model_provider(model_id)
return TEXT_EMBEDDING_REQUEST_BODY_MAPPING[model_provider](text, settings)
def parse_text_embedding_response(
model_id: str,
response: dict,
model_provider: BedrockModelProvider | None = None,
) -> list[float]:
"""Parse the response from text embedding for Amazon Bedrock models."""
model_provider = model_provider or BedrockModelProvider.to_model_provider(model_id)
return TEXT_EMBEDDING_RESPONSE_MAPPING[model_provider](response)
# endregion
@@ -0,0 +1,217 @@
# Copyright (c) Microsoft. All rights reserved.
import json
from collections.abc import Callable, Mapping
from typing import TYPE_CHECKING, Any
from semantic_kernel.connectors.ai.bedrock.bedrock_prompt_execution_settings import BedrockChatPromptExecutionSettings
from semantic_kernel.connectors.ai.function_choice_behavior import FunctionChoiceType
from semantic_kernel.contents.chat_message_content import ChatMessageContent
from semantic_kernel.contents.function_call_content import FunctionCallContent
from semantic_kernel.contents.function_result_content import FunctionResultContent
from semantic_kernel.contents.image_content import ImageContent
from semantic_kernel.contents.text_content import TextContent
from semantic_kernel.contents.utils.author_role import AuthorRole
from semantic_kernel.contents.utils.finish_reason import FinishReason
from semantic_kernel.exceptions.service_exceptions import ServiceInvalidRequestError
if TYPE_CHECKING:
from semantic_kernel.connectors.ai.function_call_choice_configuration import FunctionCallChoiceConfiguration
from semantic_kernel.connectors.ai.prompt_execution_settings import PromptExecutionSettings
def remove_none_recursively(data: dict, max_depth: int = 5) -> dict:
"""Remove None values from a dictionary recursively."""
if max_depth <= 0:
return data
if not isinstance(data, dict):
return data
return {k: remove_none_recursively(v, max_depth=max_depth - 1) for k, v in data.items() if v is not None}
def _format_system_message(message: ChatMessageContent) -> dict[str, str]:
"""Format a system message to the expected object for the client.
Note that Guardrails are currently not supported.
Args:
message: The system message.
Returns:
The formatted system message.
"""
return {"text": message.content}
def _format_user_message(message: ChatMessageContent) -> dict[str, Any]:
"""Format a user message to the expected object for the client.
Note that Guardrails and Documents are currently not supported.
Args:
message: The user message.
Returns:
The formatted user message.
"""
contents: list[Any] = []
for item in message.items:
if not isinstance(item, (ImageContent, TextContent)):
raise ServiceInvalidRequestError("Only text and image content are supported in a user message.")
if isinstance(item, ImageContent):
contents.append({
"image": {
"format": item.mime_type.removeprefix("image/"),
"source": {
"bytes": item.data,
},
}
})
else:
contents.append({"text": item.text})
return {
"role": "user",
"content": contents,
}
def _format_assistant_message(message: ChatMessageContent) -> dict[str, Any]:
"""Format an assistant message to the expected object for the client.
Note that Guardrails and documents are currently not supported.
Args:
message: The assistant message.
Returns:
The formatted assistant message.
"""
contents: list[Any] = []
for item in message.items:
if isinstance(item, ImageContent):
raise ServiceInvalidRequestError("Image content is not supported in an assistant message.")
if isinstance(item, TextContent):
contents.append({"text": item.text})
elif isinstance(item, FunctionCallContent):
contents.append({
"toolUse": {
"toolUseId": item.id,
"name": item.name,
"input": item.arguments
if isinstance(item.arguments, Mapping)
else json.loads(item.arguments or "{}"),
}
})
else:
raise ServiceInvalidRequestError(f"Unsupported content type in an assistant message: {type(item)}")
return {
"role": "assistant",
"content": contents,
}
def _format_tool_message(message: ChatMessageContent) -> dict[str, Any]:
"""Format a tool message to the expected object for the client.
Args:
message: The tool message.
Returns:
The formatted tool message.
"""
contents: list[Any] = []
for item in message.items:
if isinstance(item, ImageContent):
raise ServiceInvalidRequestError("Image content is not supported in a tool message.")
if isinstance(item, TextContent):
contents.append({"text": item.text})
elif isinstance(item, FunctionResultContent):
contents.append({
"toolResult": {
"toolUseId": item.id,
# Image and document content are not yet supported in a tool message by SK
"content": [{"text": str(item)}],
}
})
else:
raise ServiceInvalidRequestError(f"Unsupported content type in a tool message: {type(item)}")
return {
"role": "user",
"content": contents,
}
MESSAGE_CONVERTERS: dict[AuthorRole, Callable[[ChatMessageContent], dict[str, Any]]] = {
AuthorRole.SYSTEM: _format_system_message,
AuthorRole.USER: _format_user_message,
AuthorRole.ASSISTANT: _format_assistant_message,
AuthorRole.TOOL: _format_tool_message,
}
def update_settings_from_function_choice_configuration(
function_choice_configuration: "FunctionCallChoiceConfiguration",
settings: "PromptExecutionSettings",
type: FunctionChoiceType,
) -> None:
"""Update the settings from a FunctionChoiceConfiguration."""
assert isinstance(settings, BedrockChatPromptExecutionSettings) # nosec
# Bedrock supports 3 types of tool choice behavior: auto, any, tool
# We will map our `FunctionChoiceType` to the corresponding Bedrock type following these rules:
# `FunctionChoiceType.NONE` -> No configuration needed
# `FunctionChoiceType.AUTO` -> "auto"
# `FunctionChoiceType.REQUIRED`:
# - If there are more than one available functions -> "any"
# - If there is only one available function -> "tool"
if type == FunctionChoiceType.NONE:
return
if function_choice_configuration.available_functions:
if type == FunctionChoiceType.AUTO:
settings.tool_choice = {"auto": {}}
elif type == FunctionChoiceType.REQUIRED:
if len(function_choice_configuration.available_functions) > 1:
settings.tool_choice = {"any": {}}
else:
settings.tool_choice = {
"tool": {
"name": function_choice_configuration.available_functions[0].fully_qualified_name,
}
}
settings.tools = [
{
"toolSpec": {
"name": function.fully_qualified_name,
"description": function.description or "",
"inputSchema": {
"json": {
"type": "object",
"properties": {param.name: param.schema_data for param in function.parameters},
"required": [p.name for p in function.parameters if p.is_required],
}
},
}
}
for function in function_choice_configuration.available_functions
]
def finish_reason_from_bedrock_to_semantic_kernel(finish_reason: str) -> FinishReason | None:
"""Convert a finish reason from Bedrock to Semantic Kernel."""
return {
"stop_sequence": FinishReason.STOP,
"end_turn": FinishReason.STOP,
"max_tokens": FinishReason.LENGTH,
"content_filtered": FinishReason.CONTENT_FILTER,
"tool_use": FinishReason.TOOL_CALLS,
}.get(finish_reason)