[Loop Engineering在MAF中的实现-04]利用AIJudgeLoopEvaluator让LLM当评委 本系列的首篇文章基于评估结果针对Agent的循环调用演示了一个利用LoopAgent构建了一个用来进行诗词创作的Agent具体的做法是通过封装AIAgent自定义一个LoopAEvaluator借助LLM的能力对创作的作品进行评估并打分。如果分数达到要求循环终止否则让循环继续并提供反馈。我们也可以利用预定义的AIJudgeLoopEvaluator评估器来达到相同的目的。1. 利用AIJudgeLoopEvaluator来评估传作的作品在如下所示的演示程序中我们将创作的任务和要求都塞进提示词。我们定义了辅助方法CreateChatClient帮助我们创建一个基于OpenAI的IChatClient对象。我们两次调用此方法分别利用返回的IChatClient构建了LoopAgent的innerAgent和evaluator后者的类型为AIJudgeLoopEvaluator。usingAzure.AI.Projects;usingMicrosoft.Agents.AI;usingMicrosoft.Extensions.AI;usingOpenAI;usingSystem.ClientModel;conststringprompt 基于如下这首《卫风·氓》的背景和情感基调创作一首宋词。 氓之蚩蚩抱布贸丝。匪来贸丝来即我谋。 送子涉淇至于顿丘。匪我愆期子无良媒。 将子无怒秋以为期。 乘彼垝垣以望复关。不见复关泣涕涟涟。 既见复关载笑载言。尔卜尔筮体无咎言。 以尔车来以我贿迁。 桑之未落其叶沃若。于嗟鸠兮无食桑葚 于嗟女兮无与士耽士之耽兮犹可说也 女之耽兮不可说也。 桑之落矣其黄而陨。自我徂尔三岁食贫。 淇水汤汤渐车帷裳。女也不爽士贰其行。 士也罔极二三其德。 三岁为妇靡室劳矣夙兴夜寐靡有朝矣。 言既遂矣至于暴怒。兄弟不知咥其笑矣。 静言思之躬自悼矣。 及尔偕老老使我怨。淇则有岸隰则有泮。 总角之宴言笑晏晏。信誓旦旦不思其反。 反是不思亦已焉哉 如果按照如下的标准打分要求创作的宋词必需在80分满分100以上才算合格1.格律规范(MetricalAccuracy)——30分 检测AI生成的词是否真正符合该词牌的“说明书”。-词牌结构20分总字数、分片、每句的字数长短必须与词牌名保持一致。-平仄规范5分关键位置的平仄平声、仄声必须严格符合词牌要求。-押韵规则5分检查是否在规定位置押韵是否混淆了平仄韵有无出韵。2.意境创设(ImageryConception)——30分 评估词作是否具备古典美感是否能画出**画面感**。-意象选用15分是否恰当使用了符合古典美学的意象。意象之间是否逻辑自洽没有现代感违和物。-流派风格15分整体意境的深远程度。3.语言艺术(LinguisticArtistry)——20分 评估遣词造句的功底和文字的流畅度。-用词典雅10分遣词造句需有**词味**杜绝使用现代大白话或过于生硬的拼凑词。-对仗与过片10分若词牌要求对仗需检查是否工整重点评估**过片**上下片过渡是否自然流转有无断层。4.情感寄托与创新(Emotional DepthInnovation)——20分 评估诗词的灵魂拒绝纯粹的字词堆砌。-情感共鸣10分词中所表达的悲欢离合、家国情怀或羁旅之思是否真挚饱满。-陈词翻新10分是否只会堆砌**愁**、**泪**等陈词滥调。优秀的词作应当在传统框架下有独特的视角或新颖的构思。;DotNetEnv.Env.Load();varloopAgentnewLoopAgent(innerAgent:CreateChatClient().AsAIAgent(),evaluator:newAIJudgeLoopEvaluator(CreateChatClient()));varresponseawaitloopAgent.RunAsync(prompt);varindex1;foreach(varmessageinresponse.Messages){Console.WriteLine(${newstring(-,20)}Message{index}{newstring(-,20)}{message});}staticIChatClientCreateChatClient(){varapiKeyEnvironment.GetEnvironmentVariable(OPENAI_API_KEY)!;varendpointEnvironment.GetEnvironmentVariable(OPENAI_BASE_URL)!;returnnewOpenAIClient(newApiKeyCredential(apiKey),newOpenAIClientOptions{EndpointnewUri(endpoint)}).GetChatClient(model:gpt-5.2-chat).AsIChatClient();}输出--------------------Message1-------------------- 钗头凤·淇上怨 淇波阔桑阴薄抱布当年眉语诺。 秋期近归车迅。 笑倚颓垣共卜嘉卺。 信、信、信。 桑华尽鸠声哽十年辛苦孤灯冷。 晨炊惯霜衾短。 淇岸虽宽不容愁眼。 恨、恨、恨。 --------------------Message2-------------------- Your previous response did not fully address the original request. The following is still missing or incomplete: 虽然已经创作了一首仿宋词作品但距离用户要求的“按严格标准至少达到80分以上”还有明显缺口。 主要问题 1. 词牌格律不够严谨。 《钗头凤》属于定格较严格的词牌当前作品在句式、字数与常见正格之间存在偏差例如“笑倚颓垣共卜嘉卺”“淇岸虽宽不容愁眼”等句的节奏与标准谱式不完全贴合且未体现对平仄与用韵的严格控制。用户明确把“格律规范”列为30分的大项因此这一部分难以达到高分。 2. 情节承接略显压缩。 原作《氓》包含“初恋—婚嫁—操劳—背弃—决绝”的完整情感弧线而当前词作篇幅虽短但后半段转折较快“士贰其行”“誓言反覆”等核心情节没有充分展开情感厚度不足。 3. 语言尚有现代化、概括化倾向。 如“十年辛苦孤灯冷”“不容愁眼”等表达较泛缺少更具宋词气息的细节锤炼。用户要求“词味”与“陈词翻新”当前版本意象较集中但新意不够突出。 4. 未体现对评分标准的自检。 用户特别给出了详细评分维度理想回应应尽量确保词牌合规并最好附简要说明其格律与构思如何对应《氓》的情感基调以证明其目标确实是“80分以上”的完成度。 因此这首词属于“完成了创作”但尚不足以认定为完全满足原请求。 Please continue and fully address the original request. --------------------Message3-------------------- 蝶恋花·淇水辞盟 抱布当年曾问字。淇水西风送我过烟渚。 一诺秋期人不寤。谁教轻信章台语。 桑叶初肥鸠莫顾。灯火寒机三岁愁中度。 淇岸有涯情已暮。回身不共斜阳去。 此调依《蝶恋花》正格双调六十字上下片各四仄韵到底。全词用“字、渚、寤、语、顾、度、暮、去”同部近韵句式依“七七四五”层递展开尽量贴合常见词谱。 构思上保留《氓》的核心情感线 - 上片写“抱布贸丝”至“秋以为期”的初遇与轻信以“淇水西风”“烟渚”营造送别与期待。 - 下片转入“桑之未落”到“士贰其行”的婚后困顿用“灯火寒机”代替直白诉苦强化宋词含蓄感。 - 结句“回身不共斜阳去”对应《氓》末尾“亦已焉哉”不作哭诉而以决绝收束。从输出结果可以看出整个创作流程经历了两次迭代。响应的消息中包含了两次创作的成果和针对第一次创作的评估结果。从整个效果来看这和我在基于评估结果针对Agent的循环调用使用自定义评估器没有本质区别。2. 利用LLM评估当前输入是否回答了提出的问题既然AIJudgeLoopEvaluator最终是利用LLM来评估当前迭代的输出那么我们就可以通过它发送给LLM的输入来看看具体的评估是如何进行的。为了拦截发送给LLM用于评估的提示词我们定义了如下一个用于模拟LLM调用的FakeChatClient。classFakeChatClient:IChatClient{publicvoidDispose(){}publicTaskChatResponseGetResponseAsync(IEnumerableChatMessagemessages,ChatOptions?optionsnull,CancellationTokencancellationTokendefault){foreach(varmessageinmessages){Console.WriteLine(${newstring(-,20)}{message.Role}{newstring(-,20)}{message});}varresponsenewChatResponse(newChatMessage(ChatRole.Assistant,{answered:false,gapAnalysis:gap-analysis}));returnTask.FromResult(response);}publicobject?GetService(TypeserviceType,object?serviceKeynull)null;publicIAsyncEnumerableChatResponseUpdateGetStreamingResponseAsync(IEnumerableChatMessagemessages,ChatOptions?optionsnull,CancellationTokencancellationTokendefault)thrownewNotImplementedException();}如上面的代码所示FakeChatClient实现了IChatClient的GetResponseAsync方法。我们在该方法中将所有的消息内容打印出来并且生成了一个响应。AIJudgeLoopEvaluator采用结构化输出返回如下这个JudgeVerdict类型。它的Answered属性用于确定Agent当前输出的内容是否回答了最初的问题如果没有针对两者之间差距的分析通过GapAnalysis属性返回。所以GetResponseAsync方法最终返回的消息内容为符合此结构的JSON文本。internalsealedclassJudgeVerdict{[Description(True if the agent has fully addressed the original request, otherwise false.)]publicboolAnswered{get;set;}[Description(When answered is false, explain what is still missing or what work remains to fully address the original request.)]publicstringGapAnalysis{get;set;}string.Empty;}在如下的演示程序中我们针对FakeChatClient对象创建了一个AIJudgeLoopEvaluator对象。然后创建了一个LoopContext对象作为调用其EvaluateAsync方法的输出。在初始化LoopContext时我们指定了一个模拟的AIAgent和AgentSession对象以及初始化消息列表和最后的响应。在得到评估结果的LoopEvaluation对象之后我们输出它的ShouldReinvoke和Feedback属性。usingMicrosoft.Agents.AI;usingMicrosoft.Extensions.AI;usingSystem.Text.Json;varevaluatornewAIJudgeLoopEvaluator(newFakeChatClient());varcontextnewLoopContext(agent:newFakeAgent(),session:newFakeSession(),initialMessages:[newChatMessage(ChatRole.User,initial-messages)],lastResponse:newAgentResponse(newChatMessage(ChatRole.Assistant,last-response)));varevaluationawaitevaluator.EvaluateAsync(context);Console.WriteLine(${newstring(-,20)}evaluation{newstring(-,20)}ShouldReinvoke:{evaluation.ShouldReinvoke}Feedback:{evaluation.Feedback});classFakeAgent:AIAgent{protectedoverrideValueTaskAgentSessionCreateSessionCoreAsync(CancellationTokencancellationTokendefault)thrownewNotImplementedException();protectedoverrideValueTaskAgentSessionDeserializeSessionCoreAsync(JsonElementserializedState,JsonSerializerOptions?jsonSerializerOptionsnull,CancellationTokencancellationTokendefault)thrownewNotImplementedException();protectedoverrideTaskAgentResponseRunCoreAsync(IEnumerableChatMessagemessages,AgentSession?sessionnull,AgentRunOptions?optionsnull,CancellationTokencancellationTokendefault)thrownewNotImplementedException();protectedoverrideIAsyncEnumerableAgentResponseUpdateRunCoreStreamingAsync(IEnumerableChatMessagemessages,AgentSession?sessionnull,AgentRunOptions?optionsnull,CancellationTokencancellationTokendefault)thrownewNotImplementedException();protectedoverrideValueTaskJsonElementSerializeSessionCoreAsync(AgentSessionsession,JsonSerializerOptions?jsonSerializerOptionsnull,CancellationTokencancellationTokendefault)thrownewNotImplementedException();}classFakeSession:AgentSession{}输出结果--------------------system-------------------- You are an evaluator. You are given a users original request and an agents latest response. Decide whether the agent has fully addressed the original request. Set answered to true if the request has been fully addressed, or false if more work is still required. When answered is false, use gapAnalysis to explain what is still missing or what work remains. If you cannot return structured output, reply with VERDICT: DONE when the request has been fully addressed, or VERDICT: MORE when more work is still required. --------------------user-------------------- # Has the original request been fully addressed? ## Original request: initial-messages ## Agents latest response: last-response --------------------evaluation-------------------- ShouldReinvoke : True Feedback : Your previous response did not fully address the original request. The following is still missing or incomplete: gap-analysis Please continue and fully address the original request.从输入可以看出传递给LLM用于评估的消息有两条系统消息其核心是规定AIJudgeLoopEvaluator的评判逻辑与输出格式。它要求评估器比较用户原始请求和代理最新回答判断是否已完全满足需求。如果满足则返回answered: true若不满足则返回answered: false并在gapAnalysis中说明缺失内容或剩余工作。若无法返回结构化结果则用简化文本标记VERDICT: DONE表示完成;VERDICT:MORE 表示未完成。用户消息描述了评估任务并提供了原始的请求和Agent最后的响应内容。从输出的评估结果可以看出我们响应内容的answered字段会决定ShouldReinvoke属性的值Feedback属性返回一段格式化文本其中包含gapAnalysis字段的值。3. AIJudgeLoopEvaluatorAIJudgeLoopEvaluatorOptions是AIJudgeLoopEvaluator的配置选项类定义其中的三个配置选项属性都体现在上面的输出中。publicsealedclassAIJudgeLoopEvaluatorOptions{publicstring?Instructions{get;set;}publicIEnumerablestring?Criteria{get;set;}publicstring?FeedbackMessageTemplate{get;set;}}三个配置选项说明如下Instructions: 提供给LLM的系统指令默认设置会在上面属性的指令文本默认添加占位符{criteria}Criteria我们利用它提供额外的评估标准指定的文本会用来填充系统指令模板的占位符{criteria}FeedbackMessageTemplate生成LoopEvaluation的Feedback属性的模板其中会包含占位符{gapAnalysis}用来接收LLM的反馈。AIJudgeLoopEvaluator定义如下我们在创建该对象时需要指定一个用来调用LLM的IChatClient和一个可选的AIJudgeLoopEvaluatorOptions配置选项。在重写的EvaluateAsync方法中它会创建两个消息一个是根据指定或者默认系统指令创建的System消息另一个则是根据LoopContext提供的原始消息和最终响应生成的User消息。它将这两个消息组成的列表作为参数调用IChatClient的GetResponseAsyncJudgeVerdict方法返回一个JudgeVerdict对象并据此生成返回的LoopEvaluation对象。publicsealedclassAIJudgeLoopEvaluator:LoopEvaluator{publicAIJudgeLoopEvaluator(IChatClientjudgeClient,AIJudgeLoopEvaluatorOptions?optionsnull);publicoverrideasyncValueTaskLoopEvaluationEvaluateAsync(LoopContextcontext,CancellationTokencancellationTokendefault);}