[Loop Engineering在MAF中的实现-02]循环调用在LoopAgent中的实现
在上一篇文章中我们介绍了Loop Engineering基本原理并通过两个简单的示例分别演示使用LoopAgent和HarnessAgent来实现Agent循环调用的基本方法。在本篇文章中我们将介绍LoopAgent在MAF的中定义和背后的设计和实现原理。1. 利用LoopEvaluator评估每次调用迭代Loop Engineering并不是简单地循环调用Agent必须对每次调用的结果实施评估。如果满足预先设定的条件则停止循环调用否则继续调用。如果决定继续还应该将评估的结果反馈给下一次调用迭代。这样的评估工作落在了具有如下定义的LoopEvaluator的肩上。publicabstractclassLoopEvaluator{publicabstractValueTaskLoopEvaluationEvaluateAsync(LoopContextcontext,CancellationTokencancellationTokendefault);}LoopEvaluator这个抽象类定义了唯一的抽象方法EvaluateAsync作为输入的是表示当前循环调用上下文的LoopContext对象评估的结果体现为返回的LoopEvaluation对象上。1.1 LoopContextLoopEvaluator的任务是对上一次调用Agent生成的结果实施评估所以被评估的核心材料有三个分别是描述原始任务的初始消息列表、上一次Agent返回的响应和之前提供的评估反馈。它们分别体现在作为循环上下文对象LoopContext的InitialMessages、LastResponse和Feedback属性上。除了这三个核心输入之外LoopContext还提供了其他一些属性来描述当前循环调用的上下文信息publicsealedclassLoopContext{publicAIAgentAgent{get;}publicAgentSessionSession{get;}publicIReadOnlyListChatMessageInitialMessages{get;}publicAgentRunOptions?RunOptions{get;}publicintIteration{get;}publicAgentResponseLastResponse{get;}publicIReadOnlyListstring?Feedback{get;}publicAdditionalPropertiesDictionaryAdditionalProperties{get;}newAdditionalPropertiesDictionary();}LoopContext承载的上下文信息包括Agent当前循环调用的Agent对象Session当前循环调用的Agent会话对象InitialMessages描述原始任务的初始消息列表RunOptions当前循环调用的Agent运行选项Iteration当前循环调用的迭代次数LastResponse上一次Agent调用的响应结果Feedback之前的评估结果反馈列表AdditionalProperties一个字典对象用于存储额外的上下文信息开发者可以在评估过程中使用这个字典来传递自定义的上下文数据。1.2 LoopEvaluation表示评估结果的LoopEvaluation类型定义如下。它利用ShouldReinvoke属性来指示是否继续循环调用利用Feedback属性来提供评估的反馈信息。Feedback属性提供的文本会转换成一条角色为User消息反馈给后续的跌倒。我们也可以直接利用Messages属性来提供反馈消息而这只能选其一。publicsealedclassLoopEvaluation{publicboolShouldReinvoke{get;}publicstring?Feedback{get;}internalIReadOnlyListChatMessage?Messages{get;}privateLoopEvaluation(boolshouldReinvoke,string?feedback,IReadOnlyListChatMessage?messages){ShouldReinvokeshouldReinvoke;Feedbackfeedback;Messagesmessages;}}由于LoopEvaluation的构造函数是私有的所以我们需要通过静态方法来创建LoopEvaluation对象。LoopEvaluation提供了三个静态方法publicsealedclassLoopEvaluation{privatestaticreadonlyLoopEvaluations_stopnewLoopEvaluation(shouldReinvoke:false,null,null);publicstaticLoopEvaluationStop()s_stop;publicstaticLoopEvaluationContinue(string?feedbacknull)newLoopEvaluation(shouldReinvoke:true,feedback);publicstaticLoopEvaluationContinueWithMessages(IEnumerableChatMessagemessages)newLoopEvaluation(shouldReinvoke:true,null,(messagesasIReadOnlyListChatMessage)??messages.ToList());}三个静态方法分别是Stop表示停止循环调用以单例形式返回一个LoopEvaluation对象ShouldReinvoke属性为falseContinue表示继续循环调用ShouldReinvoke属性为true并可提供一个反馈信息但是Messages为nullContinueWithMessages表示继续循环调用并提供下一次调用的消息列表但是Feedback为null。2. LoopAgentOptions在正式介绍LoopAgent之前我们先来看看它对应的配置选项类型LoopAgentOptions的定义。LoopAgentOptions提供的配置选项循环迭代Agent的行为作相应的控制。publicsealedclassLoopAgentOptions{publicint?MaxIterations{get;set;}publicboolFreshContextPerIteration{get;set;}publicstring?OnBehalfOfAuthorName{get;set;}publicboolExcludeOnBehalfOfMessages{get;set;}publicboolNonStreamingReturnsLastResponseOnly{get;set;}publicFuncAgentSession,CancellationToken,ValueTask?SessionCreatedCallback{get;set;}}具体的配置选项包括MaxIterations最大循环迭代次数如果为null则表示没有限制FreshContextPerIteration是否在每次迭代中使用新的Agent上下文true每次迭代都会重建输入消息只保留原始输入 聚合的反馈日志不会把前面的对话历史带入false每次迭代会在已有的对话基础上追加反馈。此为默认值。OnBehalfOfAuthorName给循环LoopEvaluator自动注入的消息打上作者标记区分哪些消息是循环自动生成的哪些是用户真实输入。默认值为null意味着不标记ExcludeOnBehalfOfMessages是否在下一次迭代中排除掉LoopEvaluator注入的消息false调用方能看到循环的自主行为此为默认值true调用方只看到封装Agent的响应不看到循环注入的消息。NonStreamingReturnsLastResponseOnly控制非流式运行的返回策略false返回完整的聚合消息包括所有迭代的消息此为默认值true 只返回最后一次迭代的响应。FreshContextPerIteration配置选项还体现了针对Session的控制。如果此选项设置为true,意味着如果调用方提供了AgentSession此AgentSession会以序列化的形式生成一个快照每次迭代都会利用此快照重建AgentSession对象并以此调用Agent否则循环会在每次迭代创建一个新的AgentSession。3. LoopAgent作为一个继承自DelegatingAIAgent的Agent中间件我们在构建一个LoopAgent的时候除了需要执行innerAgent还可以指定一个或者多个LoopEvaluator。在实施评估的时候会依次调用每个LoopEvaluator直到返回一个ShouldReinvoke为false的LoopEvaluation。也就是说只有所有LoopEvaluator决定循环可以终止才可以阻止迭代循环前提是迭代次数没有超过MaxIterations选项的设置。publicsealedclassLoopAgent:DelegatingAIAgent{publicLoopAgent(AIAgentinnerAgent,LoopEvaluatorevaluator,LoopAgentOptions?optionsnull,ILoggerFactory?loggerFactorynull);publicLoopAgent(AIAgentinnerAgent,IEnumerableLoopEvaluatorevaluators,LoopAgentOptions?optionsnull,ILoggerFactory?loggerFactorynull);protectedoverrideasyncTaskAgentResponseRunCoreAsync(IEnumerableChatMessagemessages,AgentSession?sessionnull,AgentRunOptions?optionsnull,CancellationTokencancellationTokendefault);protectedoverrideasyncIAsyncEnumerableAgentResponseUpdateRunCoreStreamingAsync(IEnumerableChatMessagemessages,AgentSession?sessionnull,AgentRunOptions?optionsnull,CancellationTokencancellationTokendefault);}LoopAgent针对innerAgent的循环调用实现在重写的RunCoreAsync和RunCoreStreamingAsync方法中在整个流程的执行受LoopAgentOptions配置选项的控制。我们将在下篇对整个循环的执行流程进行系统的介绍接下来我们简单介绍LoopAgent默认采用的循环迭代流程。4. 默认的循环迭代流程为了探测在默认情况下保留LoopAgentOptions的默认配置LoopAgent采用怎样的方式循环调用Agent和实施评估我们定义了如下这个基于迭代次数的LoopEvaluator。顾名思义这个InterationCountEvaluator单纯判断迭代次数是否超过设置的阈值决定是否继续相当于MaxIterations配置选项。在重写的EvaluateAsync方法中我们会输出LoopContext上下文提供的迭代次数、原始消息、上一次响应消息、之前的返回以及当前提供的反馈如果决定继续。classInterationCountEvaluator(intallowedIterations):LoopEvaluator{publicoverrideValueTaskLoopEvaluationEvaluateAsync(LoopContextcontext,CancellationTokencancellationTokendefault){Console.WriteLine(${newstring(-,30)}Evaluator{newstring(-,30)}Iteration:{context.Iteration}Initialmessages:{string.Join(\n ,context.InitialMessages.Select(it${it}))}Lastresponse:{string.Join(\n ,context.LastResponse.Messages.Select(it${it}))}Previousfeedback:{string.Join(\n ,context.Feedback)});if(context.IterationallowedIterations){returnValueTask.FromResult(LoopEvaluation.Stop());}varfeedback$Feedback for iteration{context.Iteration};Console.WriteLine($Feedback:{feedback}\n);returnValueTask.FromResult(LoopEvaluation.Continue(feedback));}}为了确定LoopAgent调用inerAgent提供的消息和返回的响应我们定义了如下这个FakeChatClient来模拟与LLM的交互。在重写的GetResponseAsync方法中我们调用ChatMessageGenerator的静态方法GenerateMessage方法生成一个响应消息消息内容会携带自增的序号。我们会输出传入的消息和响应的消息。classFakeChatClient:IChatClient{publicvoidDispose(){}publicTaskChatResponseGetResponseAsync(IEnumerableChatMessagemessages,ChatOptions?optionsnull,CancellationTokencancellationTokendefault){varmessageChatMessageGenerator.GenerateMessage(Response message from LLM.,ChatRole.Assistant);Console.WriteLine(${newstring(-,30)}LLM{newstring(-,30)}Incomingmessages:{string.Join(\n ,messages.Select(it${it}))}Outgoingresponse:{message});returnTask.FromResult(newChatResponse(message));}publicobject?GetService(TypeserviceType,object?serviceKeynull)null;publicIAsyncEnumerableChatResponseUpdateGetStreamingResponseAsync(IEnumerableChatMessagemessages,ChatOptions?optionsnull,CancellationTokencancellationTokendefault)thrownewNotImplementedException();}internalstaticclassChatMessageGenerator{privatestaticint_counter0;publicstaticChatMessageGenerateMessage(stringcontent,ChatRolerole){returnnewChatMessage(role:role,content:$[{Interlocked.Increment(ref_counter)}]{content});}}整个演示程序很简单。如下面的代码所示我们创建了一个FakeChatClient对象并将其转换成AIAgent然后根据它和创建InterationCountEvaluator对象将LoopAgent创建出来并将允许的迭代次数设置为3。usingMicrosoft.Agents.AI;usingMicrosoft.Extensions.AI;varagentnewLoopAgent(innerAgent:newFakeChatClient().AsAIAgent(),evaluator:newInterationCountEvaluator(3));ListChatMessageinput[ChatMessageGenerator.GenerateMessage(Original message from user.,ChatRole.User),ChatMessageGenerator.GenerateMessage(Original response from LLM.,ChatRole.Assistant),ChatMessageGenerator.GenerateMessage(Original message from user.,ChatRole.User),];varresponseawaitagent.RunAsync(input);varindex1;foreach(varmessageinresponse.Messages){Console.WriteLine(${newstring(-,20)}Message{index}{newstring(-,20)}{message});}我们调用ChatMessageGenerator的GenerateMessage方法创建了三个消息我们将它们组成消息列表作为输入调用LoopAgent的RunAsync后输出响应携带的所有消息。程序运行后针对FakeChatClient和InterationCountEvaluator的迭代调用会体现在如下的输出中------------------------------LLM------------------------------ Incoming messages: [1]Original message from user. [2]Original response from LLM. [3]Original message from user. Outgoing response: [4]Response message from LLM. ------------------------------Evaluator------------------------------ Iteration: 1 Initial messages: [1]Original message from user. [2]Original response from LLM. [3]Original message from user. Last response: [4]Response message from LLM. Previous feedback: Feedback:Feedback for iteration 1 ------------------------------LLM------------------------------ Incoming messages: [1]Original message from user. [2]Original response from LLM. [3]Original message from user. [4]Response message from LLM. Feedback for iteration 1 Outgoing response: [5]Response message from LLM. ------------------------------Evaluator------------------------------ Iteration: 2 Initial messages: [1]Original message from user. [2]Original response from LLM. [3]Original message from user. Last response: [5]Response message from LLM. Previous feedback: Feedback for iteration 1 Feedback:Feedback for iteration 2 ------------------------------LLM------------------------------ Incoming messages: [1]Original message from user. [2]Original response from LLM. [3]Original message from user. [4]Response message from LLM. Feedback for iteration 1 [5]Response message from LLM. Feedback for iteration 2 Outgoing response: [6]Response message from LLM. ------------------------------Evaluator------------------------------ Iteration: 3 Initial messages: [1]Original message from user. [2]Original response from LLM. [3]Original message from user. Last response: [6]Response message from LLM. Previous feedback: Feedback for iteration 1 Feedback for iteration 2从输出可以看出整个流程经历三次针对LLMEvaluator的迭代调用。Evaluator的输入只有原始的消息列表上一次的响应之前的评估反馈。调用LLM提供的对话历史则包括原始的消息列表之前LLM的响应Evaluator提供的反馈。作为LoopAgent最终的响应它携带的消息体现在如下的输出中。可以看出LLM的响应和Evaluator的反馈均在其中。--------------------Message1-------------------- [4]Response message from LLM. --------------------Message2-------------------- Feedback for iteration 1 --------------------Message3-------------------- [5]Response message from LLM. --------------------Message4-------------------- Feedback for iteration 2 --------------------Message5-------------------- [6]Response message from LLM.如果你希望看到LoopAgent完整的循环流程请看Loop Engineering在MAF中的实现-03:Agent循环调用流程详解

