探索语义内核 BedrockAgent

重要

单代理功能(如 BedrockAgent)目前处于实验阶段。 这些功能处于积极开发阶段,在正式发布之前可能会更改。

有关此讨论的详细 API 文档在以下位置提供:

即将推出 BedrockAgent API 文档。

即将推出 BedrockAgent API 文档。

功能当前在 Java 中不可用。

什么是 BedrockAgent

Bedrock Agent 是语义内核中的专用 AI 代理,旨在与 Amazon Bedrock 的代理服务集成。 与 OpenAI 和 Azure AI 代理一样,Bedrock 代理通过无缝工具(作)集成实现高级多轮对话功能,但它完全在 AWS 生态系统中运行。 它自动化函数/工具的调用(在 Bedrock 中称为动作组),因此您不必手动解析和执行动作,并通过会话在 AWS 上安全地管理对话状态,从而减少在应用程序中维护聊天历史记录的需求。

基岩代理与其他类型的代理在几个关键方面存在差异:

  • AWS 托管执行: 与使用 OpenAI 云的 OpenAI 助手或使用 Azure Foundry 服务的 Azure AI 代理不同,Bedrock 代理在 Amazon Bedrock 上运行。 必须拥有有权访问 Bedrock 的 AWS 帐户(以及适当的 IAM 权限),才能使用它。 代理的生命周期(创建、会话、删除)和某些工具执行由 AWS 服务管理,而函数调用工具在环境中本地执行。

  • 基础模型选择: 创建贝底岩代理时,可以指定应使用哪个基础模型(例如 Amazon Titan 或合作伙伴模型)。 只能使用你被授予访问权限的模型。 这不同于聊天完成代理(你可以通过直接模型终结点实例化它们)——在使用 Bedrock 时,模型在代理创建时被选定为代理的默认能力。

  • IAM 角色要求: 创建时需要为 Bedrock 代理提供 IAM 角色 ARN。 此角色必须有权代表你调用所选模型(以及任何集成工具)。 这可确保代理具有执行其作(例如运行代码或访问 AWS 帐户下的其他 AWS 服务)所需的权限。

  • 内置工具(动作组): Bedrock 支持内置的“动作组”(工具),这些工具可以附加到代理上。 例如,您可以启用代码解释器动作组以允许代理执行 Python 代码,或启用用户输入动作组以允许代理提示进行澄清。 这些功能类似于 OpenAI 的代码解释器插件或函数调用,但在 AWS 中,它们在代理上显式配置。 基岩代理还可以通过特定领域的工具和功能自定义语义内核插件来扩展,类似于其他代理。

  • 基于会话的线程: 与 Bedrock 代理的对话发生在与 AWS 上的 Bedrock 会话关联的线程中。 每个线程(会话)由 Bedrock 服务提供的唯一 ID 标识,会话历史记录由服务而不是进程内存储。 这意味着多轮对话保存在 AWS 上,并通过会话 ID 检索上下文。 Semantic Kernel BedrockAgentThread 类抽象化此详细信息 - 使用它时,代理会在后台创建或继续一个 Bedrock 会话。

总之,BedrockAgent 使您能够通过语义内核利用 Amazon Bedrock 的强大代理和工具组合框架,从而通过 AWS 托管的模型和工具提供定向对话。 它自动执行 Bedrock 的代理 API(代理创建、会话管理、工具调用)的复杂程度,以便你可以在高级的跨语言 SK 界面中与之交互。

准备您的开发环境

若要开始使用 a BedrockAgent进行开发,请使用适当的语义内核包设置环境,并确保满足 AWS 先决条件。

小窍门

请查看 AWS 文档 ,了解如何将环境配置为使用 Bedrock API。

将 Semantic Kernel Bedrock Agents 包添加到 .NET 项目中:

dotnet add package Microsoft.SemanticKernel.Agents.Bedrock --prerelease

