[AG-UI详解-04]如何构建一个兼容AG-UI协议的Agent
Agent是AG-UI协议的核心组件负责处理请求和生成响应。它们为前端应用程序提供了一种标准化的方式使其能够通过一致的接口与AI服务进行通信而无需考虑底层实现方式。与我们针对某个Agent框架MAF或者LangChain谈论的Agent不同AG-UI语境下的Agent本质上是一个供前端应用调用的远程服务。1. AG-UI语境下的AgentAG-UI协议下的Agent是一个具有如下能力和特性的后端服务管理对话状态和消息历史记录处理传入消息和上下文通过事件驱动的流式接口生成响应遵循标准化的通信协议。AG-UI协议中的Agent提供了一系列丰富的功能可以实现复杂的AI交互。要实现一个完整的Agent需要提供这样的功能具体的功能包括1.1 交互式通信Interactive CommunicationAgent通过事件流与前端应用程序建立双向通信通道。这实现了逐字符实时流式响应用户与人工智能之间的即时反馈循环长时间运行操作的进度指示器双向结构化数据交换。1.2 工具使用Tool UsageAgent可以使用工具来执行操作并访问外部资源。重要的是工具由前端应用程序定义并传递给Agent从而实现了灵活且可扩展的系统。工具的调用通过一系列事件进行ToolCallStartEvent表示工具调用的开始ToolCallArgsEvent传递工具调用的参数ToolCallEndEvent表示工具调用的完成。前端应用程序随后可以执行该工具并将结果反馈给Agent。这种双向流程支持复杂的人机协作工作流程Agent可以请求执行特定操作人类可以做出适当的判断来执行这些操作结果反馈给Agent以供其继续推理Agent始终了解流程中做出的所有决策。1.3 状态管理State ManagementAgent维护一个在交互过程中保持不变的结构化状态。该状态可以通过StateDeltaEvent事件进行增量更新通过StateSnapshotEvent事件进行完全刷新可供Agent和前端访问用于存储用户偏好、对话上下文或应用程序状态。1.4 多Agent协作Multi-Agent CollaborationAG-UI支持Agent之间的交接和协作Agent可以将任务委派给其他专业Agent多个Agent可以在协调的工作流程中协同工作Agent之间可以传递状态和上下文前端在Agent切换过程中保持一致的用户体验。比如当需要编程帮助时一般助理Agent可能会将任务转交给专门的编程Agent并将对话内容和具体要求传递给对方。1.5 人机交互工作流Human-in-the-Loop WorkflowAgent支持人类干预和协助Agent可以就特定决策请求人类输入前端可以暂停Agent的执行并在收到人类反馈后恢复执行人类可以在Agent的输出最终确定之前对其进行审查和修改。混合工作流程将人工智能的效率与人类的判断相结合。这使得Agent能够作为协作伙伴而不是自主系统发挥作用。1.6 对话记忆Conversational MemoryAgent会维护完整的对话消息历史记录过去的互动会为未来的回复提供信息消息历史记录在客户端和服务器之间同步消息可以包含丰富的内容文本、结构化数据、引用上下文窗口可以进行管理以便专注于相关信息。1.7 跟踪观测InstrumentationAgent可以发出有关其内部流程的元数据推理步骤性能指标和时间信息来源引用和参考文献跟踪不同响应选项的置信度分数。这使得前端能够提供Agent决策过程的透明度并帮助用户了解结论是如何得出的。2. 构建一个基于SSE的简易版Agent构建一个兼容AG-UI协议的Agent具有两种形式。如果我们使用现有的Agent框架比如MAF和LangChain我们一般会利用框架提供的扩展比如中间件实现与AG-UI的适配。如果我们打算从头开始设计一块Agent框架则可以在设计之初就将针对AG-UI的支持考虑进去。为了演示针对Agent的开发以及它与前端应用的通信方式我们利用ASP.NET Core开发一个极简的Agent后端服务。通过上面的介绍我们知道要开发一个完整的Agent需要实现的东西太多这里作为演示我们做了如下的简化只考虑System、User和Assistant三种角色的消息消息的内容只考虑字符串文本这一种形式不涉及工具调用不涉及基于中断的人机交互整个实现最终体现在如下两个扩展方法上核心方法MapAGUIServer会注册一个通过指定模板创建的路由终结点来处理针对Agent的调用。这个路由终结点与指定的IChatClient进行绑定并利用它调用LLM来生成回答用户的问题。另一个辅助的AddAGUIServer扩展方法旨在注册所需的服务。publicstaticIServiceCollectionAddAGUIServer(thisIServiceCollectionservices);publicstaticIEndpointConventionBuilderMapAGUIServer(thisIEndpointRouteBuilderendpoints,[StringSyntax(route)]stringpattern,IChatClientchatClient,ChatOptions?chatOptionsnull);如下所示的是整个Agent后端的程序。我们创建了一个OpenAIClient对象并将其转换成IChatClient对象。我们将这个IChatClient对象作为参数调用MapAGUIServer扩展方法注册了对应的路由终结点。当前在这之前调用了AddAGUIServer扩展方法对所需的依赖服务进行了注册。usingDotNetEnv;usingMicrosoft.Extensions.AI;usingOpenAI;usingSystem.ClientModel;Env.Load();varendpointEnvironment.GetEnvironmentVariable(OPENAI_BASE_URL)!;varapiKeyEnvironment.GetEnvironmentVariable(OPENAI_API_KEY)!;varbuilderWebApplication.CreateBuilder(args);builder.Services.AddAGUIServer();varappbuilder.Build();varchatClientnewOpenAIClient(newApiKeyCredential(apiKey),newOpenAIClientOptions{EndpointnewUri(endpoint)}).GetChatClient(gpt-5.4-mini).AsIChatClient();app.MapAGUIServer(/,chatClient);awaitapp.RunAsync(http://localhost:5566);接下来我们就来看看最为核心的MapAGUIServer方法是如何实现的。先来看看我们定义的如下几个辅助方法publicstaticclassUtilities{privatestaticreadonlyDictionaryChatRole,stringRoleMapnew(){[ChatRole.User]AGUIRoles.User,[ChatRole.Assistant]AGUIRoles.Assistant,[ChatRole.System]AGUIRoles.System,[ChatRole.Tool]AGUIRoles.Tool,};publicstaticChatMessageAsChatMessage(thisAGUIMessagemessage){varchatMessagemessageswitch{AGUISystemMessagesystemMessagenewChatMessage(ChatRole.System,systemMessage.Content),AGUIAssistantMessageassistantMessagenewChatMessage(ChatRole.Assistant,assistantMessage.Content),AGUIUserMessageuserMessagenewChatMessage(ChatRole.User,[..userMessage.Content.OfTypeAGUITextInputContent().Select(itnewTextContent(it.Text))]),_thrownewArgumentException(AGUIMessage type is not supported.)};chatMessage.MessageIdmessage.Id??NewMessageId();returnchatMessage;}publicstaticstringAsAGUIRole(thisChatRolerole)RoleMap.TryGetValue(role,outvaraguiRole)?aguiRole:role.Value.ToLower();publicstaticstringNewMessageId()$msg-{Guid.NewGuid()};publicstaticTaskWriteEventsAsync(thisHttpResponseresponse,IAsyncEnumerableBaseEventevents,CancellationTokencancellationToken){returnSseFormatter.WriteAsync(WrapAsSseItems(events,cancellationToken),response.Body,SerializeEvent,cancellationToken);}privatestaticasyncIAsyncEnumerableSseItemBaseEventWrapAsSseItems(IAsyncEnumerableBaseEventevents,[EnumeratorCancellation]CancellationTokencancellationToken){awaitforeach(varevtinevents.WithCancellation(cancellationToken).ConfigureAwait(false)){yieldreturnnewSseItemBaseEvent(evt);}}privatestaticvoidSerializeEvent(SseItemBaseEventitem,IBufferWriterbytewriter){usingvarjsonWriternewUtf8JsonWriter(writer);JsonSerializer.Serialize(jsonWriter,item.Data,AGUIJsonSerializerContext.Default.BaseEvent);}}四个辅助方法说明如下AsChatMessage将AGUIMessage类型的消息转换成ChatMessage。正如前面介绍的假设我们只考虑system、assistant和user三种角色的消息并且只考虑文本内容AsAGUIRole将ChatRole类型表示的消息角色转换成AG-UI标准的角色NewMessageId生成一个新的随机字符串作为消息的IDWriteEventsAsync将通过IAsyncEnumerable对象表示的一组异步生成的BaseEvent对象以SSE的方式写入响应。用于注册路由的核心方法MapAGUIServer定义如下。为了提供Session的支持我们将基于ThreadId的对话历史保存静态字段_chatHistories表示的字典中。publicstaticclassAGUIEndpointBuilderExtensions{privatestaticreadonlyConcurrentDictionarystring,ListChatMessage_chatHistories[];publicstaticIEndpointConventionBuilderMapAGUIServer(thisIEndpointRouteBuilderendpoints,[StringSyntax(route)]stringpattern,IChatClientchatClient,ChatOptions?chatOptionsnull){returnendpoints.MapPost(pattern,async([FromBody]RunAgentInputinput,HttpResponseresponse,[FromServices]IOptionsJsonOptionsjsonOptions,CancellationTokencancellationToken){varincomingMessagesinput.Messages.Select(itit.AsChatMessage()).ToArray();varhistory_chatHistories.TryGetValue(input.ThreadId,outvarlist)?list:_chatHistories[input.ThreadId][];varisClientSessionhistory.Select(itit.MessageId).Intersect(incomingMessages.Select(itit.MessageId)).Any();if(!isClientSession){history.AddRange(incomingMessages);}varupdateschatClient.GetStreamingResponseAsync(isClientSession?incomingMessages:history,chatOptions);vareventsupdates.AsBaseEvents(input,jsonOptions.Value.JsonSerializerOptions,isClientSession?null:history);awaitresponse.WriteEventsAsync(events,cancellationToken);});}privatestaticasyncIAsyncEnumerableBaseEventAsBaseEvents(thisIAsyncEnumerableChatResponseUpdateupdates,RunAgentInputinput,JsonSerializerOptionsjsonSerializerOptions,ListChatMessage?historynull){ListAgentResponseUpdate?responseUpdateshistoryisnull?null:[];yieldreturnnewRunStartedEvent{ParentRunIdinput.ParentRunId,RunIdinput.RunId,ThreadIdinput.ThreadId};string?aguiMessageIdnull;awaitforeach(varupdateinupdates){responseUpdates?.Add(newAgentResponseUpdate(update));if(string.IsNullOrEmpty(update.Text)){continue;}varmessageIdupdate.MessageId??Utilities.NewMessageId();if(aguiMessageId!messageId){if(aguiMessageIdisnotnull){yieldreturnnewTextMessageEndEvent{MessageIdaguiMessageId,};}yieldreturnnewTextMessageStartEvent{MessageIdmessageId,Roleupdate.Role!.Value.AsAGUIRole(),Nameupdate.AuthorName,};aguiMessageIdmessageId;}yieldreturnnewTextMessageContentEvent{Deltaupdate.Text,MessageIdaguiMessageId,};}yieldreturnnewTextMessageEndEvent{MessageIdaguiMessageId!,};yieldreturnnewRunFinishedEvent{RunIdinput.RunId,ThreadIdinput.ThreadId,OutcomenewRunFinishedSuccessOutcome()};if(historyisnotnull){varmessagesresponseUpdates!.ToAgentResponse().Messages;history.AddRange(messages);}}}注册的路由处理器采用这样处理来自客户端的调用根据输入提供的ThreadId提取存储的对话历史提取输入携带的消息列表并将它们转换成ChatMessage添加到对话历史中判断输入的消息列表和对话历史是否有交集如果有意味着采用了客户端Session输入的就是整个对话历史此时无需在本地维护Session否则将输入消息添加到对话历史。即使使用了客户端Session初次调用的消息列表也不得冗余存储此为无奈之举。将对话历史作为输入调用IChatClient的GetStreamingResponseAsync并调用AsBaseEvents方法将异步迭代器IAsyncEnumerableBaseEvent对象转换成IAsyncEnumerableBaseEvent其中涉及针对如下五种事件的生成RunStartedEventTextMessageStartEventTextMessageContentEventTextMessageEndEventRunFinishedEvent调用WriteEventsAsync方法以异步形式将生成的事件按照SSE格式写入响应输出流如果没有采用客户端Session响应的消息列表会存储对话历史。另一个针对IServiceCollection的扩展方法AddAGUIServer仅仅按照如下的方式根据AG-UI默认的序列化行为注册了一个IConfigureOptionsJsonOptions类型的服务。publicstaticclassAGUIServiceExtensions{publicstaticIServiceCollectionAddAGUIServer(thisIServiceCollectionservices){services.TryAddEnumerable(ServiceDescriptor.TransientIConfigureOptionsJsonOptions,ConfigureAGUIJsonOptions());returnservices;}internalsealedclassConfigureAGUIJsonOptions:IConfigureOptionsJsonOptions{publicvoidConfigure(JsonOptionsoptions){varchainoptions.SerializerOptions.TypeInfoResolverChain;chain.Add(AgentAbstractionsJsonUtilities.DefaultOptions.TypeInfoResolver!);chain.Add(AGUIJsonSerializerContext.Default.Options.TypeInfoResolver!);}}}3. 使用AG-UI客户端测试既然上面的Agent后端所是针对AG-UI构建的那么原则上任何支持AG-UI的协议的客户端都能与之交互。在AG-UI详解-01:创建一个类似ChatGPT应用与Agent实时交互中我曾经使用过AGUIChatClient来调用后端基于AIAgent构建的AG-UI后端服务我们看看是否可以利用它直接与我们构建的AG-UI Agent交互。如下面的演示程序所示。和AG-UI详解-01中的演示程序相比较我只改动了两点一个是将AGUIChatClient连接的后端地址改成上面构建的ASP.NET Core应用的监听地址。再就是提供以首个消息的方式指定了一个系统指令之前这个指令直接在后端指定。usingMicrosoft.Agents.AI;usingMicrosoft.Agents.AI.AGUI;usingMicrosoft.Extensions.AI;usingHttpClienthttpClientnew(){TimeoutTimeSpan.FromSeconds(60)};varchatClientnewAGUIChatClient(httpClient,http://localhost:5566);varagentchatClient.AsAIAgent(name:AGUIAgent,description:AG-UI Client Agent);varsessionawaitagent.CreateSessionAsync();varinstructionsnewChatMessage(ChatRole.System, 你是一个深谙中国古代历史的专家善于根据正史以公正客观的态度于人交流历史问题。 对于用户提出的问题请以简介概括性的语言予以答复字数尽量保持在200字以内。);ListChatMessagemessages[instructions];while(true){Console.Write(\n$ (:q or quit to exit): );varmessageConsole.ReadLine();if(messageis:qorquit){break;}messages.Add(newChatMessage(ChatRole.User,message));awaitforeach(AgentResponseUpdateupdateinagent.RunStreamingAsync(messages,session)){ChatResponseUpdatechatUpdateupdate.AsChatResponseUpdate();foreach(AIContentcontentinupdate.Contents){if(contentisTextContenttextContent){Console.Write(textContent.Text);}elseif(contentisErrorContenterrorContent){Console.WriteLine($\n[Error:{errorContent.Message}]);}}}messages.Clear();}然后我们利用上述程序构建的对话循环简单讨论了如下这个关于战国纵横家排名的问题可以看出三轮对话还是很流畅的。由于整个流程都是采用流式处理所以响应内容是逐字输出的不会有明显的卡顿。$ (:q or quit to exit): 列举战国最重要的四个纵横家 战国时期最重要、影响最大的纵横家通常可列为以下四人 - 苏秦主张“合纵”游说六国联合抗秦曾佩六国相印是合纵战略代表人物。 - 张仪秦国重要谋士主张“连横”以外交分化六国帮助秦国逐步取得优势。 - 公孙衍早期纵横家代表曾任魏、秦将相积极推动合纵抗秦对战国外交格局影响很大。 - 范雎虽偏重政治谋略但以远交近攻策略著称辅佐秦昭襄王加强秦国统一基础。 其中苏秦、张仪最具代表性被后世并称“纵横家双璧”。 $ (:q or quit to exit): 我个人觉得应该有鲁仲连 你的看法有一定道理。若从“纵横家的典型性”和历史影响看鲁仲连确实常被列入战国顶级纵横家之列甚至有人会用他替代范雎或公孙衍。 鲁仲连是齐国人以游说诸侯、反对强秦著称。他最著名的是赵国邯郸之围时成功说服魏、楚援赵并以“不帝秦”闻名体现了强烈的政治理想色彩。与苏秦、张仪偏重权术外交不同鲁仲连更有士人风骨因此后世评价很高。 若按“纵横家代表性”而言常见组合其实是 苏秦、张仪、公孙衍、鲁仲连。 而范雎更多被视为秦国政治战略家。】 $ (:q or quit to exit): ‌甘茂‌‌‌‌和范雎不相伯仲吧 确实甘茂的地位并不低若论战国中后期秦国的重要谋臣他与范雎可以说各有千秋。 甘茂主要活跃于秦武王时期长于军事与外交结合最著名的是提出并推动“宜阳之战”帮助秦国打通东进通道。他也善于权变游说《战国策》中有不少相关记载带有明显纵横家色彩。 范雎则更偏战略层面。他提出“远交近攻”并推动废除“四贵”干政加强王权对秦国长期统一战略影响更深。因此后世政治史中的地位通常高于甘茂。 如果按“纵横家风格”来说甘茂确实比范雎更接近传统意义上的纵横之士。战国人物本来就很难绝对排名不同标准会得出不同名单。

相关新闻

STGCN时空图卷积网络:交通预测核心技术解析

STGCN时空图卷积网络:交通预测核心技术解析

1. STGCN论文核心思想解析 STGCN(Spatio-Temporal Graph Convolutional Network)是2018年AAAI会议上提出的时空图卷积网络模型,专门用于处理交通预测等时空序列数据。这个模型创新性地将图卷积网络(GCN)与时间卷积网络…

2026/7/24 2:06:05 阅读更多 →
移动机器人技术演进:从AGV到AMR的三大核心突破

移动机器人技术演进:从AGV到AMR的三大核心突破

1. 移动机器人技术演进全景图2008年我参与第一个仓储AGV项目时,激光导航设备还需要进口,单台成本高达80万元。如今国产激光SLAM方案已降至5万元以内,这十年间移动机器人行业经历了三次技术代际跃迁:从早期的磁导引AGV(…

2026/7/24 2:06:05 阅读更多 →
GPT-4应用成本优化:从Token管理到工程实践

GPT-4应用成本优化:从Token管理到工程实践

1. 成本差异背后的技术真相去年我帮两家规模相近的互联网公司做AI方案咨询时,发现一个有趣现象:同样调用GPT-4接口完成智能客服升级,A公司的月账单比B公司高出217%。这让我意识到,大模型应用的成本控制远比想象中复杂。今天我们就…

2026/7/24 2:06:05 阅读更多 →

最新新闻

用了就感觉捡到宝的软件

用了就感觉捡到宝的软件

用了就感觉捡到宝的软件!所有工具下载方法文章末尾提供APP介绍我的物品 好记性不如烂笔头,它就是帮你收录东西放在哪的电子笔头!它界面非常简洁,主页就是要记录的物品分类,内置了多种分类可选,衣服、书籍、…

2026/7/24 2:12:06 阅读更多 →
自动驾驶紧急制动系统(AEB)的验收与测试实践

自动驾驶紧急制动系统(AEB)的验收与测试实践

1. 自动驾驶紧急制动系统的关键挑战在城区复杂路况下,一辆装载L3级自动驾驶系统的测试车以45km/h的速度行驶,突然从右侧岔路冲出一辆横穿马路的电动自行车。系统在0.2秒内完成障碍物识别,0.3秒后触发紧急制动,最终在距行人0.8米处…

2026/7/24 2:12:06 阅读更多 →
MSPM0 I2C DMA触发机制详解:从FIFO事件到高效数据搬运

MSPM0 I2C DMA触发机制详解:从FIFO事件到高效数据搬运

1. 项目概述在嵌入式系统开发中,尤其是涉及实时数据采集、高速通信或低功耗设计的场景,如何高效、可靠地处理外设与内存之间的数据搬运,是每个工程师都会面临的挑战。如果你还在为I2C通信中频繁的字节搬运中断而烦恼,或者苦恼于CP…

2026/7/24 2:12:06 阅读更多 →
深度体验:我如何通过“盗铃医生”洞察身体的隐秘信号【真实感受与健康管理复盘】

深度体验:我如何通过“盗铃医生”洞察身体的隐秘信号【真实感受与健康管理复盘】

深度体验:我如何通过“盗铃医生”洞察身体的隐秘信号在快节奏的现代生活中,我一直处于一种“没病但总觉得哪里不对劲”的状态。虽然每年的常规体检报告总是显示“一切正常”,但那种持续的疲劳感和偶尔的肠胃不适,始终像阴影一样笼…

2026/7/24 2:12:06 阅读更多 →
亿级订单状态流转不乱序:RocketMQ 顺序消息架构实战

亿级订单状态流转不乱序:RocketMQ 顺序消息架构实战

亿级订单状态流转不乱序:RocketMQ 顺序消息架构实战 订单状态流转看起来只是几次字段更新,真正到高并发交易链路里,问题远没有这么简单。CREATE、PAID、CANCEL、REFUND 这些事件一旦被下游乱序处理,用户看到的就不是“偶发延迟”,而是状态回跳、库存反扣、积分重复发放、…

2026/7/24 2:12:06 阅读更多 →
DC-DC电源滤波器与LVDS高速信号完整性的协同设计与优化

DC-DC电源滤波器与LVDS高速信号完整性的协同设计与优化

1. 项目概述:当电源噪声遇上高速信号在任何一个电子系统的核心,都存在着两股至关重要的“能量流”:一股是为芯片和电路提供动力的直流电源,另一股是承载着信息的高速数据信号。这两者看似独立,实则紧密耦合&#xff0c…

2026/7/24 2:11:06 阅读更多 →

日新闻

用Highcharts 创建可拖拽三维散点立方体3D图表

用Highcharts 创建可拖拽三维散点立方体3D图表

该案例基于Highcharts scatter3d 三维散点图实现空间立方体散点可视化,核心特色:三维 X/Y/Z 三轴空间,所有散点分布在 0~10 立方体空间内;散点使用径向渐变实现立体 3D 圆球质感;支持鼠标 / 触屏拖拽画布,…

2026/7/24 0:00:29 阅读更多 →
AppCertDlls:进程创建路径上的 DLL 入口

AppCertDlls:进程创建路径上的 DLL 入口

AppCertDlls:进程创建路径上的 DLL 入口 AppCertDlls 位于 HKLM\System\CurrentControlSet\Control\Session Manager\AppCertDlls。本文的程序功能是只读列出这个键在 64 位和 32 位注册表视图中的全部值,并显示每条值的来源、名称、类型和可安全显示的数…

2026/7/24 0:00:29 阅读更多 →
我的编程之路:第一篇博客

我的编程之路:第一篇博客

大家好,我是一名编程初学者,同时这也是我编程学习之路上的第一篇博客。在这里,我想要向大家介绍我的一些想法和规划。a.自我介绍我是一个刚刚接触编程的新手,目前在学习c语言,我对编程世界充满了强烈的好奇。当然&…

2026/7/24 0:00:29 阅读更多 →

周新闻

Go语言静态资源打包方案对比与实践指南

Go语言静态资源打包方案对比与实践指南

1. 项目背景与核心需求在Go语言开发中,我们经常需要处理静态资源文件的打包问题。无论是Web应用的模板文件、前端资源,还是配置文件、证书等,都需要随程序一起分发。传统做法是将这些文件与编译后的二进制文件放在同一目录下,但这…

2026/7/22 8:58:19 阅读更多 →
Go语言实现高性能LDAP认证服务的架构与实践

Go语言实现高性能LDAP认证服务的架构与实践

1. 项目背景与核心价值LDAP(轻量级目录访问协议)作为企业级身份认证的黄金标准,已经服务了超过80%的财富500强公司。我在金融科技领域实施统一认证体系时,发现传统Java方案存在启动慢、内存占用高等痛点。而Go语言凭借其协程并发模…

2026/7/24 1:23:39 阅读更多 →
【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

更多请点击: https://intelliparadigm.com 第一章:AI面试官实战指南的核心价值与适用场景 AI面试官并非替代人类HR的“黑箱工具”,而是以可解释、可审计、可迭代的方式,赋能招聘全链路的关键基础设施。其核心价值在于将主观经验沉…

2026/7/23 17:49:47 阅读更多 →

月新闻