AG-UI 协议
AG-UI(Agent-User Interaction)协议定义了 Agent 后端与 Web 或移动前端之间的通信格式,客户端通过 HTTP 发起一次运行,服务端通过 SSE 持续返回文本、工具调用和状态事件。
这样不同框架、不同编程语言编写的 Agent,可以使用同一套协议,复用一套前端 Agent 交互。
协议独立于 Microsoft Agent Framework,MAF 分别通过 Microsoft.Agents.AI.Hosting.AGUI.AspNetCore 和 Microsoft.Agents.AI.AGUI 提供服务端与 .NET 客户端实现。
AG-UI 协议官网:https://docs.ag-ui.com/introduction
AG-UI 协议和 MAF 差别
采用统一协议后,后端可以连接 CopilotKit 等现成前端,客户端也可以更换服务端实现,而不必重新约定文本流、工具调用和状态事件。审批流程内容较多,下一章再单独展开。
在 .NET 实现中,托管层负责把 MAF 内容转换为协议事件:
| MAF / Microsoft.Extensions.AI 概念 | AG-UI 协议对应 | 说明 |
|---|---|---|
AIAgent | Agent Endpoint | 每个 Agent 映射成一个 HTTP 端点 |
agent.RunStreamingAsync() | SSE 事件流 | 流式响应转成事件 |
AgentResponseUpdate / ChatResponseUpdate | AG-UI 事件 | update 里的各种 AIContent 被翻译成不同事件 |
TextContent | TEXT_MESSAGE_* 事件 | 文本流式 |
FunctionCallContent | TOOL_CALL_START/ARGS/END | 工具调用 |
FunctionResultContent | TOOL_CALL_RESULT | 工具结果 |
DataContent(mediaType=application/json) | STATE_SNAPSHOT | 状态快照 |
DataContent(mediaType=application/json-patch+json) | STATE_DELTA | 状态增量(JSON Patch) |
TextReasoningContent | REASONING_* 事件 | 推理内容或受保护的推理数据 |
AgentSession(ConversationId) | threadId | 会话标识 |
后面的工具和状态管理都建立在这层转换之上。
服务端把每次 Agent 运行转换为一串 BaseEvent,每个事件作为一行 data: {json} 写入 SSE:
| 类别 | 事件 | 作用 |
|---|---|---|
| 运行 | RUN_STARTED、RUN_FINISHED、RUN_ERROR | 标记一次运行的开始、结束或错误 |
| 文本 | TEXT_MESSAGE_START/CONTENT/END | 按 delta 流式传递助手消息 |
| 工具 | TOOL_CALL_START/ARGS/END/RESULT | 传递工具名称、参数和执行结果 |
| 状态 | STATE_SNAPSHOT、STATE_DELTA | 传递完整状态或 JSON Patch 增量 |
| 推理 | REASONING_* | 传递推理文本或受保护的推理数据 |
暴露 AG-UI 服务端点
服务端项目必须使用 Microsoft.NET.Sdk.Web,并安装 AG-UI 托管包。下面使用 Azure OpenAI 创建 Agent:
Microsoft.Agents.AI.Hosting.AGUI.AspNetCore
Microsoft.Agents.AI.OpenAI
最小服务端如下:
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Hosting.AGUI.AspNetCore;
using Microsoft.Extensions.AI;
using OpenAI;
using System.ClientModel;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddHttpClient().AddLogging();
builder.Services.AddAGUIServer(); // 注册 AGUI 的 JSON 多态序列化器
var app = builder.Build();
IChatClient chatClient = new OpenAIClient(
credential: new ApiKeyCredential(key: "1234"),
options: new OpenAIClientOptions { Endpoint = new Uri("http://127.0.0.1:1234/v1") })
.GetChatClient(model: "qwen/qwen3.5-9b")
.AsIChatClient();
AIAgent agent = chatClient.AsAIAgent(
name: "AGUIAssistant",
instructions: "You are a helpful assistant.");
app.MapAGUIServer("/", agent); // 把 Agent 挂到根路径
app.MapControllers();
app.Run();
读取 SSE 事件流
可以用 curl 直接观察原始事件流:
curl -N http://localhost:5216/ \
-H "Content-Type: application/json" \
-H "Accept: text/event-stream" \
-d '{ "messages": [{"role":"user","content":"2+2?"}] }'
返回(每行 data: 是一个事件):

空闲时服务端默认每 15 秒发送一次
: keepalive注释。这是 SSE 保活信息,不属于 AG-UI 事件,客户端可以忽略。
每个事件在 .NET 中都是 BaseEvent 的派生类型。BaseEventJsonConverter 根据 type 字段在协议事件与具体 CLR 类型之间转换,这也是服务端需要调用 AddAGUI() 注册序列化配置的原因。
使用 .NET 客户端
AG-UI 不止是服务端协议,.NET 也提供了客户端 AGUIChatClient(包 Microsoft.Agents.AI.AGUI),它实现了 IChatClient,能把任意 AG-UI 服务端当成本地 IChatClient 用。
Microsoft.Agents.AI.AGUI
Microsoft.Agents.AI
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.AGUI;
using Microsoft.Extensions.AI;
string serverUrl = "http://localhost:5216";
using HttpClient httpClient = new() { Timeout = TimeSpan.FromSeconds(60) };
AGUIChatClient chatClient = new(httpClient, serverUrl);
// 把 AGUIChatClient 包成 AIAgent
AIAgent agent = chatClient.AsAIAgent(name: "agui-client", description: "AG-UI Client");
AgentSession session = await agent.CreateSessionAsync();
List<ChatMessage> messages = new();
while (true)
{
Console.Write("\nUser: ");
string? message = Console.ReadLine();
if (message is ":q" or "quit") break;
if (string.IsNullOrWhiteSpace(message)) continue;
messages.Add(new ChatMessage(ChatRole.User, message));
await foreach (AgentResponseUpdate update in agent.RunStreamingAsync(messages, session))
{
ChatResponseUpdate chatUpdate = update.AsChatResponseUpdate();
foreach (AIContent content in update.Contents)
{
if (content is TextContent text)
Console.Write(text.Text);
else if (content is ErrorContent err)
Console.WriteLine($"\n[Error: {err.Message}]");
}
}
}
AGUIChatClient 把 IChatClient 调用转换为 RunAgentInput,使用 SseParser 读取事件,再转回 ChatResponseUpdate。它还会从消息中提取 application/json 状态,把 options.Tools 转成只有名称、描述和参数 schema 的前端工具声明。
客户端内部使用 FunctionInvokingChatClient 执行前端工具。来自服务端的后端工具调用会包装成 ServerFunctionCallContent,因此不会在客户端重复执行。AG-UI 要求每轮发送完整消息历史,所以客户端在进入这层调用前会移除 ConversationId,避免 FunctionInvokingChatClient 根据会话 ID 裁剪上下文。

此外,我们还可以使用前端 SDK 接入 AGUI,这里不再赘述。