news 2026/3/19 23:29:39

MAF快速入门(14)快速集成A2A Agent

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MAF快速入门(14)快速集成A2A Agent

大家好,我是Edison。

最近我一直在跟着圣杰的《.NET+AI智能体开发进阶》课程学习MAF开发多智能体工作流,我强烈推荐你也上车跟我一起出发!

上一篇,我们学习了MAF中常见的多智能体编排模式。本篇,我们来了解下在MAF中如何快速集成A2A (Agent to Agent)。

1 A2A协议介绍

在之前的系列文章中我们其实已经介绍过A2A协议了,这里我们快速温习一下。

A2A 即 Agent-to-Agent,翻译过来就是“智能代理之间的协议”,我们可以理解为它就是一个大模型Agent们用来“聊天”的“通用语言”。

A2A定义了一套清晰、标准的沟通方式,让Agent们可以顺畅地交流,让不同平台和框架下的Agent都能够说“同一种话”,实现无障碍的信息交换和协作。

更多关于A2A协议的内容:

多Agent协作入门:基于A2A协议的Agent通信(上)

多Agent协作入门:基于A2A协议的Agent通信(中)

多Agent协作入门:基于A2A协议的Agent通信(下)

2 将A2A Agent封装为Tool

在MAF集成A2A Agent,最主要的操作就是:将A2A Agent封装为一个Tool,这个Tool对应到MAF中就是一个AIFunction对象。

前面我们提到可以将MCP服务也封装为一个Tool(AIFunction)让Agent调用,这里A2A Agent也是一样的道理。

这样做的好处是:让MAF中的Agent像调用本地函数一样调用远程A2A Agent 或 MCP Server。

下面的代码展示了在MAF中将A2A Card转换为Agent,然后再将Agent转换为AIFunction:

