当AI Agent学会“打电话“——微软Agent Framework的A2A与AGUI协议深度解析
微软AgentFramework通过A2A和AGUI协议实现了AI智能体之间的高效协作,构建了类似微服务的分布式AI系统。A2A协议定义了智能体间的通信标准,支持异步任务和上下文保持;AGUI协议则规范了智能体与客户端的交互,支持混合执行模式。这种架构将复杂功能分解到专业智能体,通过标准协议协同工作,既提升了系统扩展性,又降低了维护成本。框架采用.NET实现,提供流式响应、任务续传等特性,适用于从
"如果说单个AI Agent是一个聪明的员工,那么能够互相通信的Agent网络就是一个高效协作的团队。"
引子:从孤岛到生态
你有没有想过这样一个场景:你的客服AI助手在处理客户投诉时,需要查询发票系统、物流系统和政策库,但这三个系统分别由不同的AI Agent管理。传统做法是什么?要么把所有功能塞进一个超级Agent(然后看着它变得臃肿不堪),要么写一堆胶水代码让它们勉强配合(然后在维护时欲哭无泪)。
微软的Agent Framework给出了一个更优雅的答案:让Agent之间像人类同事一样,通过标准化的"语言"直接对话。这就是A2A(Agent-to-Agent)和AGUI(Agent UI)协议的核心价值——不是造一个无所不能的超级英雄,而是建立一个能够高效协作的复仇者联盟。
今天,我们就来深入剖析这套框架的技术内核,看看微软的工程师们是如何用.NET把这个愿景变成现实的。
一、架构哲学:为什么需要Agent间通信协议?
1.1 单体Agent的困境
想象你正在开发一个企业级AI助手。最初,它只需要回答一些简单问题,一切都很美好。但随着业务增长,需求开始失控:
-
财务部门要它能查发票
-
物流部门要它能追踪包裹
-
客服部门要它能处理退款
-
HR部门要它能查考勤...
你会发现,这个Agent变成了一个"什么都懂一点,什么都不精通"的万金油。更糟糕的是,每次某个部门的业务逻辑变更,你都要重新训练整个模型,牵一发而动全身。
1.2 微服务思想在AI领域的映射
软件工程早就给出了答案:微服务架构。把大系统拆成小服务,每个服务专注做好一件事,通过标准接口通信。
Agent Framework的A2A和AGUI协议,本质上就是把这套思想搬到了AI领域:
传统单体Agent → Agent微服务架构
┌─────────────────┐ ┌──────────┐
│ 超级Agent │ │ 主Agent │
│ - 发票查询 │ └────┬─────┘
│ - 物流追踪 │ │ A2A协议
│ - 政策查询 │ ┌─────────┼─────────┐
│ - 客户服务 │ │ │ │
│ - ... │ ┌──▼──┐ ┌──▼──┐ ┌──▼──┐
└─────────────────┘ │发票 │ │物流 │ │政策 │
│Agent │ │Agent │ │Agent │
└─────┘ └─────┘ └─────┘
A2A协议:定义Agent之间如何"打电话"——如何发起请求、传递消息、处理响应 AGUI协议:定义Agent如何与客户端通信——如何流式传输、状态同步、工具调用
二、A2A协议深度剖析:Agent之间的"外交语言"
2.1 协议设计的核心理念
A2A协议基于Google提出的开放标准,微软在Agent Framework中提供了完整的.NET实现。它的设计哲学可以用三个词概括:
-
标准化:就像HTTP之于Web服务,A2A为Agent通信提供统一规范
-
异步优先:支持长时间运行的任务,不会因为一个Agent"思考"太久而阻塞整个系统
-
上下文保持:通过ContextId维护对话连续性,Agent能记住"我们刚才聊到哪了"
2.2 核心概念解析
让我们通过代码来理解A2A的关键抽象:
// A2AAgent: 远程Agent的本地代理
internal sealed class A2AAgent : AIAgent
{
private readonly A2AClient _a2aClient;
private readonly string? _id;
private readonly string? _name;
public override async Task<AgentRunResponse> RunAsync(
IEnumerable<ChatMessage> messages,
AgentThread? thread = null,
AgentRunOptions? options = null,
CancellationToken cancellationToken = default)
{
A2AAgentThread typedThread = this.GetA2AThread(thread, options);
// 构造A2A消息,携带上下文信息
var a2aMessage = CreateA2AMessage(typedThread, messages);
// 通过A2A协议发送消息
A2AResponse? a2aResponse = await this._a2aClient
.SendMessageAsync(new MessageSendParams { Message = a2aMessage },
cancellationToken);
// 处理响应:可能是即时消息,也可能是长时间任务
if (a2aResponse is AgentMessage message)
{
return ConvertToAgentRunResponse(message);
}
else if (a2aResponse is AgentTask task)
{
return ConvertToAgentRunResponse(task);
}
}
}
这段代码揭示了几个关键设计:
1. 透明代理模式
A2AAgent继承自AIAgent,这意味着调用方根本不需要知道它在和远程Agent通信。就像RPC(远程过程调用)一样,本地调用和远程调用在接口层面完全一致:
// 本地Agent
var localAgent = chatClient.CreateAIAgent("本地助手");
await localAgent.RunAsync("你好");
// 远程A2A Agent - 使用方式完全相同!
var remoteAgent = await cardResolver.GetAIAgentAsync();
await remoteAgent.RunAsync("你好");
2. 上下文延续机制
A2AAgentThread维护了两个关键ID:
public sealed class A2AAgentThread : AgentThread
{
public string? ContextId { get; internal set; } // 对话上下文ID
public string? TaskId { get; internal set; } // 当前任务ID
}
-
ContextId:标识一次完整的对话会话,就像你和客服的工单号
-
TaskId:标识Agent正在处理的具体任务,支持任务续传和状态查询
3. 响应类型的二元性
A2A协议支持两种响应模式:
-
AgentMessage:即时响应,适合快速查询(如"今天天气如何?")
-
AgentTask:后台任务,适合耗时操作(如"帮我分析这份100页的报告")
这种设计让框架能够优雅地处理从毫秒级到小时级的各种场景。
2.3 实战案例:多Agent协作的客户投诉处理
让我们看一个真实场景。客户投诉:"我订了1000件T恤,只收到900件!"
传统做法需要人工协调三个系统:
-
查发票确认订单
-
查物流确认发货数量
-
查政策确定处理流程
使用A2A协议,主Agent可以这样编排:
// 主Agent的工具定义
var invoiceAgent = await CreateA2AAgent("http://localhost:5000/");
var logisticsAgent = await CreateA2AAgent("http://localhost:5001/");
var policyAgent = await CreateA2AAgent("http://localhost:5002/");
// 将远程Agent包装为本地工具
var tools = new[] {
invoiceAgent.AsAIFunction(),
logisticsAgent.AsAIFunction(),
policyAgent.AsAIFunction()
};
var hostAgent = chatClient.CreateAIAgent(
name: "客服主管",
instructions: "你负责协调各部门处理客户投诉",
tools: tools
);
// 一句话触发多Agent协作
var response = await hostAgent.RunAsync(
"客户投诉TICKET-XYZ987,声称收到的T恤数量不足"
);
背后发生了什么?主Agent会:
-
调用发票Agent查询订单详情
-
调用物流Agent确认实际发货量
-
调用政策Agent获取处理规范
-
综合信息给出解决方案
整个过程对用户来说是一次对话,但底层是三个专业Agent的精密协作。这就是A2A协议的魅力——让复杂性在架构层面被优雅地分解。
2.4 技术亮点:流式响应与任务续传
A2A协议的流式API设计值得特别关注:
public override async IAsyncEnumerable<AgentRunResponseUpdate>
RunStreamingAsync(
IEnumerable<ChatMessage> messages,
AgentThread? thread = null,
AgentRunOptions? options = null,
CancellationToken cancellationToken = default)
{
var a2aMessage = CreateA2AMessage(typedThread, messages);
// 订阅服务器发送事件(SSE)流
var a2aSseEvents = this._a2aClient
.SendMessageStreamingAsync(
new MessageSendParams { Message = a2aMessage },
cancellationToken);
await foreach (var sseEvent in a2aSseEvents)
{
if (sseEvent.Data is AgentMessage message)
{
yield return ConvertToUpdate(message);
}
else if (sseEvent.Data is AgentTask task)
{
yield return ConvertToUpdate(task);
}
else if (sseEvent.Data is TaskUpdateEvent updateEvent)
{
// 任务进度更新
yield return ConvertToUpdate(updateEvent);
}
}
}
这里使用了C#的IAsyncEnumerable,配合SSE(Server-Sent Events)实现真正的流式传输。用户可以实时看到Agent的"思考过程",而不是傻等一个最终结果。
更巧妙的是任务续传机制:
// 首次调用,Agent开始处理
var response = await agent.RunAsync(message, thread,
new AgentRunOptions { AllowBackgroundResponses = true });
// 如果返回了ContinuationToken,说明任务还在后台运行
if (response.ContinuationToken != null)
{
// 稍后可以用token查询进度
var updatedResponse = await agent.RunAsync([], thread,
new AgentRunOptions {
ContinuationToken = response.ContinuationToken
});
}
这种设计让Agent能够处理真正的长时间任务,比如"分析过去一年的销售数据并生成报告"——用户不需要保持连接,可以过一小时再回来取结果。
三、AGUI协议深度剖析:Agent与客户端的"实时对讲机"
3.1 AGUI vs A2A:定位差异
如果说A2A是Agent之间的"内部通信协议",那AGUI就是Agent与最终用户(或客户端应用)的"对外接口"。
用户应用 <--AGUI--> 主Agent <--A2A--> 专业Agent群
│ │ │
│ │ ├─ 发票Agent
│ │ ├─ 物流Agent
│ │ └─ 政策Agent
│ │
└─ 实时流式交互 └─ 任务编排协调
AGUI的核心特性:
-
完整消息历史:每次请求都发送全部对话历史(类似ChatGPT的做法)
-
丰富的事件类型:不只是文本,还有工具调用、状态更新、错误通知等
-
客户端工具执行:支持在客户端执行某些工具(比如访问本地传感器)
3.2 核心实现:AGUIChatClient的巧妙设计
AGUIChatClient的实现展示了如何在标准IChatClient接口上构建协议适配层:
public sealed class AGUIChatClient : DelegatingChatClient
{
public override async IAsyncEnumerable<ChatResponseUpdate>
GetStreamingResponseAsync(
IEnumerable<ChatMessage> messages,
ChatOptions? options = null,
CancellationToken cancellationToken = default)
{
// 生成或复用Thread ID
var threadId = ExtractThreadIdFromOptions(options)
?? $"thread_{Guid.NewGuid():N}";
var runId = $"run_{Guid.NewGuid():N}";
// 构造AGUI请求
var input = new RunAgentInput
{
ThreadId = threadId,
RunId = runId,
Messages = messages.AsAGUIMessages(),
Tools = options?.Tools?.AsAGUITools()
};
// 发送POST请求,接收SSE流
await foreach (var update in
this._httpService.PostRunAsync(input, cancellationToken)
.AsChatResponseUpdatesAsync(cancellationToken))
{
// 处理不同类型的更新
yield return ProcessUpdate(update);
}
}
}
设计亮点1: 透明的Thread管理
AGUI要求每次请求都带ThreadId,但IChatClient接口并没有这个概念。框架通过AdditionalProperties巧妙地传递这个信息:
// 首次调用,框架自动生成ThreadId
var response = await chatClient.GetResponseAsync(messages);
// 后续调用,ThreadId通过ConversationId传递
var options = new ChatOptions {
ConversationId = response.ConversationId
};
var nextResponse = await chatClient.GetResponseAsync(newMessages, options);
用户感知不到ThreadId的存在,但框架确保了对话的连续性。
设计亮点2: 客户端工具的智能路由
AGUI支持一个强大特性:某些工具在服务端执行,某些在客户端执行。框架如何区分?
// 客户端注册的工具集
var clientToolSet = new HashSet<string>();
foreach (var tool in options?.Tools ?? [])
{
clientToolSet.Add(tool.Name);
}
// 处理工具调用响应
if (update.Contents[0] is FunctionCallContent fcc)
{
if (clientToolSet.Contains(fcc.Name))
{
// 这是客户端工具,交给FunctionInvokingChatClient处理
PrepareForClientExecution(fcc);
}
else
{
// 这是服务端工具,已经执行完毕,直接返回结果
yield return ConvertServerResult(fcc);
}
}
这种设计让同一个Agent可以同时使用服务端能力(如数据库查询)和客户端能力(如读取本地传感器),实现真正的混合执行模式。
3.3 实战案例:智能家居控制Agent
让我们看一个AGUI的典型应用场景——智能家居助手:
// 服务端Agent定义
var agent = chatClient.CreateAIAgent(
name: "智能家居助手",
tools: [
AIFunctionFactory.Create(
() => DateTimeOffset.UtcNow,
name: "get_current_time",
description: "获取当前时间"
),
AIFunctionFactory.Create(
(WeatherRequest req) => GetWeatherForecast(req),
name: "get_weather",
description: "获取天气预报"
)
]
);
// 客户端连接并注册本地工具
var aguiClient = new AGUIChatClient(httpClient, serverUrl);
var clientAgent = aguiClient.CreateAIAgent(
name: "客户端代理",
tools: [
AIFunctionFactory.Create(
(SensorRequest req) => ReadLocalSensors(req),
name: "read_climate_sensors",
description: "读取本地温湿度传感器"
),
AIFunctionFactory.Create(
() => ChangeBackgroundColor(),
name: "change_background_color",
description: "改变界面背景色"
)
]
);
// 用户提问:"现在室内温度多少?外面天气如何?"
await foreach (var update in clientAgent.RunStreamingAsync(userMessage))
{
// Agent会自动决定:
// 1. 调用客户端的read_climate_sensors获取室内温度
// 2. 调用服务端的get_weather获取室外天气
// 3. 综合信息回答用户
foreach (var content in update.Contents)
{
if (content is TextContent text)
{
Console.Write(text.Text); // 实时显示Agent的回答
}
else if (content is FunctionCallContent funcCall)
{
Console.WriteLine($"[调用工具: {funcCall.Name}]");
}
}
}
这个例子展示了AGUI的强大之处:
-
服务端工具(get_weather)可以访问云端API和数据库
-
客户端工具(read_climate_sensors)可以访问本地硬件
-
Agent自动编排两类工具,用户无需关心执行位置
3.4 事件流的精妙设计
AGUI定义了丰富的事件类型,让客户端能够精确感知Agent的每个动作:
// AGUI事件类型
public enum AGUIEventTypes
{
RunStarted, // 运行开始
TextMessageStart, // 文本消息开始
TextMessageContent, // 文本内容(流式)
TextMessageEnd, // 文本消息结束
ToolCallStart, // 工具调用开始
ToolCallArgs, // 工具参数(流式)
ToolCallEnd, // 工具调用结束
ToolCallResult, // 工具执行结果
StateDelta, // 状态增量更新
StateSnapshot, // 状态快照
RunFinished, // 运行完成
RunError // 运行错误
}
这种细粒度的事件设计让客户端可以构建丰富的UI体验:
await foreach (var update in agent.RunStreamingAsync(messages))
{
foreach (var content in update.Contents)
{
switch (content)
{
case TextContent text:
// 逐字显示,模拟打字效果
await TypewriterEffect(text.Text);
break;
case FunctionCallContent funcCall:
// 显示"正在查询天气..."的加载动画
ShowLoadingIndicator($"正在{funcCall.Name}...");
break;
case FunctionResultContent result:
// 隐藏加载动画,显示结果
HideLoadingIndicator();
break;
case ErrorContent error:
// 显示错误提示
ShowErrorNotification(error.Message);
break;
}
}
}
四、架构对比:A2A vs AGUI vs 传统方案
让我们通过一个表格直观对比三种架构方案:
| 维度 | 传统REST API | A2A协议 | AGUI协议 |
|---|---|---|---|
| 通信模式 | 请求-响应 | 消息+任务 | 流式事件 |
| 状态管理 | 无状态/手动管理 | ContextId自动管理 | ThreadId+RunId管理 |
| 长时间任务 | 需要轮询或WebHook | 原生支持ContinuationToken | 支持后台任务 |
| 流式传输 | 需要额外实现 | SSE原生支持 | SSE原生支持 |
| 工具执行 | 仅服务端 | 仅服务端 | 客户端+服务端混合 |
| 协议标准化 | 各自实现 | Google A2A标准 | 新兴标准 |
| 适用场景 | 简单查询 | Agent间协作 | Agent与用户交互 |
选型建议:
-
纯后端Agent协作 → 使用A2A协议
-
需要客户端交互 → 使用AGUI协议
-
简单的单次查询 → 传统REST API足够
五、生产实践:从Demo到Production的关键考量
5.1 性能优化策略
1. 连接池管理
// 不要每次都创建新的HttpClient
// ❌ 错误做法
var client = new HttpClient();
var aguiClient = new AGUIChatClient(client, serverUrl);
// ✅ 正确做法:使用IHttpClientFactory
services.AddHttpClient<AGUIChatClient>();
2. 流式响应的背压控制
// 处理速度跟不上生产速度时,需要背压控制
await foreach (var update in agent.RunStreamingAsync(messages)
.WithCancellation(cancellationToken))
{
// 如果UI渲染太慢,考虑批量处理
await Task.Delay(10); // 给UI喘息的机会
ProcessUpdate(update);
}
3. Agent Card缓存
// Agent Card不会频繁变化,应该缓存
private static readonly ConcurrentDictionary<string, AIAgent> _agentCache = new();
public async Task<AIAgent> GetOrCreateAgentAsync(string url)
{
return _agentCache.GetOrAdd(url, async u =>
{
var resolver = new A2ACardResolver(new Uri(u), _httpClient);
return await resolver.GetAIAgentAsync();
});
}
5.2 错误处理与重试
A2A和AGUI都是网络协议,必须考虑各种异常情况:
public async Task<AgentRunResponse> RunWithRetryAsync(
AIAgent agent,
string message,
int maxRetries = 3)
{
for (int i = 0; i < maxRetries; i++)
{
try
{
return await agent.RunAsync(message);
}
catch (HttpRequestException ex) when (i < maxRetries - 1)
{
// 网络错误,指数退避重试
await Task.Delay(TimeSpan.FromSeconds(Math.Pow(2, i)));
_logger.LogWarning($"重试第{i + 1}次: {ex.Message}");
}
catch (TaskCanceledException ex)
{
// 超时错误,检查是否有ContinuationToken
if (ex.CancellationToken.IsCancellationRequested)
{
throw; // 用户主动取消,不重试
}
_logger.LogWarning("请求超时,尝试使用ContinuationToken恢复");
// 实现续传逻辑...
}
}
throw new Exception("达到最大重试次数");
}
5.3 安全性考虑
1. Agent身份验证
// 服务端:验证调用方身份
app.MapA2A("/", agent, options =>
{
options.RequireAuthorization = true;
options.AllowedOrigins = new[] { "https://trusted-client.com" };
});
// 客户端:提供认证信息
var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", apiKey);
2. 工具权限控制
// 不是所有工具都应该暴露给所有Agent
var agent = chatClient.CreateAIAgent(
name: "受限Agent",
tools: FilterToolsByPermission(allTools, currentUserRole)
);
private IList<AITool> FilterToolsByPermission(
IList<AITool> tools,
string userRole)
{
return tools.Where(t =>
t.Metadata.GetValueOrDefault("RequiredRole") as string
== userRole
).ToList();
}
5.4 可观测性:让Agent行为可追踪
// 集成OpenTelemetry追踪Agent调用链
using var tracerProvider = Sdk.CreateTracerProviderBuilder()
.AddSource("Microsoft.Agents.AI")
.AddConsoleExporter()
.Build();
// 每次Agent调用都会生成Span
await agent.RunAsync(message);
// 在分布式追踪系统中可以看到:
// Span 1: HostAgent.RunAsync
// Span 2: InvoiceAgent.RunAsync (A2A调用)
// Span 3: LogisticsAgent.RunAsync (A2A调用)
// Span 4: PolicyAgent.RunAsync (A2A调用)
六、未来展望:Agent生态的演进方向
6.1 标准化进程
A2A协议目前由Google主导,正在快速演进(当前v2.x,v3.0即将发布)。微软的积极参与表明:
-
跨平台互操作将成为现实:Python的Agent可以调用.NET的Agent
-
Agent市场可能出现:就像Docker Hub一样,开发者可以发布和订阅Agent服务
-
协议生态会更丰富:除了A2A和AGUI,可能出现更多专用协议
6.2 技术演进趋势
1. 更智能的Agent路由
// 未来可能出现的Agent网关
var gateway = new AgentGateway()
.RegisterAgent("invoice", invoiceAgentUrl)
.RegisterAgent("logistics", logisticsAgentUrl)
.WithLoadBalancing() // 负载均衡
.WithCircuitBreaker() // 熔断保护
.WithRateLimiting(); // 限流控制
// 主Agent只需要知道网关地址
var hostAgent = chatClient.CreateAIAgent(
tools: gateway.GetAllAgentsAsTools()
);
2. Agent能力的动态发现
// 不需要硬编码Agent URL,通过服务发现
var discoveryClient = new AgentDiscoveryClient("https://agent-registry.com");
var agents = await discoveryClient.FindAgentsAsync(
capabilities: new[] { "invoice_query", "logistics_tracking" }
);
// 动态组装工具集
var tools = agents.Select(a => a.AsAIFunction()).ToList();
3. 多模态Agent协作
// 未来的Agent可能不只处理文本
var visionAgent = await CreateA2AAgent("https://vision-agent.com");
var audioAgent = await CreateA2AAgent("https://audio-agent.com");
// 用户上传图片+语音,多个Agent协同处理
var response = await hostAgent.RunAsync(
messages: [
new ChatMessage(ChatRole.User, [
new ImageContent(imageBytes),
new AudioContent(audioBytes),
new TextContent("这张图片里的人在说什么?")
])
]
);
七、总结:重新定义AI应用的构建方式
回到文章开头的问题:如何构建一个既强大又灵活的AI系统?
微软Agent Framework通过A2A和AGUI协议给出的答案是:不要试图造一个无所不能的超级Agent,而是建立一个能够高效协作的Agent网络。
这种架构带来的价值是多维度的:
对开发者:
-
关注点分离:每个Agent只需专注自己的领域
-
技术栈自由:不同Agent可以用不同语言、不同模型
-
渐进式演进:新增功能只需添加新Agent,不影响现有系统
对企业:
-
降低维护成本:业务变更只影响对应的Agent
-
提高复用性:同一个Agent可以被多个应用调用
-
增强可扩展性:Agent可以独立扩容和部署
对用户:
-
更好的体验:流式响应让交互更自然
-
更强的能力:多Agent协作解决复杂问题
-
更高的可靠性:单个Agent故障不影响整体服务
当然,这套架构也不是银弹。它引入了分布式系统的复杂性:网络延迟、部分失败、一致性问题等。但就像微服务架构一样,当系统复杂度达到一定程度,这些代价是值得的。
最后的思考:如果说GPT-4让我们看到了单个AI的潜力,那么A2A和AGUI协议让我们看到了AI网络的未来。就像互联网连接了全世界的计算机,Agent协议正在连接全世界的AI。
这不是科幻,这是正在发生的现实。而微软Agent Framework,正是这场变革的重要推动者。
参考资源
更多推荐




所有评论(0)