news 2026/4/17 2:01:12

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/4/16 7:37:09

ChatTTS高可用架构:7x24小时语音服务保障

ChatTTS高可用架构&#xff1a;7x24小时语音服务保障 1. 为什么需要高可用的语音合成服务&#xff1f; 你有没有遇到过这样的情况&#xff1a;刚给客户演示完ChatTTS生成的自然语音&#xff0c;系统突然卡住、网页打不开&#xff0c;或者连续生成几段后声音变僵硬、断句错乱&…

作者头像 李华
网站建设 2026/4/16 7:32:44

GLM-4-9B-Chat-1M开源社区贡献指南:从问题排查到PR提交

GLM-4-9B-Chat-1M开源社区贡献指南&#xff1a;从问题排查到PR提交 1. 开源不是口号&#xff0c;是实实在在的协作过程 第一次打开GLM-4-9B-Chat-1M的GitHub仓库时&#xff0c;我盯着那个绿色的"Contribute"按钮看了好一会儿。它不像其他项目那样写着"Star&qu…

作者头像 李华
网站建设 2026/4/16 7:34:06

Lingyuxiu MXJ LoRA人工智能原理:风格迁移核心技术

Lingyuxiu MXJ LoRA人工智能原理&#xff1a;风格迁移核心技术 最近在AI绘画圈子里&#xff0c;Lingyuxiu MXJ LoRA这个名字挺火的。你可能已经看过用它生成的那些惊艳的唯美人像&#xff0c;皮肤质感通透&#xff0c;光影氛围感十足。但很多人用归用&#xff0c;心里可能有个…

作者头像 李华
网站建设 2026/4/16 5:28:17

GLM-4.7-Flash性能实测报告:MoE架构下推理速度较GLM-4提升300%

GLM-4.7-Flash性能实测报告&#xff1a;MoE架构下推理速度较GLM-4提升300% 最近&#xff0c;智谱AI正式发布了GLM-4.7-Flash——一款专为高性能推理场景深度优化的开源大语言模型。它不是简单的小版本迭代&#xff0c;而是一次架构级跃迁&#xff1a;首次在GLM系列中落地MoE&a…

作者头像 李华
网站建设 2026/4/15 22:17:04

Nano-Banana Studio开源贡献指南:参与模型改进

Nano-Banana Studio开源贡献指南&#xff1a;参与模型改进 1. 开源不是代码提交&#xff0c;而是共同塑造AI的未来 很多人第一次听说“为AI模型做开源贡献”时&#xff0c;下意识觉得这一定是件高门槛的事——得是算法专家、得懂PyTorch底层、得会调参优化。但事实恰恰相反&a…

作者头像 李华