导语 :9 月 25 日和 28 日,微软 .NET 团队连发两篇博客。一篇宣布 AG-UI 协议有了官方 .NET SDK(和 CopilotKit 合作,MIT 协议,已上架 NuGet),另一篇介绍 .NET 11 RC1 里新加的实验性 Blazor AI 组件。放在一起看,这两件事其实是一个意思:微软把 Agent 应用的"界面层"这块拼图,正式补进了 .NET 生态。 这篇文章拆开讲讲这两块拼图各自解决什么问题,以及哪些细节值得你现在就动手试。
一、先说 AG-UI:它治的是"每家框架一套流式格式"的病
Agent 这东西,把传统的请求-响应模型打得很碎。它长时间运行、边干活边吐 token、随时调子智能体、响应到一半插一个工具调用。问题就出在这儿------目前每个 Agent 框架都自己定义了一套流式事件格式。
这就导致前端很苦:得自己解析 chunk、自己跟踪状态、自己把框架的私有事件映射成界面元素。而且这套胶水代码你得一直养着,框架哪天改个事件格式,前端就得跟着改。
AG-UI(Agent-User Interaction Protocol)就是来标准化这件事的。它把 Agent 的整个生命周期拆成一组类型化事件 :RUN_STARTED、TEXT_MESSAGE_CONTENT、STATE_DELTA 等等。前端只管监听这些事件,根本不需要知道对面是 Python 写的还是 C# 写的。
这里有个关键点,很多人体会错了:AG-UI 定义的是"事件",不是"界面"。
它不关心你怎么渲染。事件流出去了,落地成聊天框、终端输出、App 界面,甚至 Slack / Teams 里的消息,AG-UI 都不管。所以同一个后端端点,可以同时喂给好几种前端------这在"一框架配一前端"的时代基本是不可想象的事。
判断一个协议是不是真的做对了,看它划的边界在哪。AG-UI 划得很干净:管传输,不管呈现。
SDK 的关键设计:只依赖 IChatClient
这次微软把 .NET SDK 直接放进了 AG-UI 主仓库,和 TypeScript、Python SDK 并排维护,MIT 协议,NuGet 上直接装。
但我觉得最值得说的一点不是包发下来了,而是它对框架零依赖。
服务端那侧,唯一的集成点是 IChatClient------一个来自 Microsoft.Extensions.AI 的标准聊天抽象。这意味着你根本不需要引入任何 Agent 框架,一个 Worker 服务、一个内部 API、一个跑了十年的存量业务系统,只要能返回 IChatClient,就能自己开口说 AG-UI。
换句话说,微软没有让"必须先上 Agent 框架"成为使用 AG-UI 的前置条件。这个门槛压得很低。
NuGet 上一共五个包:
| 包 | 作用 |
|---|---|
AGUI.Abstractions |
协议模型:事件、消息、工具、状态、源生成的序列化器 |
AGUI.Formatting |
传输格式抽象 + 默认的 SSE(Server-Sent Events)实现 |
AGUI.Protobuf |
可选的 protobuf 编解码(从 TS 的 .proto 生成,覆盖部分事件) |
AGUI.Client |
消费 AG-UI(反向:你的 .NET 应用去连远程 Agent) |
AGUI.Server |
生产 AG-UI(正向:把你的 Agent 变成一个端点) |
用起来很短。服务端核心就两步:
csharp
var context = input.ToChatRequestContext(jsonOptions.Value.SerializerOptions);
var events = chatClient
.GetStreamingResponseAsync(context.Messages, context.ChatOptions, cancellationToken)
.AsAGUIEventStreamAsync(context, cancellationToken);
return TypedResults.ServerSentEvents(events);
ToChatRequestContext 把协议层的 RunAgentInput 拆成 IChatClient 认识的 messages 和 options;AsAGUIEventStreamAsync 再把响应流重新组装成协议事件------它会自动在一次 run 的首尾发 RUN_STARTED / RUN_FINISHED,在切换消息或工具调用前把没闭合的文本和推理块收尾,多个中断也会合并成一个终态事件。
对已有 Agent Framework 用户:改名不改协议
如果你之前就在用 Microsoft Agent Framework(MAF)自带的 AG-UI 支持,编程模型基本没变,有几处 API 改名了:
| 之前 | 现在 |
|---|---|
AddAGUI() / MapAGUI() |
AddAGUIServer() / MapAGUIServer() |
Microsoft.Agents.AI.AGUI 命名空间 |
AGUI.Client / AGUI.Server / AGUI.Abstractions |
AGUIChatClient 位置参数构造 |
基于 Options 的构造函数 |
| 读原始请求 | chatOptions.TryGetRunAgentInput(out RunAgentInput? input) |
但事件格式一个字节都没变。 这意味着你升级后端之后,已有的前端不需要任何改动。MAF 本身也从自带实现切换成了依赖 NuGet 上的 AGUI.* 包。
二、再说 Blazor AI 组件:让 Agent 的流变成 Razor 组件
第一篇讲的是协议,第二篇讲的是呈现。AG-UI 解决"事件怎么传",Blazor AI 组件解决"事件怎么变成界面"。
先交代一下目前的形态:.NET 11 RC1 SDK + prerelease 包,实验性,官方给的反馈入口是 dotnet/aspnetcore 仓库的 issue。上手很快:
console
dotnet add package Microsoft.AspNetCore.Components.AI --prerelease
然后一个组件就能起步:
razor
<ChatPage Agent="_agent" Placeholder="Ask me anything..." />
ChatPage 给你消息列表、输入框、流式状态和重试行为。底层是一个 UIAgent,它包住 IChatClient,消费流式的 ChatResponseUpdate,把内容映射成一组可观察的 ContentBlock。
每个 Block 有身份、有角色、有生命周期状态、有变更通知。数据一到,组件自动重渲染------这就是"流式"在 Blazor 里的实现方式:不是定时器轮询,是状态驱动的响应式。
工具调用被渲染成真正的 UI
这是我觉得最能说明这套设计意图的地方。
Agent 调了 get_weather 这个服务端工具,MAF 在服务端执行,AG-UI 把调用和结果流到浏览器,然后:
csharp
[ToolBlock("get_weather")]
public partial class WeatherToolBlock : FunctionInvocationContentBlock
{
[ToolParameter(Name = "location")]
public string? Location { get; set; }
[ToolResult]
public WeatherInfo? Weather { get; set; }
}
[ToolBlock] 是源生成器 ------编译期就把参数和结果投影成强类型的 block。界面上先用 context.Location 渲染一个"正在获取"的状态,等流式结果到了、块重渲染,就变成了一个完整的天气卡片。
razor
<ChatPage Agent="_agent" Placeholder="Ask for the weather in a city...">
<MessageListContent>
<BlockRenderer TBlock="WeatherToolBlock">
@{ var weather = context.Weather ?? new WeatherInfo(); }
<div class="weather-card">
<div class="weather-card__location">@context.Location</div>
<div class="weather-card__temp">@weather.Temperature°C</div>
@if (!context.HasResult)
{
<div class="weather-card__pending">Fetching weather...</div>
}
</div>
</BlockRenderer>
</MessageListContent>
</ChatPage>
工具执行完全在服务端管道里,但卡片是你的 Blazor 应用的表达------可以用依赖注入、子组件、CSS、本地化。
对比一下现在常见的做法:让模型生成一段 HTML,塞进 iframe 渲染。演示效果同样惊艳,但样式不统一、交互不可控、状态存不下来,而且你拿不回一个 WeatherInfo 对象。组件模型不是这样的。
一个容易被忽略的设计:Blazor 组件不直接耦合 AG-UI
这点我觉得挺有意思的,值得单独说。
微软的措辞很明确:Blazor AI 组件的交互模型参考了 AG-UI,但没有直接绑定 这个协议。接任何 IChatClient 它都能跑;想要 AG-UI 的丰富能力,再在外面套一层 AGUIChatClient 就行。
csharp
public IChatClient CreateChatClient(string endpoint)
{
HttpClient http = httpClientFactory.CreateClient("agentserver");
return new AGUIChatClient(new AGUIChatClientOptions(http, endpoint));
}
UIAgent 之后拿到的还是同一个 IChatClient 抽象------Agent 是远程的、进程内的、还是某个特定厂商的,Blazor 组件这一层完全无感。
做平台的人很清楚,绑定协议意味着放弃生态。这层解耦,懂架构的人会 appreciate。
审批:把方向盘交回给人
Agent 应用最难的不是"让模型说话",是"让人看懂并控制它在干什么"。这块是整套设计里我认为最值钱的部分。
审批。 把有后果的服务端工具(比如 book_meeting)包进 ApprovalRequiredAIFunction,产生的 AG-UI interrupt 会变成一个 FunctionApprovalBlock,界面上就是两个按钮:
razor
<BlockRenderer TBlock="FunctionApprovalBlock" Context="block">
@if (block.Status == ApprovalStatus.Pending)
{
<button @onclick="block.Approve">Approve</button>
<button @onclick="() => block.Reject()">Reject</button>
}
</BlockRenderer>
批准才执行工具,拒绝就带着决定返回、不执行。流式过程中还能通过 AgentContext.CancelAsync 随时叫停。
前端工具。 set_accent_color 这类属于应用本地行为,注册在 ChatOptions.Tools 里,AGUIChatClient 会在 Blazor 侧真实执行------改主题色、开关弹窗、更新本地状态。
可预测状态。 这个设计我最喜欢。Agent 想改你的文档,不会直接覆盖,而是先给一个提案:编辑器显示 diff,旧文档作为基线保留,你点接受才 AcceptPredictiveState(),点拒绝就 RejectPredictiveState() 回滚。运行失败、被取消、或者始终没做决定,未决的提案会自动作废。
这解决的是一个很真实的信任问题:AI 干活的过程必须是可回退的,而不是既成事实。 一个能一键撤销的 AI,和一个不能的,是两种产品。
微软对这条边界的表述很到位:Agent 可以请求一个动作,但"用户看到什么、什么时候能继续"由应用决定。
状态共享:不是替换,是协作
有些 Agent 输出就不该待在消息列表里。UIAgent<TState> 提供强类型应用状态,可以驱动界面的任何部分。
服务端这边,用 AGUIStreamOptions 把工具结果映射成协议事件:
csharp
app.MapAGUIServer("/agentic_generative_ui", agents.CreateAgenticGenerativeUI())
.WithMetadata(new AGUIStreamOptions()
.MapResultAsStateSnapshot("create_plan")
.MapResultAsStateDelta("update_plan_step"));
create_plan 的结果变成 STATE_SNAPSHOT(完整快照),update_plan_step 的结果变成 STATE_DELTA(RFC 6902 的 JSON Patch 增量)。前端通过 StateMapper 把快照和增量应用到强类型状态,Razor 直接渲染成清单,每次更新触发一次重渲染。
Agent 负责生成和更新这些状态,应用决定这些状态看起来长什么样、怎么动。 分工很干净。
顺带一提,组件包故意不内置 Markdown 解析器------你得自己选,或者直接构造节点树。想清楚这个取舍背后的原因:解析器选型是个会反复撕的问题,与其绑定一个默认答案,不如把选择权留给应用。
三、这两篇合起来,其实是一张完整的图
把两次发布按时间线拼一下:
Microsoft.Extensions.AI (抽象层,IChatClient)→ MAF (Agent 框架)→ AG-UI .NET SDK (协议层,AGUI.*)→ ASP.NET Core (暴露端点)→ Blazor AI 组件 (呈现)→ Foundry + Aspire(模型与编排)
中间任何一个环节你都可以替换:不想用 MAF?那就自己写 Agent,只要能返回 IChatClient 就能暴露成 AG-UI 端点。不想让 Blazor 消费?那就用 CopilotKit 之类的 React 前端------AG-UI 线格式是跨语言兼容的,TypeScript 和 Python SDK 直接对接。不想用 Foundry?换任何模型供应商,IChatClient 这一层是通的。
官方还放出了一个 AgenticUI 示例工程(danroth27 维护),把流式结构化输出、服务端工具、前端工具、人工审批、共享状态、可预测状态、生成式 UI、推理摘要这八个场景各拆成一页,Blazor Web App + ASP.NET Core 智能体服务器 + Aspire AppHost,可以直接跑。
推理摘要那页有个细节值得注意:它渲染的是模型主动提供的 reasoning summary (通过 OpenAI Responses API 返回,MAF 转成 AG-UI 的 REASONING_* 事件,ReasoningActivityHandler 累积成一个块),放在一个可折叠面板里,和最终答案分开。不是隐藏的思维链,微软在文档里专门强调了这个区别------这个措辞是刻意的。
写在最后
如果只看一件事:微软这次解决的不是"AI 能不能做界面",而是"Agent 的界面该由谁说了算"。
答案是:协议层由 AG-UI 统一,呈现层由你的应用决定。这两篇博客分别从传输和呈现两端给出了官方答案,而且都不强制你上全栈------一个标准抽象(IChatClient)打通了所有环节。
我印象最深的反而是个不起眼的细节:Blazor AI 组件刻意不耦合 AG-UI 协议。技术上多一层间接,架构上换来的是生态自由度。这种"不把自己的实现塞进用户的地基"的选择,在平台方的发布里其实不常见。
如果你的团队正在做 .NET 侧的 AI 应用,我建议现在就把手搭上去,理由有三条:
- 组件目前是实验性阶段,API 还会动,现在提 issue 反馈的成本最低、影响力最大------官方明确在征集场景和易用性问题;
- AG-UI 的 SDK 已经 stable 落在 NuGet 上,不依赖组件就能先用起来,迁移成本极低;
- 如果你有存量 .NET 系统想接 Agent,
IChatClient这个入口意味着你不需要重写架构,只需要在边缘加一层端点。
至于 Blazor AI 组件,我会等它转正再在生产项目里用。 实验性组件在 Agent 这个快速迭代的领域里,API 变动的概率不低,值得观望几个预览版。但 AG-UI 协议本身可以现在就押注------线格式稳定、跨语言兼容、且已经被多个前端框架接受。
你打算用 AG-UI 做什么形态的前端?还是说你的 Agent 场景里,"可回退"是最重要的一条?评论区聊聊。
参考来源
- Microsoft .NET Blog ------《AG-UI Protocol now has a first-class .NET SDK》(2026-09-25,Daniel Roth)
- Microsoft .NET Blog ------《Build Agentic UI with the new Blazor AI components》(2026-09-28,Daniel Roth)
- AG-UI 官方文档 / AG-UI .NET SDK 源码与示例
- AgenticUI 示例工程
- MAF AG-UI 集成文档