......var functionTools = new List<AIFunction>();foreach (var endpoint in agentEndpoints){ var resolver = new A2ACardResolver(new Uri(endpoint)); var card = await resolver.GetAgentCardAsync(); var agent = card.AsAIAgent(); // Convert A2A Agent to AIAgent instance functionTools.AddRange(AgentFunctionHelper.CreateFunctionTools(agent, card));}......

下面是AgentFunctionHelper类的代码实现:

public class AgentFunctionHelper{ public static IEnumerable<AIFunction> CreateFunctionTools(AIAgent a2aAgent, AgentCard agentCard) { foreach (var skill in agentCard.Skills) { AIFunctionFactoryOptions options = new() { Name = Sanitize(skill.Id), Description = $$""" { "description": "{{skill.Description}}", "tags": "[{{string.Join(", ", skill.Tags ?? [])}}]", "examples": "[{{string.Join(", ", skill.Examples ?? [])}}]", "inputModes": "[{{string.Join(", ", skill.InputModes ?? [])}}]", "outputModes": "[{{string.Join(", ", skill.OutputModes ?? [])}}]" } """, }; yield return AIFunctionFactory.Create(RunAgentAsync, options); } async Task<string> RunAgentAsync(string input, CancellationToken cancellationToken) { var response = await a2aAgent.RunAsync(input, cancellationToken: cancellationToken).ConfigureAwait(false); return response.Text; } } private static readonly Regex InvalidNameCharsRegex = new Regex("[^0-9A-Za-z]+", RegexOptions.Compiled); public staticstringSanitize(string name) { return InvalidNameCharsRegex.Replace(name, "_"); }}

其中的CreateFunctionTools方法实现了将A2A Agent的所有公开技能转换为AIFunction工具。

而Sanitize方法则实现了函数名称的规范化,因为AIFunction的名称必须符合一定规范(仅限字母、数字和下划线),因此需要主动对技能名称进行规范化。

3 完整集成示例

这次我们还是使用上次文章中的案例,即一个旅游助手,它可以通过A2A协议调用多个Agent的技能。

我们需要创建四个.NET项目,其中:

  • 1个.NET控制台项目:主助手

  • 3个ASP.NET Web项目:天气智能体、酒店智能体、路线智能体

在VS中的项目结构如下:

本次案例我们希望实现主助手可以回答用户关于不同主题(景点,酒店,天气)的问题,它可以根据问题自主选择需要调用一个或多个Agent去获取必要的信息后进行整合优化后再回复用户。

3.1 天气Agent

添加NuGet包,后续A2A Agent项目都需要安装此包,不再赘述:

A2A.AspNetCore "0.3.3-preview"

创建一个 WeatherAgent类,定义其能力 和 AgentCard,这里我们需要公开一个AgentSkill即天气查询的能力:

public class WeatherAgent{ public void Attach(ITaskManager taskManager) { taskManager.OnMessageReceived = QueryWeatherAsync; taskManager.OnAgentCardQuery = GetAgentCardAsync; } private Task<A2AResponse> QueryWeatherAsync(MessageSendParams messageSendParams, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { return Task.FromCanceled<A2AResponse>(cancellationToken); } // Process the message var messageText = messageSendParams.Message.Parts.OfType<TextPart>().First().Text; // Create and return an artifact var message = new AgentMessage() { Role = MessageRole.Agent, MessageId = Guid.NewGuid().ToString(), ContextId = messageSendParams.Message.ContextId, Parts = [new TextPart() { Text = $""" 🌤️ **天气查询结果** 查询时间:{DateTime.Now:yyyy-MM-dd HH:mm} **北京天气** - 今日:晴转多云,气温 -2°C ~ 8°C - 明日:多云,气温 0°C ~ 10°C - 后日:阴,气温 2°C ~ 9°C **上海天气** - 今日:多云,气温 5°C ~ 12°C - 明日:小雨,气温 6°C ~ 10°C - 后日:阴转晴,气温 4°C ~ 11°C 👔 穿衣建议:北京较冷,建议穿羽绒服;上海温和,建议穿夹克外套,带好雨具。 """ }] }; return Task.FromResult<A2AResponse>(message); } private Task<AgentCard> GetAgentCardAsync(string agentUrl, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { return Task.FromCanceled<AgentCard>(cancellationToken); } var capabilities = new AgentCapabilities() { Streaming = true, PushNotifications = false, }; return Task.FromResult(new AgentCard() { Name = "weather agent", Description = "weather information agent", Url = agentUrl, Version = "1.0.0", DefaultInputModes = ["text"], DefaultOutputModes = ["text"], Capabilities = capabilities, Skills = [ new AgentSkill { Id = "weather-query", Name = "天气查询", Description = "查询指定城市的天气预报,包括温度、降水概率、穿衣建议等", Tags = ["weather", "forecast", "climate"], Examples = ["上海明天天气怎么样", "成都这周的天气预报", "杭州下雨吗"], InputModes = ["text"], OutputModes = ["text"] }], }); }}

这里说明一下,这里为了方便是直接返回了一个固定的天气信息输出内容,但在实际应用中往往需要进行具体的业务逻辑处理 或 调用大模型进行处理。下面的几个Agent也是类似的情况,就不再赘述。

然后,在Program.cs中进行注册,完成端口映射:

using A2A;using A2A.AspNetCore;using WeatherAgentServer; var builder = WebApplication.CreateBuilder(args);var app = builder.Build(); var taskManager = new TaskManager();var agent = new WeatherAgent();agent.Attach(taskManager);// Add JSON-RPC endpoint for A2Aapp.MapA2A(taskManager, "/weather");// Add well-known agent card endpoint for A2Aapp.MapWellKnownAgentCard(taskManager, "/weather");// Add HTTP endpoint for A2Aapp.MapHttpA2A(taskManager, "/weather"); app.Run();

3.2 酒店Agent

创建一个HotelAgent类,定义其能力 和 AgentCard:

public class HotelAgent{ public void Attach(ITaskManager taskManager) { taskManager.OnMessageReceived = QueryHotelsAsync; taskManager.OnAgentCardQuery = GetAgentCardAsync; } private Task<A2AResponse> QueryHotelsAsync(MessageSendParams messageSendParams, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { return Task.FromCanceled<A2AResponse>(cancellationToken); } // Process the message var messageText = messageSendParams.Message.Parts.OfType<TextPart>().First().Text; // Create and return an artifact var message = new AgentMessage() { Role = MessageRole.Agent, MessageId = Guid.NewGuid().ToString(), ContextId = messageSendParams.Message.ContextId, Parts = [new TextPart() { Text = $""" 🏨 **酒店推荐** 根据您的需求,为您推荐以下酒店: **豪华型 ⭐⭐⭐⭐⭐** 1. 上海外滩华尔道夫酒店 - 📍 外滩核心位置,江景房 - 💰 ¥2,500/晚起 - ⭐ 评分 4.9/5.0 **舒适型 ⭐⭐⭐⭐** 2. 上海静安香格里拉大酒店 - 📍 静安寺商圈,交通便利 - 💰 ¥1,200/晚起 - ⭐ 评分 4.7/5.0 **经济型 ⭐⭐⭐** 3. 全季酒店(上海南京路店) - 📍 南京路步行街旁 - 💰 ¥380/晚起 - ⭐ 评分 4.5/5.0 💡 提示:建议提前预订,周末和节假日价格可能上涨 20-50%。 """ }] }; return Task.FromResult<A2AResponse>(message); } private Task<AgentCard> GetAgentCardAsync(string agentUrl, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { return Task.FromCanceled<AgentCard>(cancellationToken); } var capabilities = new AgentCapabilities() { Streaming = true, PushNotifications = false, }; return Task.FromResult(new AgentCard() { Name = "hotel-a2a-agent", Description = "hotel information agent", Url = agentUrl, Version = "1.0.0", DefaultInputModes = ["text"], DefaultOutputModes = ["text"], Capabilities = capabilities, Skills = [ new AgentSkill { Id = "hotel-recommendation", Name = "酒店推荐", Description = "根据目的地和预算推荐合适的酒店,包括豪华型、舒适型、经济型", Tags = ["hotel", "accommodation", "booking", "travel"], Examples = ["推荐上海的酒店", "上海外滩附近有什么好酒店", "预算500以内的北京酒店"], InputModes = ["text"], OutputModes = ["text"] } ], }); }}

同样,请参考天气Agent完成Program.cs中的注册。

3.3 景点Agent

创建一个PlanAgent类,定义其能力 和 AgentCard:

public class PlanAgent{ public void Attach(ITaskManager taskManager) { taskManager.OnMessageReceived = QueryPlansAsync; taskManager.OnAgentCardQuery = GetAgentCardAsync; } private Task<A2AResponse> QueryPlansAsync(MessageSendParams messageSendParams, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { return Task.FromCanceled<A2AResponse>(cancellationToken); } // Process the message var messageText = messageSendParams.Message.Parts.OfType<TextPart>().First().Text; // Create and return an artifact var message = new AgentMessage() { Role = MessageRole.Agent, MessageId = Guid.NewGuid().ToString(), ContextId = messageSendParams.Message.ContextId, Parts = [new TextPart() { Text = $""" 🎡 **景点推荐** 为您推荐上海必游景点: **历史文化类** 1. 🏛️ 外滩 - 欣赏万国建筑博览群 2. 🏯 豫园 - 江南古典园林代表 3. 🕌 城隍庙 - 品尝地道上海小吃 **现代都市类** 4. 🗼 东方明珠塔 - 上海地标,俯瞰浦江两岸 5. 🌆 陆家嘴 - 金融中心,上海之巅 6. 🛍️ 南京路步行街 - 购物天堂 **文艺休闲类** 7. 🎨 田子坊 - 文艺小店聚集地 8. 📚 武康路 - 梧桐树下的法式风情 9. 🌳 世纪公园 - 城市绿肺,亲子游首选 📅 建议游玩时间:3-4 天可覆盖主要景点 """ }] }; return Task.FromResult<A2AResponse>(message); } private Task<AgentCard> GetAgentCardAsync(string agentUrl, CancellationToken cancellationToken) { if (cancellationToken.IsCancellationRequested) { return Task.FromCanceled<AgentCard>(cancellationToken); } var capabilities = new AgentCapabilities() { Streaming = true, PushNotifications = false, }; return Task.FromResult(new AgentCard() { Name = "plan agent", Description = "travel plan & attraction agent", Url = agentUrl, Version = "1.0.0", DefaultInputModes = ["text"], DefaultOutputModes = ["text"], Capabilities = capabilities, Skills = [ new AgentSkill { Id = "attraction-recommendation", Name = "景点推荐", Description = "推荐目的地的热门景点和游玩路线,包括历史文化、现代都市、文艺休闲等类型", Tags = ["attraction", "sightseeing", "tourism", "travel"], Examples = ["上海有什么好玩的", "北京必去的景点", "杭州西湖怎么玩"], InputModes = ["text"], OutputModes = ["text"] } ], }); }}

同样,请参考天气Agent完成Program.cs中的注册。

3.4 主助手

这里我们暂且命名为TravelPlannerClient,在该项目中我们需要用到MAF,因此我们先安装一下相关NuGet包:

Microsoft.Extensions.AI.OpenAIMicrosoft.Agents.AI.A2AMicrosoft.Agents.AI.AbstractionsMicrosoft.Extensions.AI.Abstractions

首先,创建一个ChatClient供主助手使用:

var chatClient = new OpenAIClient( new ApiKeyCredential(openAIProvider.ApiKey), new OpenAIClientOptions { Endpoint = new Uri(openAIProvider.Endpoint) }) .GetChatClient(openAIProvider.ModelId) .AsIChatClient();

其次,将远程A2A Agents转换为AIFunction Tools:

var agentEndpoints = new[]{ "https://localhost:7021/a2a", // hotel agent "https://localhost:7011/a2a", // weather agent "https://localhost:7031/a2a" // plan agent}; // Collecting all AI Toolsvar functionTools = new List<AIFunction>();foreach (var endpoint in agentEndpoints){ var resolver = new A2ACardResolver(new Uri(endpoint)); var card = await resolver.GetAgentCardAsync(); var agent = card.AsAIAgent(); // Convert A2A Agent to AIAgent instance functionTools.AddRange(AgentFunctionHelper.CreateFunctionTools(agent, card));}

然后,创建一个可以调用A2A Agents的主Agent,这一步是核心所在:

var mainAgent = new ChatClientAgent( chatClient: chatClient, instructions: """ 你是一个智能旅行规划助手。你可以利用可用的工具来帮助用户完成任务。 当用户询问时,请使用合适的工具获取信息,然后给出建议。 """, tools: [.. functionTools] );

最后,我们可以做下测试:

// 用户请求 - 测试不同的技能调用var userRequests = new[]{ "查询一下上海的天气情况", "推荐一下上海的酒店", "帮我规划一下今日上海的一日游景点,并告诉我该如何穿衣服",}; foreach (var userRequest in userRequests){ Console.WriteLine("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); Console.WriteLine($"👤 用户请求: {userRequest}"); Console.WriteLine("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); // 执行 Agent Console.WriteLine("⏱️ 主 Agent 处理中..."); var response = await mainAgent.RunAsync(userRequest); Console.WriteLine($"💬 回答:\n{response.Text}"); Console.WriteLine();}

现在,我们来看看测试结果:

case1:查询一下上海的天气情况(简单任务

可以看到,主助手通过调用天气Agent获取天气信息完成了回答。

case2:推荐上海的酒店(简单任务)

可以看到,主助手通过调用酒店Agent获取酒店信息完成了回答。

case3:帮我规划一下今日上海的一日游景点,并告诉我该如何穿衣服(复杂任务)

可以看到,主助手调用了多个Agent(景点Agent 和 天气Agent)获取信息,还在此之上进行了整合优化,最后输出了完善的回复。

4 小结

本文介绍了MAF中集成A2A Agent的核心操作:将A2A Agent转换为AIFunction工具,然后由主Agent自主选择调用最终生成回答,希望本文的案例对你有所帮助。

示例源码

Github: https://github.com/EdisonTalk/MAFD

参考资料

圣杰,《.NET + AI 智能体开发进阶》

年终总结:Edison的2024年终总结

数字化转型:我在传统企业做数字化转型

C#刷算法题:C#刷剑指Offer算法题系列文章目录

C#刷设计模式:C#刷23种设计模式系列文章目录

.NET面试:.NET开发面试知识体系

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

RexUniNLU零样本NLU部署教程:Linux环境Python3依赖安装与端口调试

RexUniNLU零样本NLU部署教程&#xff1a;Linux环境Python3依赖安装与端口调试 1. 为什么你需要RexUniNLU——一个真正开箱即用的中文NLU工具 你是否遇到过这样的问题&#xff1a;手头有一批中文文本&#xff0c;需要快速识别其中的人物、地点、组织&#xff0c;或者想从一段话…

作者头像 李华
网站建设 2026/3/13 6:40:15

BGE-Reranker-v2-m3与向量数据库联动:Milvus集成案例

BGE-Reranker-v2-m3与向量数据库联动&#xff1a;Milvus集成案例 在构建高质量RAG系统时&#xff0c;光靠向量检索往往不够——你可能搜到了很多“看起来相关”的文档&#xff0c;但真正能帮大模型生成准确答案的&#xff0c;可能只有其中一两篇。这时候&#xff0c;重排序&am…

作者头像 李华
网站建设 2026/3/14 12:51:38

Ollama部署ChatGLM3-6B-128K完整教程:含HTTPS反向代理与域名访问配置

Ollama部署ChatGLM3-6B-128K完整教程&#xff1a;含HTTPS反向代理与域名访问配置 1. 为什么选择ChatGLM3-6B-128K 当你需要处理一份长达数万字的技术文档、分析整本PDF报告&#xff0c;或者连续对话中需要记住大量上下文信息时&#xff0c;普通大模型往往会在中途“忘记”前面…

作者头像 李华
网站建设 2026/3/11 15:43:52

通义千问3-VL-Reranker-8B效果展示:学术论文图表+公式+文字重排序

通义千问3-VL-Reranker-8B效果展示&#xff1a;学术论文图表公式文字重排序 1. 这不是普通重排序&#xff0c;是“看懂论文”的能力升级 你有没有试过在几十页的PDF论文里找一张关键图表&#xff1f;或者想快速定位某个公式的推导过程&#xff0c;却要在密密麻麻的文字和符号…

作者头像 李华