news 2026/9/13 5:42:18

Semantic Kernel A2A Client 实战:用命令行构建调用远程 Agent 的 A2A 协议客户端

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Semantic Kernel A2A Client 实战:用命令行构建调用远程 Agent 的 A2A 协议客户端

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 配置方法,以及A2AAgentAgentKernelFunctionFactoryFunctionChoiceBehavior.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 并给出最终回答。退出交互循环的方式是在提示符后输入:qquit(这一行为定义在 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 执行:

  1. 创建带 60 秒超时的HttpClient
  2. A2AClient(url, httpClient)建立 A2A 协议客户端;
  3. A2ACardResolver拉取远程服务器的 Agent Card(.well-known端点);
  4. 返回new A2AAgent(client, agentCard)

也就是说,客户端在连接时会先发现并校验远程 Agent 的能力描述(Agent Card),再基于它构造A2AAgentA2AAgent是 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:5001http://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 中基于内存模拟数据实现的QueryByTransactionIdQueryInvoices等 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,引用A2ASystem.CommandLineMicrosoft.Extensions.Hosting包,以及src/Agents/A2Asrc/Agents/Coresrc/Connectors/Connectors.OpenAI三个项目;
  • A2A 协议实现:A2AAgent.cs 与 A2AHostAgent.cs —— 客户端侧的调用封装与服务器侧的任务管理、Agent Card 下发。

几个实践要点:ApiKey是唯一必填配置,缺失会直接抛出异常;ModelIdAgentUrls均有合理默认值,最小的启动实验只需配置ApiKeyAgentUrls务必以分号;分隔多个地址;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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 5:42:00

SSM员工培训考试系统实战:MyBatis Plus与Vue全栈解析

简介&#xff1a;面向企业人事培训与考核场景的员工知识培训考试系统源码&#xff0c;是一套基于SSM&#xff08;SpringSpringMVCMyBatisPlus&#xff09;与Vue的前后端分离实现。项目涵盖题库管理、在线考试、自动评分、成绩统计、员工信息管理等核心功能&#xff0c;同时区分…

作者头像 李华
网站建设 2026/9/13 5:41:33

企业经营分析五大误区与实战解决方案

1. 经营分析常见误区解析作为从业十年的商业分析师&#xff0c;我见过太多企业在经营分析过程中踩坑。今天就来聊聊最常见的5个误区&#xff0c;这些坑我都亲身踩过&#xff0c;希望能帮你少走弯路。文末还准备了实用的分析模板和工具包&#xff0c;都是我们团队在实际项目中验…

作者头像 李华
网站建设 2026/9/13 5:39:50

C++ constexpr编译期优化实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 5:36:06

LoRa凉凉?分清AI的LoRA与无线LoRa,合规落地指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/13 5:35:25

AI Agent双层记忆架构:短期会话缓存与长期用户档案工程实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华