Semantic Kernel A2A Client 实战:用命令行构建调用远程 Agent 的 A2A 协议客户端
【免费下载链接】semantic-kernelIntegrate cutting-edge LLM technology quickly and easily into your apps项目地址: https://gitcode.com/GitHub_Trending/se/semantic-kernel
导读
本文聚焦 Semantic Kernel 仓库中的 A2A 客户端示例(dotnet/samples/Demos/A2AClientServer/A2AClient),讲解如何基于 Google A2A(Agent-to-Agent)协议,构建一个带命令行交互界面的客户端,通过该客户端把远程 A2A 服务器上暴露的多个 Agent 转化为本地 Kernel 函数,并借助 Chat Completion Agent 自动编排调用。读完本文,你将掌握 A2A 客户端的运行方式、Secret Manager 配置方法,以及A2AAgent、AgentKernelFunctionFactory与FunctionChoiceBehavior.Auto()在客户端侧的底层配合原理。
示例背景:A2A Client 与 A2A Server 的分工
本演示包含两个组件(详见 A2AClientServer 目录 README):
- A2AServer:需要运行三个实例,分别对应 Invoice(发票)、Policy(政策)、Logistics(物流)三个 Agent,每个实例通过 A2A 协议将单个 Agent 暴露为可访问的服务;
- A2AClient:代表一个客户端应用,使用 A2A 协议连接远程 A2A 服务器,从而在你提问时借助这些远程 Agent 来回答问题。
客户端本身不直接持有业务数据,而是通过 A2A 协议"借用"远端 Agent 的能力。下图展示了整个演示的架构:
需要特别说明的是,A2A 协议仍处于快速演进阶段(仓库在 A2AClientServer 目录 README 中有明确警告),示例会随协议演进持续更新。
快速运行:三行命令启动客户端
按照 A2AClient README 的说明,运行客户端只需两步:
cd dotnet/samples/Demos/A2AClientServer/A2AClient dotnet run程序启动后会进入交互式命令行,等待你输入请求,例如:
Show me all invoices for Contoso?随后客户端会调用远程 Agent 并给出最终回答。退出交互循环的方式是在提示符后输入:q或quit(这一行为定义在 Program.cs 中)。
用 Secret Manager 配置客户端密钥
A2A 客户端需要三个配置项,通过 .NET Secret Manager 写入。原文档的配置命令如下(原文档中有一处引号笔误,以下为修正后的可用形式):
cd dotnet/samples/Demos/A2AClientServer/A2AClient dotnet user-secrets set "A2AClient:ModelId" "..." dotnet user-secrets set "A2AClient:ApiKey" "..." dotnet user-secrets set "A2AClient:AgentUrls" "http://localhost:5000/;http://localhost:5001/;http://localhost:5002/"各配置项的具体语义、默认值与代码依据,可以通过 Program.cs 确认:
| 配置项 | 是否必填 | 默认值 | 说明 |
|---|---|---|---|
A2AClient:ApiKey | 必填 | 无(缺失时抛出ArgumentException,提示 "A2AClient:ApiKey must be provided") | 用于创建 OpenAI Chat Completion 的 API Key |
A2AClient:ModelId | 可选 | gpt-4.1 | 托管 Agent(Host Agent)使用的模型 ID |
A2AClient:AgentUrls | 可选 | http://localhost:5000/;http://localhost:5001/;http://localhost:5002/ | 要连接的远程 A2A Agent 地址列表 |
分隔符注意:原文档把AgentUrls描述为"空格分隔的字符串列表",但示例命令与源码实现实际都以分号;分隔——Program.cs 中通过agentUrls!.Split(";")拆分,因此请以分号作为分隔符。另外,原文档示例中出现了http://localhost:5000/policy这类带路径的地址,而代码默认值与服务器端实际映射(见下文)均为根路径,使用默认根路径即可。
配置的读取机制在 Program.cs:ConfigurationBuilder依次加载环境变量(AddEnvironmentVariables)与程序集对应的 User Secrets(AddUserSecrets),这意味着你也可以通过环境变量注入同样的配置键。UserSecretsId定义在 A2AClient.csproj 中。
客户端工作原理:远程 Agent 如何变成 Kernel 函数
A2A 客户端的核心逻辑集中在 HostClientAgent.cs,其初始化流程清晰地展示了 Semantic Kernel 与 A2A 协议的桥接方式:
1. 为每个远程地址创建 A2AAgent
CreateAgentAsync(见 HostClientAgent.cs)对每个 URL 执行:
- 创建带 60 秒超时的
HttpClient; - 用
A2AClient(url, httpClient)建立 A2A 协议客户端; - 用
A2ACardResolver拉取远程服务器的 Agent Card(.well-known端点); - 返回
new A2AAgent(client, agentCard)。
也就是说,客户端在连接时会先发现并校验远程 Agent 的能力描述(Agent Card),再基于它构造A2AAgent。A2AAgent是 Semantic Kernel 中基于 A2A 协议的Agent实现(见 A2AAgent.cs),其InvokeAsync会把TextContent转换为 A2A 的TextPart,通过Client.SendMessageAsync发送message/send请求,并处理AgentTask(返回 artifacts)与AgentMessage两类响应。
2. 用 AgentKernelFunctionFactory 把 Agent 包装成函数
var agents = await Task.WhenAll(createAgentTasks); var agentFunctions = agents.Select(agent => AgentKernelFunctionFactory.CreateFromAgent(agent)).ToList(); var agentPlugin = KernelPluginFactory.CreateFromFunctions("AgentPlugin", agentFunctions);(代码见 HostClientAgent.cs)
这里发生了关键转换:远程 A2A Agent 被包装为 Kernel 函数,并统一注册进名为AgentPlugin的插件中。这样,Host Agent(一个ChatCompletionAgent)就能像调用本地函数一样调用远程 Agent。
3. 构建 Host Agent 并开启自动函数调用
var builder = Kernel.CreateBuilder(); builder.AddOpenAIChatCompletion(modelId, apiKey); builder.Plugins.Add(agentPlugin); var kernel = builder.Build(); kernel.FunctionInvocationFilters.Add(new ConsoleOutputFunctionInvocationFilter()); this.Agent = new ChatCompletionAgent() { Kernel = kernel, Name = "HostClient", Instructions = "You specialize in handling queries for users and using your tools to provide answers.", Arguments = new KernelArguments(new PromptExecutionSettings() { FunctionChoiceBehavior = FunctionChoiceBehavior.Auto() }), };(代码见 HostClientAgent.cs)
FunctionChoiceBehavior.Auto()让 LLM 自主决定何时调用远程 Agent 函数;ConsoleOutputFunctionInvocationFilter则把每次"调用哪个 Agent、传了什么参数、返回了什么结果"以缩进格式打印到控制台(见 HostClientAgent.cs),这正是运行输出中Calling Agent ... with arguments:片段的来源。
4. 命令行交互循环
Program.cs 中维护一个ChatHistoryAgentThread,循环读取用户输入并调用:
await foreach (AgentResponseItem<ChatMessageContent> response in hostAgent.Agent!.InvokeAsync(message, thread)) { Console.ForegroundColor = ConsoleColor.Cyan; Console.WriteLine($"\nAgent: {response.Message.Content}"); Console.ResetColor(); thread = response.Thread; }每次回答后都会用返回的thread更新对话状态,保证多轮对话上下文连续。整个会话置于 try/catch 中,任何异常都会记录到控制台日志(A2AClientlogger,日志级别为Information,见 Program.cs)。
搭配 A2AServer 跑通完整流程
客户端默认连接的三个远程 Agent 正是由示例 A2AServer 提供的(默认地址为http://localhost:5000/、http://localhost:5001/、http://localhost:5002/)。使用 Chat Completion Agent 方式时,先为服务器设置 OpenAI Key:
dotnet user-secrets set "A2AServer:ApiKey" "..."然后分别启动三个服务器实例(命令来自 A2AClientServer 目录 README):
cd dotnet/samples/Demos/A2AClientServer/A2AServer dotnet run --urls "http://localhost:5000;https://localhost:5010" --agentType "invoice" dotnet run --urls "http://localhost:5001;https://localhost:5011" --agentType "policy" dotnet run --urls "http://localhost:5002;https://localhost:5012" --agentType "logistics"服务器端解析--agentType参数并据此创建对应 Agent(见 A2AServer/Program.cs),随后通过app.MapA2A(hostAgent!.TaskManager!, "/")和app.MapWellKnownAgentCard(...)把 A2A 端点与 Agent Card 都映射到根路径/。三个 Agent 分别内置了不同的系统提示词与能力声明(Agent Card),例如 Policy Agent 固定输出 "Short Shipment Dispute Handling Policy V2.1",定义见 HostAgentFactory.cs。
如果改用 Azure AI Foundry 中的 Agent,则需设置端点并为每个实例传入--agentId:
dotnet user-secrets set "A2AServer:Endpoint" "..." dotnet run --urls "http://localhost:5000;https://localhost:5010" --agentId "<Invoice Agent Id>" --agentType "invoice"(Policy、Logistics 同理,分别使用http://localhost:5001、http://localhost:5002。)
服务器全部就绪后,回到客户端目录执行dotnet run并输入请求,例如:
Customer is disputing transaction TICKET-XYZ987 as they claim the received fewer t-shirts than ordered.完整运行输出(摘自 A2AClientServer 目录 README,实际可见性取决于本次会话调用了哪些 Agent):
info: A2AClient[0] Initializing Semantic Kernel agent with model: gpt-4o-mini User (:q or quit to exit): Customer is disputing transaction TICKET-XYZ987 as they claim the received fewer t-shirts than ordered. Calling Agent InvoiceAgent with arguments: query: TICKET-XYZ987 instructions: Investigate the transaction details for TICKET-XYZ987 ... Response from Agent InvoiceAgent: The invoice associated with the transaction ID TICKET-XYZ987 is for the company Contoso. ... Calling Agent LogisticsAgent with arguments: query: TICKET-XYZ987 instructions: Check the shipping details for TICKET-XYZ987 ... Response from Agent LogisticsAgent: Shipment number: SHPMT-SAP-001 Item: TSHIRT-RED-L Quantity: 900 Calling Agent PolicyAgent with arguments: query: TICKET-XYZ987 instructions: Review the policy regarding disputes and claims ... Response from Agent PolicyAgent: Policy: Short Shipment Dispute Handling Policy V2.1 Summary: "For short shipments reported by customers, ..." Agent: Here's the investigation result for transaction TICKET-XYZ987: 1. **Invoice Details**: The invoice ... indicates that 150 t-shirts were ordered. 2. **Shipment Details**: The logistics records show that a total of 900 t-shirts ... ...从输出可以清晰看到三层结果:先由 Host Agent 自动调用多个远程 Agent 取证,最后汇总成面向用户的最终答案。Invoice Agent 的查询能力来自 InvoiceQueryPlugin.cs 中基于内存模拟数据实现的QueryByTransactionId、QueryInvoices等 Kernel 函数。
验证与调试:REST Client 与 A2A Inspector
在启动客户端之前,可以用仓库自带的 A2AServer.http(Visual Studio 的 .http 文件支持)直接验证每个 Agent 是否可用:
- 查询 Agent Card:
GET {{hostInvoice}}/.well-known/agent-card.json - 发送 A2A 消息:
POST {{hostInvoice}},请求体为 JSON-RPC 2.0 格式的message/send调用,例如:
{ "id": "1", "jsonrpc": "2.0", "method": "message/send", "params": { "id": "12345", "message": { "kind": "message", "role": "user", "messageId": "msg_1", "parts": [ { "kind": "text", "text": "Show me all invoices for Contoso?" } ] } } }也可以使用 A2A Inspector(Web 端工具)在浏览器中连接http://127.0.0.1:8080/并输入 Agent 地址(例如http://host.docker.internal:5000),它会自动校验 Agent Card、发送消息并展示原始 JSON 响应,适合在排查客户端连接问题时使用。
源码速览与注意事项
- 客户端入口:Program.cs —— 配置读取、命令行循环、异常处理;
- 客户端核心:HostClientAgent.cs —— A2AAgent 创建、Agent→函数转换、Host Agent 构建、调用日志过滤器;
- 工程配置:A2AClient.csproj —— 目标框架
net10.0,引用A2A、System.CommandLine、Microsoft.Extensions.Hosting包,以及src/Agents/A2A、src/Agents/Core、src/Connectors/Connectors.OpenAI三个项目; - A2A 协议实现:A2AAgent.cs 与 A2AHostAgent.cs —— 客户端侧的调用封装与服务器侧的任务管理、Agent Card 下发。
几个实践要点:ApiKey是唯一必填配置,缺失会直接抛出异常;ModelId与AgentUrls均有合理默认值,最小的启动实验只需配置ApiKey;AgentUrls务必以分号;分隔多个地址;A2A 协议仍在演进中,示例中的A2AAgent目前只支持文本内容(TextContent),传入其他内容类型会抛出NotSupportedException(见 A2AAgent.cs),在多 Agent 协作场景下这一点需要留意。
【免费下载链接】semantic-kernelIntegrate cutting-edge LLM technology quickly and easily into your apps项目地址: https://gitcode.com/GitHub_Trending/se/semantic-kernel
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考