这将引入对 Bedrock 的语义内核 SDK 支持,包括对 Bedrock 的 AWS SDK 的依赖项。 可能还需要配置 AWS 凭据(例如,通过环境变量或默认 AWS 配置)。 AWS SDK 将使用您配置的凭据;确保在环境或 AWS 配置文件中设置了您的 AWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEY 和默认区域。 (有关凭据配置的详细信息,请参阅 AWS 的文档。

使用 AWS 附加组件安装语义内核包:

pip install semantic-kernel[aws]

这可确保必要的 AWS 库(例如 boto3)与语义内核一起安装。 在 Python 中使用 Bedrock 代理之前,请确保正确配置 AWS 凭据和区域(例如,通过设置环境变量或使用 AWS CLI)。 您应设置AWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEYAWS_DEFAULT_REGION(或 AWS 用户配置文件),以便 boto3 能实现身份验证。

功能当前在 Java 中不可用。

创建 BedrockAgent

创建 Bedrock 代理涉及两个步骤:首先,使用 Amazon Bedrock 定义代理(包括选择模型并提供初始说明),然后实例化语义内核代理对象以与之交互。 在 AWS 上创建代理时,它会以非准备状态启动,因此会执行额外的“准备”作以使其可供使用。

using Amazon.Bedrock;
using Amazon.Bedrock.Model;
using Amazon.BedrockRuntime;
using Microsoft.SemanticKernel.Agents.Bedrock;

// 1. Define a new agent on the Amazon Bedrock service
IAmazonBedrock bedrockClient = new AmazonBedrockClient();  // uses default AWS credentials & region
var createRequest = new CreateAgentRequest 
{
    AgentName = "<foundation model ID>",          // e.g., "anthropic.claude-v2" or other model
    FoundationModel = "<foundation model ID>",    // the same model, or leave null if AgentName is the model
    AgentResourceArn = "<agent role ARN>",        // IAM role ARN with Bedrock permissions
    Instruction = "<agent instructions>"
};
CreateAgentResponse createResponse = await bedrockClient.CreateAgentAsync(createRequest);

// (Optional) Provide a description as needed:
// createRequest.Description = "<agent description>";

// After creation, the agent is in a "NOT_PREPARED" state.
// Prepare the agent to load tools and finalize setup:
await bedrockClient.PrepareAgentAsync(new PrepareAgentRequest 
{
    AgentId = createResponse.Agent.AgentId
});

// 2. Create a Semantic Kernel agent instance from the Bedrock agent definition
IAmazonBedrockRuntime runtimeClient = new AmazonBedrockRuntimeClient();
BedrockAgent agent = new BedrockAgent(createResponse.Agent, bedrockClient, runtimeClient);

在上面的代码中,我们首先使用 AWS SDK (AmazonBedrockClient) 在 Bedrock 上创建代理,并指定代理应承担的基础模型、名称、说明和 IAM 角色的 ARN。 Bedrock 服务以代理的定义(包括唯一的 AgentId)进行响应。 然后,我们调用 PrepareAgentAsync 将代理转换为就绪状态(代理将从 CREATING 状态移动到NOT_PREPARED,然后迁移到 READY 一旦准备就绪)。 最后,我们使用返回的定义和 AWS 客户端构造 BedrockAgent 对象。 此 BedrockAgent 实例是我们将用来发送消息和接收响应的内容。

import boto3
from semantic_kernel.agents import BedrockAgent

# 1. Define and prepare a new agent on Amazon Bedrock
agent = await BedrockAgent.create_and_prepare_agent(
    name="<agent name>", 
    instructions="<agent instructions>",
    foundation_model="<foundation model ID>",
    agent_resource_role_arn="<agent role ARN>"
)

在上面的示例中, BedrockAgent.create_and_prepare_agent 处理完整的创建流:它使用 AWS 配置(通过 boto3)在 Bedrock 上创建具有给定名称、基础模型和说明的代理,然后自动等待代理到达就绪状态(在内部执行准备步骤)。 BedrockAgent 实例已准备好使用。 在后台,此方法使用默认凭据创建 AWS 客户端(适用于 Bedrock 和 Bedrock Runtime),因此请确保已设置 AWS 环境。 如果需要自定义配置,还可以手动构造 AWS 客户端并将其作为参数(例如 client= boto3.client("bedrock")runtime_client= boto3.client("bedrock-runtime"))传递给 create_and_prepare_agent 调用。

功能当前在 Java 中不可用。

检索现有 BedrockAgent

在 Bedrock 上创建代理后,可以使用其唯一标识符(代理 ID)稍后进行检索。 这样就可以在语义内核中重新实例化BedrockAgent,而无需从头重新创建它。

对于 .NET,Bedrock 代理的标识符是一个可以通过 agent.Id 访问的字符串。 若要按 ID 检索现有代理,请使用 AWS Bedrock 客户端,然后构造一个新 BedrockAgent代理:

string existingAgentId = "<your agent ID>";
var getResponse = await bedrockClient.GetAgentAsync(new GetAgentRequest { AgentId = existingAgentId });
BedrockAgent agent = new BedrockAgent(getResponse.Agent, bedrockClient, runtimeClient);

在这里,我们在具有已知 ID 的IAmazonBedrock客户端上调用GetAgentAsync,这将返回代理的定义(名称、模型、说明等)。 然后,使用该定义和相同的客户端初始化一个新的 BedrockAgent。 此代理实例将链接到现有的 Bedrock 代理。

在 Python 中,可以使用 AWS Bedrock boto3 客户端通过 ID 检索代理,然后将其包装在 BedrockAgent 中:

import asyncio, boto3
from semantic_kernel.agents import BedrockAgent

agent_id = "<your agent ID>"
bedrock_client = boto3.client("bedrock")  # Bedrock service client
# Fetch the agent's definition from AWS
agent_info = await asyncio.to_thread(bedrock_client.get_agent, AgentId=agent_id)
# Create the BedrockAgent instance from the retrieved definition
agent = BedrockAgent(agent_model=agent_info["agent"])

在这个代码片段中,我们使用 boto3 在 Bedrock 服务上调用 get_agent (由于 boto3 的阻塞性,因此在 asyncio.to_thread 线程中运行)。 返回的内容 agent_info["agent"] 包含代理的详细信息(ID、名称、状态等),我们将其传递到 BedrockAgent 构造函数中。 由于我们没有为 BedrockAgent 显式指定 AWS 客户端,因此它将内部创建具有默认设置的新客户端。 可选:你可以提供 client=runtime_client= 以重复利用客户端(如果有)。

功能当前在 Java 中不可用。

与 BedrockAgent 交互

有了 BedrockAgent 实例后,与其交互(发送用户消息和接收 AI 响应)非常简单。 代理使用线程来管理聊天上下文。 对于 Bedrock 代理,线程对应于 AWS Bedrock 会话。 Semantic Kernel BedrockAgentThread 类处理会话创建和跟踪:启动新会话时,会启动新的 Bedrock 会话,并在发送消息时,Bedrock 维护交替的用户/助理消息历史记录。 (贝德洛克要求聊天历史记录在用户和助理消息之间交替:语义内核的通道逻辑将在必要时插入占位符以强制实施此模式。无需指定线程即可调用代理(在这种情况下,SK 将自动创建新 BedrockAgentThread ),或者如果要跨多个调用继续对话,可以显式创建/维护线程。 每个调用都会返回一个或多个响应,你可以管理线程生存期(例如,在完成 AWS 会话时将其删除)。

Bedrock 代理线程的具体内容由 BedrockAgentThread 类(实现通用 AgentThread 接口)抽象化。 BedrockAgent 目前仅支持类型为 BedrockAgentThread的线程。

BedrockAgent agent = /* (your BedrockAgent instance, as created above) */;

// Start a new conversation thread for the agent
AgentThread agentThread = new BedrockAgentThread(runtimeClient);
try
{
    // Send a user message and iterate over the response(s)
    var userMessage = new ChatMessageContent(AuthorRole.User, "<your user input>");
    await foreach (ChatMessageContent response in agent.InvokeAsync(userMessage, agentThread))
    {
        Console.WriteLine(response.Content);
    }
}
finally
{
    // Clean up the thread and (optionally) the agent when done
    await agentThread.DeleteAsync();
    await agent.Client.DeleteAgentAsync(new DeleteAgentRequest { AgentId = agent.Id });
}

在此示例中,我们显式创建一个 BedrockAgentThread (传入 runtimeClient,用于与 Bedrock 运行时服务通信)。 然后,我们调用agent.InvokeAsync(...),方法是使用ChatMessageContent来表示用户的消息。 InvokeAsync 返回异步响应流 – 在实践中,Bedrock 代理通常会为每个调用返回一个最终响应(因为中间工具作单独处理),因此通常从循环中获取单个 ChatMessageContent 响应。 我们打印出助理的答复(response.Content)。 在 finally 块中,我们删除线程,结束 AWS 上的 Bedrock 会话。 在本例中,我们还删除代理本身(因为我们只为此示例创建了代理),此步骤是可选的,仅当不想再次重复使用代理时才需要(请参阅下面的删除 BedrockAgent)。

可以通过在后续调用中重用相同的 agentThread 来维持现有对话。 例如,可以循环读取用户输入并每次使用相同的线程调用 InvokeAsync ,以执行多轮对话。 还可以创建具有已知会话 ID 的 BedrockAgentThread,以恢复以前保存的对话:

string sessionId = "<existing Bedrock session ID>";
AgentThread thread = new BedrockAgentThread(runtimeClient, sessionId);
// Now `InvokeAsync` using this thread will continue the conversation from that session

在 Python 中使用 Bedrock 代理与管理会话类似 BedrockAgentThread 。 可以启动新线程或传递现有线程以继续对话:

from semantic_kernel.agents import BedrockAgentThread

# Assume `agent` is your BedrockAgent instance
USER_INPUTS = ["Hello", "What's your name?"]

thread = BedrockAgentThread()  # start a new conversation thread (session)
try:
    for user_input in USER_INPUTS:
        response = await agent.get_response(messages=user_input, thread=thread)
        print(response)  # print the assistant's reply
        thread = response.thread  # update thread (BedrockAgentThread) for next turn
finally:
    await thread.delete() if thread else None

在此代码中,我们将循环遍历多个用户输入项。 每次迭代时,我们都会使用用户消息和当前线程进行调用 agent.get_response(...) 。 第一个调用启动了 Bedrock 会话,并返回一个 AgentResponseItem(或 ChatMessageContent),其中包含助理的答案。 我们打印响应,然后获取 response.thread,再更新 BedrockAgentThread 以包含新的消息上下文,以供下一轮使用。 对话(在此示例中为两个轮次)之后,我们将删除线程以结束 AWS 上的会话。

如果在调用中省略参数 threadagent.get_responseagent.invoke 将自动为该调用创建新线程并将其包含在响应中。

或者,还可以一次性发送一批消息,方法是将消息列表传递给 get_response 或使用异步流式处理调用。 例如,要逐个令牌流式传输助手的响应以应对单个提示:

# Streaming a single response from the Bedrock agent
async for partial in agent.invoke_stream(messages="Tell me a joke.", thread=thread):
    print(partial.content, end="")

在生成响应时,invoke_stream(...) 方法产生 ChatMessageContent 对象。 通过迭代,你可以以增量方式输出助手的答案(此处我们打印输出的字符没有换行符,以形成完整的响应)。

功能当前在 Java 中不可用。

删除 BedrockAgent

基岩代理是 AWS 帐户中的永久性资源 - 在删除之前,它们将保留(并可能产生费用或计入服务限制)。 如果不再需要已创建的代理,应通过 Bedrock 服务 API 将其删除。

使用 Bedrock 客户端按代理 ID 删除。 例如:

await bedrockAgent.Client.DeleteAgentAsync(new() { AgentId = bedrockAgent.Id });

此调用后,代理的状态将更改,并且将不再可用。 (尝试调用已删除的代理将导致错误。

调用代理的删除方法。 例如:

await agent.delete_agent()

这将调用 Bedrock 服务以删除代理(并在内部将 BedrockAgent 对象标记为已删除)。 你可以通过检查 agent.id 或提供的标记(例如 _is_deleted)进行验证。

注意: 删除 Bedrock 代理不会自动终止其正在进行的会话。 如果你有长时间运行的会话(线程),则应通过删除线程来结束这些会话(实际上是调用 Bedrock 的 EndSession 和 DeleteSession)。 在实践中,删除线程(如上面的示例所示)将结束会话。

功能当前在 Java 中不可用。

使用 BedrockAgent 处理中间消息

当 Bedrock代理 调用工具(动作组)以得出答案时,这些中间步骤(函数调用和结果)默认在内部处理。 代理的最终答案将引用这些工具的结果,但不会自动包含详细分步详细信息。 但是,语义内核允许通过提供回调来利用这些中间消息进行日志记录或自定义处理。

agent.invoke(...)agent.invoke_stream(...)期间,可以提供on_intermediate_message回调函数。 将为在制定最终响应的过程中生成的每个中间消息调用此回调。 中间消息可能包括 FunctionCallContent (当代理决定调用函数/工具时)和 FunctionResultContent (当工具返回结果时)。

例如,假设 Bedrock 代理有权访问简单插件(或内置工具)以获取菜单信息,类似于与 OpenAI 助手一起使用的示例:

from semantic_kernel.contents import ChatMessageContent, FunctionCallContent, FunctionResultContent
from semantic_kernel.functions import kernel_function

# Define a sample plugin with two functions
class MenuPlugin:
    @kernel_function(description="Provides a list of specials from the menu.")
    def get_specials(self) -> str:
        return "Soup: Clam Chowder; Salad: Cobb Salad; Drink: Chai Tea"

    @kernel_function(description="Provides the price of a menu item.")
    def get_item_price(self, menu_item: str) -> str:
        return "$9.99"

# Callback to handle intermediate messages
async def handle_intermediate_steps(message: ChatMessageContent) -> None:
    for item in (message.items or []):
        if isinstance(item, FunctionCallContent):
            print(f"Function Call:> {item.name} with arguments: {item.arguments}")
        elif isinstance(item, FunctionResultContent):
            print(f"Function Result:> {item.result} for function: {item.name}")
        else:
            print(f"[Intermediate] {item}")

# Create the BedrockAgent with the plugin (assuming agent is not yet created above)
agent = await BedrockAgent.create_and_prepare_agent(
    name="MenuAgent",
    instructions="You are a restaurant assistant.",
    foundation_model="<model ID>",
    agent_resource_role_arn="<role ARN>",
    plugins=[MenuPlugin()]  # include our custom plugin
)

# Start a conversation with intermediate callback
thread = BedrockAgentThread()
user_queries = [
    "Hello!",
    "What are the specials today?",
    "What is the special drink?",
    "How much is that?"
]
try:
    for query in user_queries:
        print(f"# User: {query}")
        async for response in agent.invoke(messages=query, thread=thread, on_intermediate_message=handle_intermediate_steps):
            print(f"# Assistant: {response}")
            thread = response.thread
finally:
    await thread.delete() if thread else None
    await agent.delete_agent()

在此代码中,每当代理需要从 MenuPlugin 中调用函数时(例如,get_specialsget_item_price),handle_intermediate_steps 回调将输出一行函数调用的日志以及另一行函数结果的日志。 然后,每个用户查询的最终助理响应将按正常顺序打印。 通过观察中间过程内容,可以追踪代理到达其答案的过程(使用了哪个工具,返回了什么结果等)。

例如,输出可能如下所示:

# User: Hello!
# Assistant: Hello! How can I assist you today?
# User: What are the specials today?
Function Call:> MenuPlugin-get_specials with arguments: {}
Function Result:> Soup: Clam Chowder; Salad: Cobb Salad; Drink: Chai Tea for function: MenuPlugin-get_specials
# Assistant: The specials today include Clam Chowder for the soup, Cobb Salad, and Chai Tea as a special drink.
# User: What is the special drink?
# Assistant: The special drink is Chai Tea.
# User: How much is that?
Function Call:> MenuPlugin-get_item_price with arguments: {"menu_item": "Chai Tea"}
Function Result:> $9.99 for function: MenuPlugin-get_item_price
# Assistant: The special drink (Chai Tea) costs $9.99.

在上述交互中,中间结果显示代理在适当时调用了MenuPlugin.get_specialsMenuPlugin.get_item_price,并利用其结果回答了用户。 可以根据需要在应用程序逻辑中记录或使用这些中间详细信息(例如,显示代理执行的步骤)。

BedrockAgent(C#)中中间消息的回调支持遵循类似的模式,但确切的 API 正在开发中。 将来的版本将允许注册一个委托来处理 FunctionCallContentFunctionResultContent,在 InvokeAsync 时进行。

功能当前在 Java 中不可用。

使用声明式 YAML 定义 Bedrock 代理

语义内核的代理框架支持通过 YAML(或 JSON)定义代理的声明性架构。 这样,便可以在文件中指定代理的配置(其类型、模型、工具等),然后在运行时加载该代理定义,而无需编写命令性代码来构造它。

注意: 基于 YAML 的代理定义是一项新兴功能,可能是实验性的。 确保使用的是支持 YAML 代理加载的语义内核版本,并参考最新文档了解任何格式更改。

使用声明性规范可以简化配置,尤其是在想要轻松切换代理设置或使用配置文件方法时。 对于 Bedrock 代理,YAML 定义可能如下所示:

type: bedrock_agent
name: MenuAgent
description: Agent that answers questions about a restaurant menu
instructions: You are a restaurant assistant that provides daily specials and prices.
model:
  id: anthropic.claude-v2
agent_resource_role_arn: arn:aws:iam::123456789012:role/BedrockAgentRole
tools:
  - type: code_interpreter
  - type: user_input
  - name: MenuPlugin
    type: kernel_function

在此(假设)YAML 中,我们定义了一个类型 bedrock_agent代理,为其提供名称和说明,按 ID 指定基础模型,并提供它应使用的角色的 ARN。 我们还声明了几个工具:一个启用内置代码解释器,另一个启用内置用户输入工具,以及自定义 MenuPlugin(将在代码中单独定义,并注册为内核函数)。 此类文件以人工可读形式封装代理的设置。

若要从 YAML 实例化代理,请使用具有适当工厂的静态加载程序。 例如:

string yamlText = File.ReadAllText("bedrock-agent.yaml");
var factory = new BedrockAgentFactory();  // or an AggregatorAgentFactory if multiple types are used
Agent myAgent = await KernelAgentYaml.FromAgentYamlAsync(kernel, yamlText, factory);

这将使用提供的内核和工厂解析 YAML,并生成BedrockAgent实例(或者根据type字段生成其他类型)。

"BedrockAgent" 声明性规范支持即将推出。

功能当前在 Java 中不可用。

对于方案配置和测试,使用声明性架构特别强大,因为可以通过编辑配置文件而不是更改代码来交换模型或说明。 密切关注 Semantic Kernel 的文档和示例,了解随着功能逐步演变的 YAML 代理定义的更多详细信息。

其他资源

  • AWS Bedrock 文档:若要详细了解 Amazon Bedrock 的代理功能,请参阅 AWS 文档中Amazon Bedrock 代理(例如如何配置基础模型访问和 IAM 角色)。 了解基础服务有助于设置正确的权限并充分利用内置工具。
  • 语义内核示例:语义内核存储库包含基岩代理 的概念示例 。 例如,Python 示例中的 Bedrock 代理基本聊天示例演示了简单的 Q&A 和BedrockAgent带代码解释器的基岩代理示例演示如何启用和使用代码解释器工具。 这些示例是一个很好的起点,可以看到 BedrockAgent 在实际运作。

将 Amazon Bedrock 代理集成后,语义内核可实现真正的多平台 AI 解决方案 - 无论是使用 OpenAI、Azure OpenAI 还是 AWS Bedrock,都可以使用一致的框架通过工具集成构建丰富的对话应用程序。 这 BedrockAgent 为利用 AWS 的最新基础模型和安全、可扩展的语义内核项目中的代理范例打开了大门。

后续步骤