AI Agent执行循环设计:Hermes Loop如何让任务稳定可控?
做AIAgent这几年我踩过最深的坑不是模型能力不够而是模型的执行流程完全不可控。一个任务丢给Agent它可能在三轮之内就把事情做完了也可能在同一个工具调用上反复打转十几轮最后给你返回一句我尽力了。直到我把执行流程收敛成一套固定循环用Hermes Agent Loop的方式重新组织规划、调用、反思、记忆这些环节之后Agent的稳定性和可调试性才算真正拉起来。这篇文章就把这套执行流程拆开来讲不仅说清楚每个环节为什么存在也会给出一套可以直接抄的代码骨架和一次真实运行轨迹。Hermes这个名字取自希腊神话里的信使神恰好对应了这个循环的核心职责在模型、工具和任务之间传递信息、调度动作、带回结果。如果你刚接触AIAgent或者正被Agent乱跑、死循环、上下文爆掉这些问题折磨这篇文章应该能帮你少走很多弯路。1. 内容整体设计与思路拆解1.1 为什么Agent需要一条固定的执行循环先讲一个直观感受。最早我用大模型API做自动化任务时代码是这样的把用户问题拼进prompt请求拿回文本完事。这个做法对一句话问答没问题但一旦任务变成去数据库查最近一周的订单统计退货率再写一份摘要单次问答就彻底失灵了。模型不会自己去查数据库也不会在拿到数据之后判断这个结果够不够用更不会在失败时自动换一个查询角度。它只会根据你的prompt猜一个答案。所以AIAgent要做多步骤、工具依赖的任务就必须把人的工作方式翻译给模型看先理解目标、拆子任务然后调用工具拿到信息再根据结果决定下一步循环往复直到目标达成。这就是执行循环存在的根本原因。没有循环Agent就是高级聊天机器人有了循环Agent才谈得上自主完成任务。但这里有个容易被忽略的点循环不等于简单的for循环。LLM的每一步输出都有概率性同样一个prompt你跑五次可能拿到五个不同的动作。所以Agent的循环必须具备三个特征状态可记录、路径可回溯、退出有判定。这也是Hermes Agent Loop从一开始就坚持的三条设计原则。你可以把整个循环理解成一支带有地图的探险队每走一步就记录位置走错了能回滚重来到了终点必须停下。1.2 七个环节是怎么划分的Hermes Agent Loop把一个完整执行过程拆成七个环节依次是目标接收、规划拆分、方案决策、工具调度、结果评估、记忆回写、完成判定。这个顺序不是拍脑袋定的它是从一个人拿到任务后做事的自然流程映射来的。我见过不少人把Agent写成一次规划加多次工具调用两段式问题在于一旦工具返回结果和预期不符Agent没有能力修正规划整个流程就成了单行道。环节核心职责失败时的兜底策略目标接收解析用户意图提取约束和评测标准澄清问题或返回需求缺失提示规划拆分把大目标拆成可执行的小步骤缩小步骤粒度或重新生成规划方案决策决定当前步骤用哪个工具、什么参数改换工具或降级为直接文本推理工具调度真正执行外部动作拿回观察结果重试、超时熔断、错误信息回传结果评估判断当前结果是否满足步骤要求回到方案决策换方案继续记忆回写把有用的状态沉淀到短期/长期记忆压缩冗余记忆避免污染完成判定判断整个任务是否终结并输出最终结果保留当前最优结果并显式结束这张表其实就是Hermes Loop的最小可用版。实际项目里不一定每个环节都独立成一个函数但你起码要在代码里能明确回答一个问题现在执行到哪个环节了如果答不上来说明你的Agent还停留在prompt一锤子买卖的阶段出问题只能靠瞪眼排查。1.3 为什么叫Loop而不是Pipeline这里想刻意区分一下Loop和Pipeline的差异。Pipeline是预先编排的固定流水线第一步做什么、第二步做什么全部在代码里写死模型不参与决策。Loop则把下一步做什么这个决策权交还给模型只是给它画一个安全边界。Hermes Agent Loop走的是后者的思路流程框架是固定的但每个环节内部的动作和参数由模型根据当前上下文动态决定。这么设计有什么好处最大的好处是应对不确定性。比如同样是整理会议纪要这个任务上一轮参数是提取3个决策项这一轮用户改成了提取5个行动项如果流程写死代码就得改。而Loop模式下模型会读取当前上下文里新出现的约束自己调整提取规则。一句话概括Pipeline靠人力维护分支Loop靠模型理解上下文。但这个设计也带来了代价——你必须相信模型在大部分情况下能做出还行的决策。所以Hermes Loop里专门留了结果评估和记忆回写两个环节来做校验。如果模型这次决策不行评估环节会把它弹回去重新走方案决策而不是一条路走到黑。这也是实操中我觉得最值得借鉴的设计给模型自由但给自由装一个回退机制。2. 核心细节解析与实操要点2.1 规划拆分步骤的粒度怎么定规划是很多Agent翻车的重灾区。我见过最典型的失败案例是用户让Agent写一篇市场分析报告Agent规划出了12个大步骤每个步骤都像是研究行业趋势分析竞争对手这种宏大描述结果第一步就不知道该调用什么工具直接幻觉了一份数据出来。问题不在模型在规划的粒度。实操经验是规划拆分要遵循两个原则。第一每个步骤必须能映射到至少一个具体工具或明确的推理动作如果映射不出来说明步骤还不够细。第二步骤数量最好控制在3到6个之间超过6个时模型很容易在后续循环里忘了前面步骤的上下文导致前后脱节。如果你确实有十几个子任务可以引入任务分组先把12个任务合并成调研、分析、撰写三大阶段再在阶段内展开细节。落地时我一般让模型输出结构化的规划JSON包含步骤序号、步骤描述、依赖工具、验收标准四个字段。其中验收标准特别有用它直接喂给后面的结果评估环节用来判断当前步骤是否真的完成。很多人会漏掉这一步导致Agent做了个差不多的结果就往下走最后整个任务质量崩盘。2.2 工具调用的函数定义描述比参数更重要工具调用是整个Loop里最靠近实体世界的环节。不管模型多聪明工具参数填错、权限不足、返回格式异常都会让Agent卡死。我现在的做法是所有工具都按照OpenAPI风格写函数定义明确描述参数类型、必填项和取值范围并在description里写明工具的使用场景和容易填错的参数。举个实际例子有一个天气查询工具最初函数定义只写了city、date两个参数。上线后发现Agent经常把date传成Around 5pm模型根本不理解ISO日期格式。后来我在description里加了约束date必须为YYYY-MM-DD格式如2025-01-15不接受自然语言描述。调用准确率立刻从60%提到了接近95%。所以函数定义不是随便写的它其实是给模型看的API说明书描述越具体模型越不容易犯错。另一个建议是工具调用一定要有超时和失败重试机制。工具返回超时时不能把超时异常直接丢进上下文那会让模型误以为工具成功了。正确做法是把超时转成一个明确的失败消息比如get_weather调用超时已重试1次建议更换工具或检查网络再交回给方案决策环节。让模型看到失败本身就是它调整策略的依据。2.3 上下文管理防止记忆过载所有Agent最终都会撞上同一堵墙上下文窗口装不下了。Hermes Loop应对这个问题的方法是把记忆拆成两层。第一层是短期记忆保存当前任务循环里的关键中间结果比如最近几次工具返回、最近一个规划变更第二层是长期记忆保存跨任务有用的信息比如用户偏好的输出格式、常用工具的调用习惯。短期记忆的整理我建议用滚动摘要的方式。每完成一个步骤就把该步骤的动作、结果、结论浓缩成一条20到50字的摘要替换掉原始的完整内容。这样上下文占用基本保持稳定不会随着步骤增加而线性膨胀。实践中我会把摘要的压缩任务单独交给一个较轻量的小模型执行而不是让主Agent每次都为压缩付出推理成本。长期记忆的写入要克制不是所有信息都值得存。我的标准是只有连续两次任务中被重复利用的信息才允许进入长期记忆。比如用户连续两次要求输出Markdown表格这个偏好值得存一次性的任务细节就随短期记忆一起丢弃。长期记忆的引入还得配备清理机制定期删除几个月没被命中的记忆条目避免向量库被噪音污染。2.4 反思与终止Agent如何知道够了反思环节在Hermes Loop里承担两个作用一是对单步结果的质量打分二是对整任务是否完成做判断。我之前把完成判定的逻辑交给模型自由发挥结果模型经常在第一步之后就说任务完成因为它在对话式训练里习惯了尽快给出答案。所以后来我把终止判定拆成硬条件和软条件两个维度。硬条件是代码层面强制检查的比如达到最大循环次数、某个关键工具调用成功、用户指定了停止标记。硬条件不依赖模型判断所以它是稳定性的底座。软条件是模型给出的比如当前结果是否满足了所有验收标准还有没有明显信息缺口这部分我会要求模型必须输出一个confidence数字和一句简短理由。只有当硬条件满足且软条件confidence大于阈值时循环才真正终止。这套双条件终止帮我解决了大量死循环问题。以前Agent会为了一个根本不存在的更完美答案无限循环下去现在它看到max_steps快到了再加上反思环节的自我提示就会收敛到基于已有信息给出当前最优结果。换句话说反思环节不仅是为了提升质量也是为了给Agent一个台阶下。3. 实操过程与核心环节实现3.1 技术选型模型、框架与工具注册先说我验证过的技术组合供你参考。模型层面我优先选了支持Function Calling的大模型接口因为Hermes Loop的方案决策、工具调度都需要模型输出结构化参数用纯文本prompt硬解析函数参数体验很差。框架层面没有用重框架核心主循环自己写大概200行Python就够外部依赖只用了asyncio做异步调度加一个轻量向量库做长期记忆。工具注册这一层值得单说。我把所有工具都注册成一个Tool对象包含name、schema、handler三个字段。schema直接拼进发给模型的tools参数handler是真正执行动作的Python函数。这个设计的好处是新增一个工具只需要实现一个函数加一个schema不用改动主循环代码。如果你的工具数量超过20个建议再按领域分组建索引避免把所有工具的schema一股脑全塞给模型token成本和选择难度都会爆炸。3.2 主循环代码骨架下面这份代码是Hermes Loop的最简实现核心逻辑浓缩在run方法里。为了保证可读性我省略了日志和鉴权细节但保留了完整流程。# hermes_loop.py 核心主循环 import asyncio from dataclasses import dataclass dataclass class Tool: name: str schema: dict # 传给模型的 function schema handler: callable # 实际执行的 python 函数 class HermesAgent: def __init__(self, llm, tools, max_steps8, threshold0.8): self.llm llm self.tools {t.name: t for t in tools} self.max_steps max_steps self.threshold threshold self.messages [] async def run(self, user_task: str): self.messages [ {role: system, content: self._sys_prompt(user_task)} ] for step in range(1, self.max_steps 1): resp await self.llm.chat( messagesself.messages, tools[t.schema for t in self.tools.values()], temperature0.2, ) action resp[action] # {type: tool|final, ...} if action[type] final: if resp[confidence] self.threshold: return action[answer] if action[type] tool: tool self.tools[action[name]] try: result await asyncio.wait_for( tool.handler(**action[arguments]), timeout30 ) self.messages.append(self._tool_success_msg(action, result)) except Exception as e: self.messages.append(self._tool_error_msg(action, str(e))) reflection await self.llm.chat( messagesself.messages [ {role: user, content: 评估当前进度输出JSON} ], temperature0.1, ) self.messages.append(self._reflect_msg(reflection)) if reflection.get(plan_conflict): self.messages.append(await self._replan()) return self._best_guess(self.messages)这段代码里有几个关键设计。第一主循环每轮先让模型返回一个action这个action必须显式区分tool和final两种类型避免模型在中间步骤直接输出结论。第二工具执行包裹了超时控制任何一次工具调用超过30秒都会被当作异常处理转而生成失败消息。第三每一轮循环结束前强制做一次反思反思结果再写回上下文这样下一轮决策就带上了对上一轮的校验。_sys_prompt里我会固定放用户原始目标、输出格式要求和几个硬性约束。这个系统提示词在整个循环中不参与压缩始终保持在上下文中保证模型无论跑了多少步都不会忘记最初任务是什么。如果你在本地复现建议把_sys_prompt设计成模板字符串只替换目标和用户ID两个变量。3.3 关键参数怎么选这里直接给一组我实测下来比较稳的经验值参数推荐值说明max_steps6~10太少任务完不成太多容易陷入无效循环temperature0.1~0.3规划决策类任务要低温度保证稳定输出top_p0.9配合低温度使用避免输出过于随机单步超时30~60秒工具调用和模型推理都要设超时防止挂死上下文压缩阈值达到窗口60%时触发提前压缩避免最后一刻被截断confidence_threshold0.8低于这个值继续循环高于则提前终止max_steps这个值最容易被忽视。设小了复杂任务来不及完成设大了模型会在简单任务上空转。我建议按任务类型动态设置普通信息查询4到5步数据分析类8到10步多阶段创作类比如写报告、做方案可以放宽到15步。但任何任务都不建议超过20步超过之后前面的上下文和规划基本已经失准不如直接让Agent汇报当前进度把控制权交还给用户。temperature很多人习惯性填0.7但在Agent循环里这个值明显偏高。因为循环里的每一步输出都会影响后续步骤随机性会被不断放大最终导致规划前后矛盾。我在规划、方案决策、结果评估这几个环节都强制用0.2以下的温度只有最终结果润色时才会提高到0.6左右让文字表达更自然。3.4 一次实际运行轨迹记录这里放一段我调试时跑过的轨迹任务是把用户的一句话转换成结构化备忘并查询对应城市的天气。整个过程不复杂但能体现出循环是怎么一步步推进的。[Goal] 记一下明天下午3点跟产品团队开评审会顺便看下北京明天天气如何。 [Step 1] Plan: 拆成两个子任务。子任务1提取会议信息并写入备忘录子任务2查询北京天气预报。 Action: create_memo(date2025-01-16, time15:00, title产品评审会, participants产品团队) Result: 成功写入memo_id2025001。 Reflect: 子任务1完成验收标准备忘录已创建且信息完整满足confidence0.95。 [Step 2] Plan: 继续子任务2调用天气查询。 Action: get_weather(city北京, date2025-01-16) Result: 晴-3~7°C北风3级。 Reflect: 子任务2完成所有步骤均完成confidence0.9可终止。 [Done] 最终输出明天下午3点产品评审会已记入备忘录编号2025001北京明天晴-3~7°C北风3级。这个轨迹看着简单但每一步的输出格式都是严格JSON否则反思和终止判定没法程序化。第二点是每一步我都要求模型在Action里附带一个step_status字段取值continue或finished代码里先检查这个字段再做后续判断。这是我在调试中加上的因为模型偶尔会在中间步骤就输出final answer用step_status可以强制它按循环走完所有未完成步骤。4. 常见问题与排查技巧实录4.1 死循环与工具反复调用这是Agent上线后第一周最容易遇到的事。现象是模型反复调用同一个工具、参数几乎一致然后不断拿到相同结果又继续调用日志刷了几十条。根因通常是推理结果没有进入上下文或者反思环节没有正确判断此路不通。我的排查顺序是这样的先看日志里工具的返回是否每次都被写进了对话历史。如果没写进去模型等于在空转每次决策都是盲猜解决办法是把工具返回强制追加到消息列表并确保下一步请求带着历史。再看反思环节的prompt里有没有明确的如果上一步返回结果未满足预期请更换工具或修改参数不要重复相同动作指令。很多情况下加上这一句死循环率能下降七成。最后一道保险是硬性代码检查统计同一工具连续调用次数达到3次就插入一条干预消息提醒模型切换策略。这个机制不依赖模型自觉所以非常可靠。我在3.2的代码骨架里没有写这部分实际项目里一定要补上。4.2 规划宏大导致单步失效规划阶段模型列了10个步骤但执行到第3步时第2步的结果已经让整个规划失去了意义。这种情况在数据类任务里尤其常见比如先查用户总数再按城市分析结果查出来的用户总数口径和第二步的粒度对不上后面所有步骤全废。遇到这种问题我给Loop加了一个规划重写机制当单步执行结果与规划里的假设冲突时结果评估环节返回plan_conflictTrue主循环会调用一次重规划把剩余步骤基于当前实际状态重新编排。说白了就是允许Agent在循环中间修改地图而不是死板地走完原计划。代价是多一次模型调用但能挽回整条任务链路非常值。4.3 上下文爆炸与关键信息丢失跑长了之后对话历史越来越长模型开始遗忘最初的目标约束。要么是早期用户给的硬性要求被后续步骤挤出了注意力窗口要么是压缩摘要时把关键数字漏掉了。我的经验是两步走第一步把用户原始目标单独固定在系统提示词最前面并且每一步模型输出都必须带回一个goal_alignment字段校准当前动作与原始目标的一致性。第二步压缩摘要时宁可多花token也要保留数字、日期、ID这类实体信息文字描述可以缩写实体不能省。另外我强烈建议在上下文达到窗口60%时主动做一次归档操作把已经完成步骤的原始内容移到外部队列只保留摘要和关键实体在窗口内。等到全部任务结束时再按需回查原始内容。这样既避免窗口爆掉又不丢失细节。4.4 工具参数幻觉与错误输入工具参数幻觉是指模型编造了不存在的工具名或填入了明显错误的参数比如把一个UUID传递成了最新的那个。这个问题根源在于工具定义描述不够清晰或者模型对当前数据状态判断有误。除了把函数定义写细我还有一个针对性做法在执行任何参数涉及ID、文件路径、时间戳的工具前先让模型把参数来源标注出来说明是来自用户输入来自上一步工具返回还是模型推断。一旦发现参数来源是模型推断而工具执行又失败就直接触发一次澄清交互而不是盲目重试。这里分享一个真实案例Agent需要调用内部文档搜索工具参数是文档编号。模型在没看到编号的情况下把参数填成了最近上传的文档搜索接口直接报错。加了这个来源标注机制后模型会在参数不确定时主动输出无法确定文档编号需要用户提供不再硬编。表面上只是多了一个字段实际效果是让Agent学会了在信息缺失时承认缺失这个能力比强行完成重要得多。4.5 问题排查速查表最后把常见症状、可能原因和快速解法整理成一张表遇到问题先对照一遍。症状可能原因快速解法同一工具反复调用工具返回未写入上下文强制追加返回结果到历史并插入干预提示任务提前完成终止条件太宽松拆解硬条件加软条件confidence低于阈值不终止规划与应用场景脱节模型对上下文理解不足引入重规划机制冲突时重排剩余步骤上下文窗口爆掉未做摘要压缩达到60%阈值触发滚动摘要实体信息不压缩工具参数幻觉函数描述不清细化schema工具参数增加来源标注执行卡在某个环节单步超时没有兜底统一加超时熔断超时转为失败消息交回决策环节多步骤后忘记原始目标目标约束被历史淹没系统提示词固化原始目标每步输出goal_alignment字段我自己跑了半年Hermes Agent Loop之后最大的体会是AIAgent的执行流程设计本质上是在放权给模型和用代码兜底之间找平衡。模型负责聪明代码负责不出格。规划、工具调用、反思这些环节做得再花哨最后都得落到退出条件和反馈质量这两个基本功上。如果你现在正要动手写自己的Agent先把max_steps、工具返回、终止判定这三件事琢磨透再去追求复杂的记忆和多Agent协作绝对能省下一大笔调试时间。最后再分享一个小技巧每轮循环让模型输出的JSON里加一个trace字段记录它这一步的思考依据来自哪里。这个字段不参与业务逻辑但在排查问题时价值极高它能让你一眼看出模型是在基于真实工具结果做决策还是在凭空猜测。等你的Agent稳定运行了回头再看这些trace你会对模型到底是怎么思考的这件事有非常直观的理解。