相关新闻

用 PTerm 的 Section 打印机为长输出划分章节:从 DefaultSection 到自定义样式

用 PTerm 的 Section 打印机为长输出划分章节:从 DefaultSection 到自定义样式

开发工具 【免费下载链接】pterm ✨ PTerm is a modern Go module to easily beautify console output. Featuring charts, progressbars, tables, trees, text input, select menus and much more 🚀 Its completely configurable and 100% cross-platform compati…

2026/10/15 3:12:37 阅读更多 →
foobox-cn 上手指南:给 foobar2000 换一套顺手的新界面

foobox-cn 上手指南:给 foobar2000 换一套顺手的新界面

foobox-cn 上手指南:给 foobar2000 换一套顺手的新界面 【免费下载链接】foobox-cn DUI 配置 for foobar2000 项目地址: https://gitcode.com/GitHub_Trending/fo/foobox-cn foobar2000 的内核功能很强,但默认界面确实朴素:灰底面板、…

2026/10/15 3:12:37 阅读更多 →
智诺方AI|期刊论文导师意见修改,分段改写搞定查重和AIGC

智诺方AI|期刊论文导师意见修改,分段改写搞定查重和AIGC

写好「导师意见修改」,再谈降重与降 AIGC 先把研究内容写清楚,再处理重复率与 AI 痕迹,顺序不能反。导师意见修改最怕「改了表述、丢了要求」。意见若被 AI 一键稀释,返回稿看起来更顺,却没回答导师真正卡住的点。 在期…

