
1. 从能跑到能扛Agent Loop 工程化的分水岭在哪很多人第一次接触 Agent Loop 这个概念是在某个深夜调通了一个 ReAct 循环——模型输出思考、调用工具、拿到结果、继续思考最后给出答案。那一刻确实很爽感觉智能体的大门被踹开了。但接下来发生的事情往往很扫兴任务一复杂就死循环工具调用失败后模型开始胡言乱语上下文越滚越长最后直接超限同一个任务跑三次给出三种完全不同的结果。这就是能跑和能扛之间的鸿沟。Agent Loop 本身不是什么新东西它的本质就是一个带状态管理的迭代执行器把大模型的推理能力和外部工具的执行能力串成一个闭环让模型能够根据中间结果动态调整下一步动作。这个循环的伪代码简单到可以写在一张便利贴上——初始化状态、调用模型、解析动作、执行工具、更新状态、判断终止条件、回到第二步。但真正把它放进生产环境你会发现每一个环节都是坑。Graph Engineer 这个角色就是在这个背景下被提出来的。它不是一个具体的库或者框架而是一种工程思维把 Agent 的执行流程从一段 while 循环升级为一张有向图。节点是执行单元模型调用、工具执行、条件判断、数据转换边是流转逻辑成功走哪条、失败走哪条、超时走哪条。这么做的核心动机只有一个——让不可控的循环变成可控的流程。我见过太多团队在这个阶段翻车。他们用最朴素的方式写了一个 while 循环里面塞了七八个工具跑 demo 的时候效果惊艳一上真实业务就各种超时、死循环、结果不可复现。问题不在于模型不够强而在于整个执行流程缺乏工程约束。Agent Loop 的工程化本质上就是给这个循环加上护栏状态怎么存、错误怎么处理、循环怎么终止、结果怎么验证、成本怎么控制。这篇文章适合两类人看。一类是已经写过 Agent 循环、但被稳定性问题折磨的开发者另一类是准备把 Agent 能力接入真实业务、需要一套可落地工程方案的技术负责人。我会从循环的本质讲起拆解 Graph Engineer 的核心设计思路给出可复现的实操步骤重点分享那些文档里不会写、只有踩过才知道的坑。全文围绕一个目标让你的 Agent 从演示级进化到生产级。2. Agent Loop 的本质一个带状态和终止条件的迭代执行器2.1 循环的三个核心构件抛开所有框架的包装任何一个 Agent Loop 都逃不出三个核心构件状态State、动作空间Action Space、终止策略Termination Policy。状态是循环的记忆。它至少包含当前的任务目标、已经执行过的步骤历史、每一步的输入输出、累积的上下文、以及一些元信息比如已消耗的 token 数、已执行的轮次。很多人写 Agent 的时候把状态直接塞进对话历史里这是最省事但也最危险的做法——对话历史会无限膨胀而且模型很难从一堆自然语言里准确提取我到底做到哪一步了。动作空间是模型能做的事情集合。它可以是工具调用搜索、计算、读写文件、调 API也可以是内部动作反思、规划、总结。动作空间的设计直接决定了 Agent 的能力边界。我个人的经验是动作数量控制在 5 到 12 个之间。太少则能力不足太多则模型选择困难、错误率飙升。每个动作的描述要精确到什么情况下该用、什么情况下不该用而不是只写一句功能说明。终止策略是最容易被忽视、也最容易出事的部分。一个 Agent Loop 必须有至少三层终止保护第一层是任务完成判定模型明确表示任务结束第二层是轮次上限比如最多执行 15 轮超过就强制停止第三层是资源上限比如 token 消耗超过预算、总耗时超过阈值。三层缺一不可因为模型完全可能陷入我觉得还没完成的执念里或者两个工具互相调用形成死循环。2.2 为什么朴素的 while 循环一定会失控我拿一个真实场景举例。假设你要做一个根据用户需求查资料并生成报告的 Agent。朴素实现是这样的while not done: response llm.chat(messages) if response.has_tool_call: result execute_tool(response.tool_call) messages.append(result) else: done True这段代码在简单任务上没问题但一旦任务变复杂会出现几类典型故障。第一类是上下文爆炸每次工具返回的结果都往 messages 里塞搜索返回的网页全文、API 返回的 JSON几轮下来就几万 token模型开始忘记最初的目标。第二类是错误吞噬工具调用失败返回一个错误信息模型看到错误后不是修正参数重试而是换一个完全不相干的工具越跑越偏。第三类是终止失效模型在再查一下确认和信息够了之间反复横跳永远不给最终答案。这些问题的根源在于while 循环没有显式的流程控制。所有的判断都交给模型在自然语言层面完成而模型在长上下文下的判断力是衰减的。Graph Engineer 的思路就是把隐式的流程判断变成显式的图结构让该由代码控制的逻辑不要交给模型。2.3 状态管理的三种粒度状态怎么存直接决定了 Agent 的可控性。我把它分成三种粒度对应不同的复杂度场景。粗粒度全量对话历史。就是把所有消息堆在一个列表里。优点是实现简单缺点是上下文膨胀快、模型注意力分散。只适合轮次少3 轮以内、工具返回结果小的场景。中粒度结构化状态对象。定义一个 State 类包含 task、steps、current_step、artifacts、budget 等字段。每轮只把当前需要的信息喂给模型历史步骤以摘要形式保留。这是我最推荐的方案兼顾了可控性和实现成本。细粒度外部状态机 事件日志。状态存在数据库或内存存储里每一步操作都记录成事件模型每次只看到当前状态的一个视图。适合长流程、需要断点续跑、需要审计的场景但实现复杂度高。选择哪种粒度取决于你的任务平均需要多少轮、单轮返回数据有多大、是否需要跨会话恢复。我的建议是先用中粒度起步遇到瓶颈再往细粒度演进不要一上来就搞复杂的状态机那是过度设计。3. Graph Engineer 视角把循环拆成节点和边3.1 节点设计的四种类型Graph Engineer 的核心动作是把一个 Agent Loop 拆解成若干节点。节点不是随便切的它应该对应一个语义完整的执行单元。我通常把节点分成四类。推理节点Reasoning Node调用大模型输入当前状态输出下一步动作决策。这是图里最核心的节点也是成本最高的节点。一个设计良好的图推理节点的数量应该尽量少因为每次调用都有延迟和成本。执行节点Execution Node执行具体的工具调用或数据处理。它不涉及模型推理纯粹是确定性的代码逻辑。把执行逻辑从模型手里拿出来是提升稳定性的关键一步。判断节点Decision Node根据执行结果决定走哪条边。比如工具调用成功走 A 边失败走 B 边返回结果为空走 C 边。这些判断用代码写不用模型判断准确率是 100%。转换节点Transform Node对数据进行格式化、摘要、裁剪。比如把搜索结果压缩成模型能消化的长度把 JSON 转成自然语言描述。这个节点经常被忽略但它是控制上下文膨胀的关键。3.2 边的条件成功、失败、重试、兜底节点之间的边承载的是流转逻辑。一条边至少要有三个属性触发条件、目标节点、传递的数据。触发条件最常见的有四种。成功边上一步执行成功且结果有效。失败边上一步抛异常或返回错误。重试边失败但可重试且重试次数未超限。兜底边所有条件都不满足时的默认路径通常指向一个降级处理或请求人工介入的节点。这里有个关键设计原则每条边都必须是显式的、可枚举的。不能出现模型自己决定走哪条这种模糊情况。如果某个判断确实需要模型参与那就把它做成一个推理节点让模型输出一个结构化的决策比如 JSON 格式的 next_action 字段然后由代码根据这个字段路由。这样模型只负责判断代码负责执行判断结果职责清晰。3.3 循环边与终止边图里最危险的两条边图结构里最容易出事的是两条边循环边和终止边。循环边是指向已访问过节点的边它让图具备了迭代能力。但循环边必须配一个计数器或状态标记否则就是死循环。我的做法是每个节点维护一个 visit_count循环边在路由前检查这个计数超过阈值就强制走终止边。终止边是通向 END 节点的边。它必须有多重触发条件任务完成、轮次超限、预算耗尽、连续失败次数超限。我见过一个案例Agent 因为工具一直返回空结果模型反复重试跑了 40 多轮才因为 token 超限停下来账单直接爆了。如果当时有连续 3 次空结果就终止的边就不会有这个事故。提示在设计图的时候先把所有终止条件列出来再设计正常流程。这跟写代码先写异常处理是一个道理——把最坏情况想清楚正常流程反而简单。3.4 用图结构重写那个失控的 while 循环回到第 2 节那个查资料生成报告的例子用图结构重写后大概是这样入口节点解析用户需求初始化状态推理节点判断当前需要什么信息执行节点调用搜索工具判断节点搜索结果是否有效有效 → 转换节点摘要压缩→ 回到推理节点无效 → 重试边最多 2 次→ 执行节点重试超限 → 兜底节点告知用户信息不足推理节点判断信息是否足够生成报告足够 → 生成节点 → 终止不足 → 回到搜索推理全局保护轮次超过 10 或 token 超过预算 → 强制终止这么一改整个流程的每一步都是可预测的。模型只在两个推理节点里发挥作用其余全是确定性代码。稳定性提升不是一点半点。4. 工程化落地的五个关键决策点4.1 决策一状态存内存还是存外部这是落地时第一个要拍板的问题。存内存进程内的字典或对象实现简单、读写快但进程一重启状态就没了也没法做分布式。存外部Redis、数据库、文件能持久化、能跨进程共享但引入额外的读写开销和序列化成本。我的判断标准是看任务时长和恢复需求。如果单个任务在 30 秒内跑完、不需要断点续跑内存足够。如果任务可能跑几分钟、或者用户可能中途关闭页面再回来就必须外部存储。折中方案是热状态放内存冷状态定期快照到外部兼顾性能和可靠性。4.2 决策二工具调用的超时与重试策略工具调用是 Agent 里最不可控的环节。外部 API 可能超时、可能限流、可能返回格式错误的数据。如果不做保护一个慢接口就能拖垮整个循环。超时设置要分层单次调用超时比如 10 秒和整体任务超时比如 120 秒。单次超时触发重试整体超时直接终止。重试策略我推荐指数退避 抖动第一次失败等 1 秒第二次等 2 秒第三次等 4 秒每次加一个随机抖动避免同时重试。重试次数控制在 2 到 3 次再多就是浪费。还有一个容易被忽略的点重试的应该是可重试错误。网络超时、限流可以重试参数错误、权限不足重试多少次都没用应该直接走失败边。这个判断要在执行节点里用代码做不要交给模型。4.3 决策三上下文裁剪的时机和粒度上下文膨胀是 Agent 的头号杀手。裁剪的时机有两个每轮开始前和每轮结束后。我倾向于在每轮结束后立即裁剪因为这时候刚拿到工具返回结果最清楚哪些信息有用、哪些是噪音。裁剪的粒度有三种。截断直接砍掉超出长度的部分简单粗暴但可能丢失关键信息。摘要用小模型或规则把长文本压缩成短摘要保留语义但损失细节。结构化提取只保留需要的字段比如从搜索结果里只提取标题、URL 和关键段落。我一般组合使用工具返回的原始数据先结构化提取提取后的内容如果还长就摘要最后再截断兜底。4.4 决策四模型输出的解析容错模型输出是自然语言但 Agent 需要的是结构化指令。这个翻译过程是错误高发区。模型可能输出格式不对的 JSON、可能把工具名拼错、可能参数类型不对。容错要做三层。第一层是格式约束用结构化输出能力比如 JSON mode 或 function calling强制模型按格式输出从源头减少格式错误。第二层是解析兜底如果 JSON 解析失败用正则或启发式规则尝试提取关键字段。第三层是失败反馈如果实在解析不出来把错误信息作为下一轮的输入喂回模型让它重新输出。这三层下来解析成功率能到 99% 以上。4.5 决策五可观测性从第一天就要有Agent 的调试难度远高于普通程序因为它的行为有随机性。同一个输入两次运行可能走完全不同的路径。没有可观测性你根本不知道问题出在哪。必须记录的东西包括每一轮的输入状态、模型的原始输出、解析后的动作、工具调用的参数和结果、路由决策、耗时和 token 消耗。这些数据要能按任务 ID 串起来形成一条完整的执行链路。我习惯在开发阶段就把这些打到日志里用结构化格式JSON Lines方便后续分析。上线后接入监控重点看几个指标平均轮次、失败率、平均耗时、token 消耗分布。任何一个指标异常都能快速定位到具体节点。5. 一次完整的踩坑复盘从死循环到稳定收敛5.1 故障现象任务卡在第 7 轮反复横跳说一个我亲身经历的事故。当时做的是一个自动整理会议纪要的 Agent流程是读取原始记录 → 提取待办事项 → 为每个待办匹配负责人 → 生成结构化纪要。上线第一周就收到反馈有些任务跑了十几分钟还没结束。我拉出日志一看任务卡在第 7 轮反复横跳。具体表现是模型提取出一个待办事项调用匹配负责人工具工具返回了一个候选人列表模型觉得不确定又调用搜索历史记录工具搜索完还是不确定又回去调用匹配负责人。这两个工具来回调用了五六次每次都返回相似但不完全相同的结果模型就在再确认一下和应该够了之间反复纠结。5.2 排查链路从日志到根因排查分了三步。第一步是复现用出问题的原始输入重跑确认问题稳定复现不是偶发。第二步是定位节点从日志里找到循环发生的两个节点确认是匹配负责人和搜索历史记录之间的循环边没有计数保护。第三步是分析模型行为把第 7 轮附近的模型输入输出单独拎出来看发现模型每次看到的上下文里前几次的搜索结果都还在信息过载导致它无法做出决断。根因有两个。表层原因是循环边缺少访问计数两个节点可以无限互相调用。深层原因是上下文没有及时裁剪历史搜索结果累积模型在冗余信息里迷失了判断力。5.3 修复方案三重保护 上下文瘦身修复做了三件事。第一给循环边加访问计数同一对节点之间的循环最多 2 次超过就走兜底边输出信息不足建议人工确认。第二在搜索历史记录节点后加一个转换节点把搜索结果压缩成最相关的 3 条 一句话摘要而不是把原始结果全塞回去。第三在推理节点的 prompt 里明确加了一条规则如果已经调用过匹配工具且拿到了候选人不要再调用搜索工具直接基于现有信息做决策。改完之后重跑同样的输入从原来的十几轮降到 5 轮以内耗时从十几分钟降到 40 秒。更重要的是行为变得可预测了——同样的输入多次运行路径基本一致。5.4 举一反三还有哪些循环容易失控这次事故之后我梳理了 Agent 里所有可能形成循环的地方总结出几个高危模式。工具互调循环两个工具互相触发比如搜索触发验证验证失败又触发搜索。反思循环模型反复反思自己的输出每次都觉得不够好无限优化。澄清循环Agent 反复向用户提问用户不回答就重问。重试循环工具失败后无限重试没有次数上限。针对这些模式通用的防护手段是任何可能形成环的边都必须有计数器或状态标记。这是硬性规则没有例外。另外在 prompt 里明确告诉模型什么情况下应该停止比告诉它什么情况下继续更有效。6. 让 Agent 稳定收敛的实操清单6.1 循环控制的三道闸门第一道闸门是轮次上限。根据任务复杂度设定简单任务 5 轮中等任务 10 轮复杂任务 15 轮。超过就强制终止返回当前最好的结果并标注未完全完成。第二道闸门是单节点访问上限。任何节点被访问超过 N 次我一般设 3 次就认为陷入了局部循环强制跳出。第三道闸门是资源预算。token 消耗、总耗时、工具调用次数任何一个超过预算就终止。预算的设定要基于历史数据比如统计过去 100 次成功任务的平均消耗然后设一个 1.5 倍的上限。6.2 错误处理的分类策略错误要分类处理不能一刀切。我分成四类错误类型典型场景处理策略可重试错误网络超时、限流指数退避重试最多 3 次参数错误工具参数格式不对把错误信息喂回模型让它修正参数逻辑错误工具返回结果不符合预期走失败边切换到备用方案致命错误权限不足、资源不存在直接终止返回明确错误信息分类的关键是在代码里判断不要交给模型。模型看到错误信息容易过度反应比如把参数格式错误理解成这个工具不能用然后换一个完全不相干的工具。6.3 结果验证怎么判断 Agent 真的做完了模型说任务完成不等于任务真的完成了。必须有一个独立的验证环节。验证方式有三种规则验证检查输出是否包含必需字段、格式是否正确、交叉验证用另一个模型或规则检查结果的一致性、抽样验证对关键结果做人工抽检。我通常用规则验证做第一道关比如检查生成的报告是否有标题、是否有正文、待办事项是否都分配了负责人。规则验证通过后再用一个轻量模型做语义验证判断内容是否切题。两道都过了才算完成。6.4 成本控制的几个实用技巧Agent 的成本主要来自模型调用。控制成本有几个立竿见影的技巧。用小模型做路由判断下一步该干什么这种简单决策用便宜的小模型就够了只在生成最终结果时用大模型。缓存重复调用同样的工具参数结果可以缓存避免重复调用。批量处理如果多个待办事项需要匹配负责人一次性把待办列表给模型让它批量匹配比一个个匹配省 token。提前终止一旦拿到足够的信息立即进入生成阶段不要为了更全面继续搜索。6.5 上线前的自检清单最后给一份我每次上线前都会过一遍的清单所有循环边都有访问计数保护轮次、token、耗时三个预算都已设置每个工具调用都有超时和重试策略模型输出解析有三层容错上下文裁剪逻辑在每轮结束后执行所有关键节点都有日志记录终止条件覆盖了完成、超限、连续失败三种情况有一个降级方案在 Agent 失败时能返回兜底结果这份清单看起来简单但每一条背后都是踩过的坑。Agent Loop 的工程化说到底就是把这些看起来理所当然的保护措施一条条落实到位。Graph Engineer 的价值不在于用了多高级的框架而在于把执行流程从信任模型转变为信任设计。模型负责它擅长的推理和生成代码负责它擅长的流程控制和错误处理各司其职系统才能稳定。我在实际项目里最大的体会是Agent 的稳定性不取决于模型有多强而取决于你对失败路径的设计有多周全。把最坏情况想清楚正常流程自然就顺了。这个道理跟做任何分布式系统都一样——happy path 谁都会写真正体现功力的是异常处理。