AG-UI 有了官方 .NET SDK,Blazor AI 组件同步登场:.NET 的 Agent 应用栈补齐了

导语 :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 应用,我建议现在就把手搭上去,理由有三条:

  1. 组件目前是实验性阶段,API 还会动,现在提 issue 反馈的成本最低、影响力最大------官方明确在征集场景和易用性问题;
  2. AG-UI 的 SDK 已经 stable 落在 NuGet 上,不依赖组件就能先用起来,迁移成本极低;
  3. 如果你有存量 .NET 系统想接 Agent,IChatClient 这个入口意味着你不需要重写架构,只需要在边缘加一层端点。

至于 Blazor AI 组件,我会等它转正再在生产项目里用。 实验性组件在 Agent 这个快速迭代的领域里,API 变动的概率不低,值得观望几个预览版。但 AG-UI 协议本身可以现在就押注------线格式稳定、跨语言兼容、且已经被多个前端框架接受。

你打算用 AG-UI 做什么形态的前端?还是说你的 Agent 场景里,"可回退"是最重要的一条?评论区聊聊。


参考来源