Files
semantic-kernel/python/semantic_kernel/agents/agent.py
T
Tao Chen 7789c5fe55 Python: Bedrock agent (#10307)
### 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.
-->
Address https://github.com/microsoft/semantic-kernel/issues/10284

Integration of AWS Bedrock agent to SK Python

### Description
1. Implementation of Bedrock agent in SK (essentially an agent connector
to the Bedrock agent service).
2. A dedicated channel that works with Bedrock agents.
3. Samples on how to use Bedrock agents.

Unit and integration tests will be included in another PR to prevent
this PR from becoming too big.

<!-- Describe your changes, the overall approach, the underlying design.
These notes will help understanding how your code works. Thanks! -->

### 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 😄
2025-01-31 19:44:39 +00:00

130 lines
4.8 KiB
Python

# Copyright (c) Microsoft. All rights reserved.
import logging
import uuid
from collections.abc import Iterable
from typing import ClassVar
from pydantic import Field
from semantic_kernel.agents.channels.agent_channel import AgentChannel
from semantic_kernel.functions.kernel_arguments import KernelArguments
from semantic_kernel.kernel import Kernel
from semantic_kernel.kernel_pydantic import KernelBaseModel
from semantic_kernel.prompt_template.kernel_prompt_template import KernelPromptTemplate
from semantic_kernel.prompt_template.prompt_template_base import PromptTemplateBase
from semantic_kernel.prompt_template.prompt_template_config import PromptTemplateConfig
from semantic_kernel.utils.experimental_decorator import experimental_class
from semantic_kernel.utils.naming import generate_random_ascii_name
from semantic_kernel.utils.validation import AGENT_NAME_REGEX
logger: logging.Logger = logging.getLogger(__name__)
@experimental_class
class Agent(KernelBaseModel):
"""Base abstraction for all Semantic Kernel agents.
An agent instance may participate in one or more conversations.
A conversation may include one or more agents.
In addition to identity and descriptive meta-data, an Agent
must define its communication protocol, or AgentChannel.
Attributes:
name: The name of the agent (optional).
description: The description of the agent (optional).
id: The unique identifier of the agent (optional). If no id is provided,
a new UUID will be generated.
instructions: The instructions for the agent (optional)
"""
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
description: str | None = None
name: str = Field(default_factory=lambda: f"agent_{generate_random_ascii_name()}", pattern=AGENT_NAME_REGEX)
instructions: str | None = None
kernel: Kernel = Field(default_factory=Kernel)
channel_type: ClassVar[type[AgentChannel] | None] = None
arguments: KernelArguments | None = None
prompt_template: PromptTemplateBase | None = None
def get_channel_keys(self) -> Iterable[str]:
"""Get the channel keys.
Returns:
A list of channel keys.
"""
if not self.channel_type:
raise NotImplementedError("Unable to get channel keys. Channel type not configured.")
yield self.channel_type.__name__
async def create_channel(self) -> AgentChannel:
"""Create a channel.
Returns:
An instance of AgentChannel.
"""
if not self.channel_type:
raise NotImplementedError("Unable to create channel. Channel type not configured.")
return self.channel_type()
async def format_instructions(self, kernel: Kernel, arguments: KernelArguments | None = None) -> str | None:
"""Format the instructions.
Args:
kernel: The kernel instance.
arguments: The kernel arguments.
Returns:
The formatted instructions.
"""
if self.prompt_template is None:
if self.instructions is None:
return None
self.prompt_template = KernelPromptTemplate(
prompt_template_config=PromptTemplateConfig(template=self.instructions)
)
return await self.prompt_template.render(kernel, arguments)
def merge_arguments(self, override_args: KernelArguments | None) -> KernelArguments:
"""Merge the arguments with the override arguments.
Args:
override_args: The arguments to override.
Returns:
The merged arguments. If both are None, return None.
"""
if not self.arguments:
if not override_args:
return KernelArguments()
return override_args
if not override_args:
return self.arguments
# Both are not None, so merge with precedence for override_args.
merged_execution_settings = self.arguments.execution_settings or {}
if override_args.execution_settings:
merged_execution_settings.update(override_args.execution_settings)
merged_params = dict(self.arguments)
merged_params.update(override_args)
return KernelArguments(settings=merged_execution_settings, **merged_params)
def __eq__(self, other):
"""Check if two agents are equal."""
if isinstance(other, Agent):
return (
self.id == other.id
and self.name == other.name
and self.description == other.description
and self.instructions == other.instructions
and self.channel_type == other.channel_type
)
return False
def __hash__(self):
"""Get the hash of the agent."""
return hash((self.id, self.name, self.description, self.instructions, self.channel_type))