[Loop Engineering在MAF中的实现-07]在LangGraph中实现类似LoopAgent的循环
在Loop Engineering在MAF中的实现-06:利用Workflow来使用LoopAgent的循环策略中我们采用MAF的工作流实现了LoopAgent类似的循环。既然可以利用MAF的Workflow实现自然能利用LangGraph来实现。鉴于两者之间设计上的差异相同的功能如果采用这两种解决方案使用LangGraph大概率会更加简单。1. 采用LangGraph来构建诗词创作Agent我们将类似于LoopAgent的根据评估结果循环调用指定Agent的逻辑实现在如下这个工厂方法该方法会创建一个自动循环执行的Agent对象。defcreate_loop_agent(agent:Runnable,evaluator:Callable[[LoopContext],Awaitable[LoopEvaluation]],max_iterations:int100)classLoopContext(TypedDict):interation:intinit_messages:list[AnyMessage]last_response:AIMessage feedback:list[str]classLoopEvaluation(TypedDict):stop:boolfeedback:strcreate_loop_agent函数的三个参数说明如下agent: 被封装的目标Agent即循环调用的对象evaluator通过一个异步函数表示的评估器输入为表示循环上下文的LoopContext类型返回值LoopEvaluation表示评估结果两个字段分别表示是否终止以及评估反馈max_iterations允许的最多迭代次数。LoopContext的四个上下文成员interation当前迭代次数init_messages 初始传入的消息列表last_response最后一下响应的消息feedback 之前的评估反馈列表。如下定义的composer_system_message和reviewer_system_message两个变量是我们为创作Agent和调用评估模型定义的系统指令。prompt为调用loop_agent传入的提示词。composer_system_messageSystemMessage(content你是一个精通宋词创作的智能体负责根据提供的主题、意境和评估反馈以词牌名**相见欢**创作一首词)reviewer_system_messageSystemMessage(content\ 你是一个深谙古典文化和诗词的文化大家请按照如下的标准对创作的这首词进行评价。评语保持500字以内尽可能简洁。 1. 格律规范 (Metrical Accuracy) —— 30分 检测AI生成的词是否真正符合该词牌的“说明书”。 - 词牌结构20分总字数、分片、每句的字数长短必须与词牌名保持一致。 - 平仄规范5分关键位置的平仄平声、仄声必须严格符合词牌要求。 - 押韵规则5分检查是否在规定位置押韵是否混淆了平仄韵有无出韵。 2. 意境创设 (Imagery Conception) —— 30分 评估词作是否具备古典美感是否能画出**画面感**。 - 意象选用15分是否恰当使用了符合古典美学的意象。意象之间是否逻辑自洽没有现代感违和物。 - 流派风格15分整体意境的深远程度。 3. 语言艺术 (Linguistic Artistry) —— 20分 评估遣词造句的功底和文字的流畅度。 - 用词典雅10分遣词造句需有**词味**杜绝使用现代大白话或过于生硬的拼凑词。 - 对仗与过片10分若词牌要求对仗需检查是否工整重点评估**过片**上下片过渡是否自然流转有无断层。 4. 情感寄托与创新 (Emotional Depth Innovation) —— 20分 评估诗词的灵魂拒绝纯粹的字词堆砌。 - 情感共鸣10分词中所表达的悲欢离合、家国情怀或羁旅之思是否真挚饱满。 - 陈词翻新10分是否只会堆砌**愁**、**泪**等陈词滥调。优秀的词作应当在传统框架下有独特的视角或新颖的构思。 )prompt\ 基于如下这首《卫风·氓》的背景和情感基调创作**一首**宋词。 原文如下 氓之蚩蚩抱布贸丝。匪来贸丝来即我谋。 送子涉淇至于顿丘。匪我愆期子无良媒。 将子无怒秋以为期。 乘彼垝垣以望复关。不见复关泣涕涟涟。 既见复关载笑载言。尔卜尔筮体无咎言。 以尔车来以我贿迁。 桑之未落其叶沃若。于嗟鸠兮无食桑葚 于嗟女兮无与士耽士之耽兮犹可说也 女之耽兮不可说也。 桑之落矣其黄而陨。自我徂尔三岁食贫。 淇水汤汤渐车帷裳。女也不爽士贰其行。 士也罔极二三其德。 三岁为妇靡室劳矣夙兴夜寐靡有朝矣。 言既遂矣至于暴怒。兄弟不知咥其笑矣。 静言思之躬自悼矣。 及尔偕老老使我怨。淇则有岸隰则有泮。 总角之宴言笑晏晏。信誓旦旦不思其反。 反是不思亦已焉哉 整个演示程序如下所示。我们调用create_agent函数创建了用于宋词创作的Agent。由于评估器通过体现一个Callable[[LoopContext], Awaitable[LoopEvaluation]]对象所以定义了与之匹配的evaluate函数来完成评估工作。evaluate函数采用结构化输出调用ChatOpenAI对象将评估结果反序列化成一个Review对象并生成返回的LoopEvaluation对象。这里体现的评估逻辑是如果得分低于75则通过循环重新创作同时将评语反馈给后续迭代。fromlangchain.messagesimportAnyMessage,SystemMessage,HumanMessagefromlangchain.agentsimportcreate_agentfromtypingimportTypedDictfromdotenvimportload_dotenvfromlangchain_openaiimportChatOpenAIfromloop_agentsimportLoopContext,LoopEvaluation,create_loop_agentimportasyncio load_dotenv()评估结果classReview(TypedDict):分数百分制score:int评语feedback:strcomposercreate_agent(modelChatOpenAI(modelgpt-5.4-mini),system_promptcomposer_system_message)reviewerChatOpenAI(modelgpt-5.4-mini)reviewerreviewer.with_structured_output(Review)asyncdefevaluate(context:LoopContext)-LoopEvaluation:messages:list[AnyMessage][reviewer_system_message,*context[init_messages],context[last_response]]reviewawaitreviewer.ainvoke(inputmessages)return{stop:review[score]75,feedback:review[feedback]f\n\n综合得分{review[score] }}agentcreate_loop_agent(agentcomposer,evaluatorevaluate,max_iterations3)asyncdefmain():messageHumanMessage(contentprompt)resultawaitagent.ainvoke(input{init_messages:[message],messages:[message]})formessageinresult.get(messages):message.pretty_print()asyncio.run(main())我们利用创建的Agent和评估函数调用create_loop_agent函数将具有循环调用能力的Agent创建出来并传入提示词进行调用。输出的消息列表体现了Agent调用评估的两次循环迭代两次创作得分分别为70和81。 Human Message 基于如下这首《卫风·氓》的背景和情感基调创作**一首**宋词。 原文如下 氓之蚩蚩抱布贸丝。匪来贸丝来即我谋。 送子涉淇至于顿丘。匪我愆期子无良媒。 将子无怒秋以为期。 乘彼垝垣以望复关。不见复关泣涕涟涟。 既见复关载笑载言。尔卜尔筮体无咎言。 以尔车来以我贿迁。 桑之未落其叶沃若。于嗟鸠兮无食桑葚 于嗟女兮无与士耽士之耽兮犹可说也 女之耽兮不可说也。 桑之落矣其黄而陨。自我徂尔三岁食贫。 淇水汤汤渐车帷裳。女也不爽士贰其行。 士也罔极二三其德。 三岁为妇靡室劳矣夙兴夜寐靡有朝矣。 言既遂矣至于暴怒。兄弟不知咥其笑矣。 静言思之躬自悼矣。 及尔偕老老使我怨。淇则有岸隰则有泮。 总角之宴言笑晏晏。信誓旦旦不思其反。 反是不思亦已焉哉 Ai Message 【相见欢】 当年初见斜阳过横塘。 一诺轻轻笑语付疏妆。 桑叶落心先薄泪成行。 淇水汤汤流尽旧时光。 Human Message 词意能承《卫风·氓》之怨悔与决绝情感脉络较清楚淇水、桑叶等意象亦合古典语境结句有余味。但就“宋词”而言词牌未明结构、字数、句式、平仄与押韵均无从称合严格说不合格律规范。语言尚有词味但“斜阳、横塘、泪成行、旧时光”等语稍显泛化略近今人语感且新意不足仍多沿用闺怨陈辞。整体可算有情而未入法重在意境传达格律功夫明显欠缺。 综合得分70 Ai Message 【相见欢】 当年一笑相逢。 过淇东。 布贸无非来语问深衷。 秋已约心难托誓成空。 桑落人归回首怨东风。 Human Message 此词取《氓》之怨与悔情绪线索尚明但格律上问题较大词牌结构更接近小令却未严守《相见欢》定式句式、字数、分片与押韵均不够规范平仄亦难尽合。意象方面以“淇”“桑”“秋”“东风”承接原诗古典感尚可但略显提炼不足画面较单薄。语言总体较雅未见明显白话杂语然“布贸无非来语问深衷”稍嫌生硬过片亦略显跳脱。情感上能写出由欢而怨、由信而悔的转折贴合原作精神但创新性一般仍多依附原诗意脉。 综合得分812. 状态类型Loop Engineering在MAF中的实现-06:利用Workflow来使用LoopAgent的循环策略的设计只要体现将涉及的状态包括对话历史和当前迭代的循环上下文存储在Workflow的上下文中。与之类似如果使用LangGraph我们就可以直接将它作为状态图的状态。这个状态类型就是如下这个继承自AgentState的TypedDictclass State(AgentState): interation: Required[Annotated[int, operator.add]] init_messages: list[AnyMessage] last_response: AIMessage stop: bool feedback: Required[Annotated[list[str],operator.add]]除了继承自AgentState的核心成员messages之外自身定义的状态字段说明如下interation迭代次数应用的reducer函数实现自增init_messages初始调用指定的消息列表last_response当前迭代从LLM返回的AIMessagestop: 决定是否继续循环还是立即终止feedback: 历次评估收集的反馈应用的reducer函数实现追加功能。3. create_loop_agent的实现create_loop_agent函数返回的是通过状态图基于状态类型State的StateGraph对象构建并编译生成的Agent对象。fromlanggraph.graphimportStateGraphfromlangchain_core.runnablesimportRunnablefromlangchain.messagesimportAnyMessage,AIMessage,HumanMessagefromlangchain.agentsimportAgentStatefromtypingimportAny,Required,Annotated,TypedDict,Callable,Awaitablefromlanggraph.graph.messageimportadd_messagesimportoperator,functoolsasyncdefinvoke_agent(agent:Runnable,state:State)-dict[str,Any]:messagesstate.get(messages,state.get(init_messages))resultawaitagent.ainvoke(input{messages:messages})new_messagesresult[messages][len(messages):-1]response:AnyMessageresult[messages][-1]return{messages:new_messages,interation:1,last_response:response}asyncdefevaluate(evaluator:Callable[[LoopContext],Awaitable[LoopEvaluation]],state:State)-dict[str,Any]:context:LoopContext{interation:state.get(interation),init_messages:state.get(init_messages),last_response:state.get(last_response),feedback:state.get(feedback)}evaluationawaitevaluator(context)return{stop:evaluation[stop],feedback:[evaluation[feedback]],messages:[HumanMessage(contentevaluation[feedback])]}defroute(max_iterations:int,state:State)-str:ifstate.get(stop)orstate.get(interation)max_iterations:return__end__returninvoke_agentdefcreate_loop_agent(agent:Runnable,evaluator:Callable[[LoopContext],Awaitable[LoopEvaluation]],max_iterations:int100):return(StateGraph(State).add_node(invoke_agent,functools.partial(invoke_agent,agent)).add_node(evaluate,functools.partial(evaluate,evaluator)).set_entry_point(invoke_agent).add_edge(start_keyinvoke_agent,end_keyevaluate).add_conditional_edges(sourceevaluate,pathfunctools.partial(route,max_iterations)).compile())它具有两个节点函数invoke_agent代表的初始节点用来执行指定的Agent并通过返回的字典更新如下的状态messages将响应消息添加到对话历史中interation: 迭代次数加一last_response将响应的最后一条AIMessage作为本次迭代的响应。函数evaluate代表的节点会根据当前状态创建LoopContext上下文对象并调用指定的评估函数并得到代表评估结果的LoopEvaluation对象然后根据评估结果更新如下的状态stop 是否终止循环feedback将评估反馈追加到反馈文本列表中messages将评估反馈文本转换成HumanMessage追加到对话历史中意味着后续的Agent调用会参考本次评估的反馈。我们在invoke_agent和evaluate节点之间添加了双向边从invoke_agent到evaluate节点之间是一条静态边反向则是一条条件边分支条件实现在函数route中如果stop状态为true或者迭代次数超出阈值通过跳转到__end__节点立即停止执行否则跳转到invoke_agent节点。