2026/10/15 3:11:37 阅读更多 →

最新新闻

一种可快速恢复的Zabbix高可用架构设计

一种可快速恢复的Zabbix高可用架构设计

做运维的朋友们可能都有过这样的深夜惊魂:好容器完成了监控的Server、Proxy的部署、告警的收敛,监控的覆盖率也达标了,结果承载Zabbix Server和PostgreSQL的机器突然宕机。那一刻,不是“少看几张图”的问题,而是整个团…

2026/10/15 4:01:45 阅读更多 →
2026网站创建用什么ai建站系统?科技赋能才能事半功倍!

2026网站创建用什么ai建站系统?科技赋能才能事半功倍!

2026网站创建用什么AI建站系统?科技赋能才能事半功倍!中国信通院2026年初发布的《中小企业数字化发展白皮书》显示,国内超六成中小企业将“低成本快速搭建线上阵地”列为数字化转型首要需求,AI建站系统凭借低门槛、高效率、全链路…

2026/10/15 4:01:45 阅读更多 →
做了这么久技术博主,我从后台私信里看透了这几种人

做了这么久技术博主,我从后台私信里看透了这几种人

做技术专栏这么久,后台私信几千条,我现在是分情况处理的——但一开始不是。 刚开号那会儿我每条都回。有人问问题,我认认真真写解答;有人要加微信,我不好意思拒绝;有人半夜甩过来一整段代码让帮忙看&#x…

2026/10/15 4:01:45 阅读更多 →
从零开始用Canvas开发五子棋网页游戏:技术选型、AI博弈与实战解析

从零开始用Canvas开发五子棋网页游戏:技术选型、AI博弈与实战解析

1. 项目设计与技术选型思路1.1 这个项目到底在做什么五子棋网页版小游戏,是前端练手项目里出现频率极高的一种。它看起来简单,但如果把完整需求列出来,会发现麻雀虽小五脏俱全:棋盘渲染、落子交互、胜负判定、悔棋、重置、人机对战…

2026/10/15 4:01:45 阅读更多 →
Superpowers:基于浏览器的开源协作游戏开发平台

Superpowers:基于浏览器的开源协作游戏开发平台

最近好几个做独立游戏的朋友都在问同一个词:superpowers。我一开始以为他们说的是某款超能力题材的新游戏,直到有位朋友发来一张浏览器截图,我才反应过来——他们问的是开源协作游戏开发工具 Superpowers,一个真正能让你不装全家桶…

2026/10/15 4:01:45 阅读更多 →
2026大厂AI薪资大起底:普通研发卷到40万,Agent开发狂撒80万年薪?

2026大厂AI薪资大起底:普通研发卷到40万,Agent开发狂撒80万年薪?

2026年大厂AI薪资揭秘:普通研发岗薪资锚定40万左右,但竞争激烈;AI Agent开发岗(50-80K15薪)对标SSP,高薪且门槛低,企业抢“应用落地”人才;顶级AI科学家包揽80-220万年薪。求职建议分…

2026/10/15 4:00:45 阅读更多 →

日新闻

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

1. 为什么选择容器化方式部署 LoRa 网络服务LoRa 物联网项目落地时,网络服务器是整个链路的核心枢纽。它负责接收网关上报的数据包、完成去重与解密、再把有效载荷分发给上层应用。早期大家习惯把网络服务器直接装在物理机上,依赖一堆系统库和特定版本的…

2026/10/15 0:00:46 阅读更多 →
共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

1. 共享推理服务里,扩缩容到底难在哪做过大模型推理服务部署的人,大概都经历过这样的场景:一个团队共用一套推理集群,A 业务跑的是 7B 的小模型,B 业务跑的是 72B 的大模型,C 业务白天流量高、晚上几乎归零…

2026/10/15 0:00:46 阅读更多 →
Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

1. 引子:一次内核恐慌引发的追查先从一个真实场景说起。某次我给一台老旧的嵌入式设备折腾系统裁减,为了把启动时间压进两秒以内,我手贱地把一串自认为“没用”的 systemd 服务给 disable 了,其中就包括systemd-remount-fs.servic…

2026/10/15 0:00:46 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/14 16:30:29 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 0:14:36 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 7:53:34 阅读更多 →