1. 失控的 Agent,先烧掉的往往是你的钱包
先说一个让我半夜从床上弹起来的场景:凌晨两点半,手机连着推送了十几条短信,都是同一个账号在连续扣费。我下意识觉得是信用卡被盗刷,结果是自家服务器上跑的 Agent 在发疯——某个调试任务里的提示流没有配置退出条件,Agent 在循环里反复调用大模型接口,每次请求都在计费,且半小时内已经烧掉了三百多块。那一刻我才真正意识到,AI 提示流编排器里最核心的组件不是提示词模板,不是模型路由,而是运行时看门狗和死循环熔断器。
这个开源系列写到了第 13 篇,前面我一直在分享怎么把提示流编排器做"顺"——多模型接入、模板变量注入、流式输出中转、上下文记忆管理,这些都是让 Agent 跑得更聪明的功能。但"聪明"的前提是"可控"。一个没有运行时保护的编排器,就像把车钥匙交给一个喝了酒的司机:他能把车开得很远,也可能把车开进沟里,而且你根本来不及阻止。
这篇博客专门聊怎么从 0 到 1 给编排器装上"运行时看门狗"和"死循环熔断器"。这不是什么高深的大模型理论,而是工程基础设施层面的兜底设计。如果你正在自建 Agent 应用、正在开发企业级的提示流工具、或者只是好奇为什么很多 Agent 框架跑着跑着就被平台风控了,这篇内容都值得你花十分钟读一遍。我会把设计思路、关键参数、踩过的坑全部摊开讲,代码结构也会拆给你看。
2. 失控的根源:为什么 Agent 会"跑飞"
在设计保护机制之前,得先搞清楚 Agent 为什么会失控。很多人觉得死循环是因为代码写得差,但实际上,在提示流编排器里,失控是系统性的、结构性的必然结果,不是偶发的 BUG。
2.1 大模型的输出天然不具备确定性
传统程序执行一件任务,走的是老实的分支逻辑:条件成立则执行 A,否则执行 B,结果可预期。但大模型不一样。同一个 Prompt,同一个模型,参数哪怕只差一点点,输出就可能天差地别。这意味着,你要求模型"如果任务已解决,输出 FINSH 并退出",模型今天会老老实实输出 FINSH,明天可能输出 "FINISH"、后天可能输出"任务已完成,现在退出"、大后天可能因为上下文太长开始胡言乱语,输出了一个"CONTINUE",然后把整个流程重新跑一遍。
这不是模型笨,而是 token 预测的天然概率性决定的。你写再严格的 Prompt 约束,也只能把"正确退出"的概率从 60% 提升到 95%,剩下的 5% 依然会绕圈子。一个线上系统如果依赖 95% 的成功率,那它迟早会在某一次中招。
2.2 子任务拆解链路的自激震荡
编排器的一大特点是把大任务拆成小任务,级联执行。一个子任务的结果交给下一个子任务,模型在每一步都要做判断。麻烦在于,模型判断的结果可能反复横跳。
举个例子:你让 Agent 总结一份文档,它先拆出"分析目录结构"这个子任务,子任务完成后,下一步模型却判定"需要先提取关键章节",提取完后又判定"需要先理解写作背景",每一步看似都在推进,但整个链条在逻辑上是原地打转的——每个子任务的输出都只能触发"下一个同级别子任务",永远到达不了终止节点。这就像在地铁环线上坐车,每一站都停,每一站都有人上下车,但你就是回不到起点站的出口。
2.3 外部依赖的响应变化引发悬挂重试
还有一种很容易被忽略的失控模式:Agent 是个复合体,它不只是调用大模型,还调用搜索 API、数据库、爬虫、内部服务。外部接口的响应时间、返回格式在真实运行中是会变的。某个 API 偶尔超时,或者返回了预期之外的 JSON 结构,Agent 的默认反应就是"重试"。
如果重试逻辑设计成"失败 -> 换个方式再调一次 -> 还失败 -> 再换个方式 -> 继续调",而每次换方式都会消耗新的 token、产生新的计费,这就是变相的失控。很多 Agent 平台的"异常执行终止"报错,本质上就是这种重试链条超出了平台的安全水位。不是平台主动想杀你的任务,是它不杀的话,你的账单会让整个系统完蛋。
2.4 通用 LLM 的上下文窗口是失控的助推器
还有一个不太好意思想到的问题:上下文越长,模型越容易迷失目标。Agent 循环执行到第 20 轮时,对话历史可能已经塞入了 5 万 token 甚至更多,最早的原始目标被大量中间步骤的痕迹淹没。此时模型判断"当前应该做什么"的能力明显下降,误判率上升,更倾向于选择"继续做点什么"而不是"停在这里"。
所以,把死循环熔断器简单理解成"检测到相同动作就切断"是远远不够的。真正有效的防护必须结合时间维度、步数维度、成本维度和语义维度四层指标。这也是我接下来重点讲的内容。
3. 运行时看门狗的第一层防线:四条腿缺一不可
看门狗这个词最早来自嵌入式系统里的 watchdog timer——一个独立的硬件计时器,如果主程序超过时间没有喂狗(重置计时器),系统就强制重启。我们的编排器不搞粗鲁的重启,但思想是共通的:持续观察运行状态,发现异常后按预案介入,而不是等到异常彻底把系统拖垮。
我给运行时看门狗设计了四条独立的检测维度。为什么必须独立?因为任何单一维度都有盲区。比如只看执行步数,一个合法的大任务可能天然需要跑 200 步,步数上限设得保守会误杀正常任务;只看成本,编排器在跑批量数据时本身就该花不少钱。四个维度互相补充,才能把误报率和漏报率同时压到最低。
3.1 执行时间上限:最粗暴但永远有效的兜底
时间是最公平的度量衡。一个 Agent 任务就算逻辑再复杂,也总有一个合理的时间边界。我在编排器的配置中心里设计了runtime_watchdog节点,默认参数如下:
| 参数 | 默认值 | 说明 |
|---|---|---|
max_execution_seconds | 900 | 单次任务最大允许执行时长(秒) |
soft_timeout_ratio | 0.7 | 达到该比例时首次触发软告警 |
timeout_action | interrupt | 超时后的动作,可选interrupt/terminate/notify |
report_interval | 30 | 看门狗状态上报间隔(秒) |
时间维度的难点不是设上限,而是软超时和硬超时的配合。硬超时直接打断任务,是最后的底线;软超时在 70% 水位时触发告警,相当于提前提醒你"任务可能要超了,你看一眼要不要继续"。这个设计非常有价值,因为很多任务在时间消耗到 60% 的时候已经能看出有没有收敛的趋势了,如果 15 分钟后还在原地打转,那大概率后面 5 分钟也跑不完。
线上执行时,看门狗检测到的软告警会通过回调推给你配置的 Webhook,比如短信、企微机器人等。你可以在收到软告警后决定:加时间预算、保持运行、或者手动终止。而硬超时不经过任何人同意,直接中断执行——在成本保护面前,程序员的犹豫不决是最大的敌人。
3.2 执行步数上限:防循环最直白的计数器
步数计数器是第二根腿。我实现了一个StepCounter,挂在编排器的执行循环外面,每次节点切换(包括模型调用、工具调用、条件分支转向)都增加计数。默认配置是单次任务最多 50 步。这个数字怎么来的?我统计了线上大量正常任务的步数分布:绝大多数真实任务在 8 到 35 步之间完成,超过 40 步的不到 5%。把上限设成 50,给极端复杂任务留了余量,又足以卡住死循环。
{ "node": "step_counter", "config": { "max_steps": 50, "must_reset_after_each_major_action": true } }这里有个很重要的细节:must_reset_after_each_major_action。如果计数器只计算"节点经过次数",那么一个节点内部发生若干次模型重试时,重试不会计入总步数,可能无限重试却不触发熔断。所以我在实现时把这个选项默认打开——每一次实质性动作,包括子步骤、重试、工具调用,都要计入总步数。宁可计数口径偏严,也好过漏掉慢性的重试风暴。
3.3 花费成本上限:面向云账单的实时熔断
成本维度是我在为线上服务做安全加固时新加的。很多自建编排器的小团队不敢上 Agent 自动化,一个重要原因就是怕模型调用费用失控。信用卡被刷爆这个说法虽然夸张,但你的月度 API 预算瞬间被打穿是一点都不夸张的。
成本熔断的实现方式很有意思,它不依赖外部计费系统——你在代码里根本拿不到精确的账单数据,等你从云控制台看到费用暴涨的时候,钱已经扣完了。正确做法是在调用发生之前,基于模型单价做预估:
预估成本 = 输入token数 × 输入单价 / 1000 + 输出token数 × 输出单价 / 1000每次调用模型前,把上一次响应的 usage 数据传给成本核算器。因为对话类模型输入是增量累积的(上次的 history 会带进下一次),每次调用的输入 token 数都要重新按完整序列预估。这里我犯过一个错:刚开始我只统计最后一次调用的预估成本,等于没统计——因为累计成本才是你真实的账单金额。后来改成维护一个运行期累计值,才把这个指标变成真正可用的熔断依据。
成本上限的默认值我设置为 3 美元(按当前主流模型定价计算),极简任务通常跑不完 0.5 美元,给你留足了余量。如果任务确实需要大规模处理,可以在任务启动时通过参数显式覆盖这个值,但必须额外确认三次,防止手一抖把预算设没了。
3.4 智能收敛检测:唯一能懂"语义死循环"的手段
前三根腿都是"硬指标",到了语义维度就是软指标了。智能收敛检测做了两件事:第一,给每轮任务生成语义指纹;第二,当发现指纹高度相似时判定任务陷入停滞。
语义指纹不等于简单地对文本做哈希。因为大模型的输出即使是同一意图,字面表达也可能完全不同。我用的方案是:对每轮核心输出抽取摘要向量,然后计算相邻若干轮向量的余弦相似度。实现上我引入了轻量级的文本嵌入模型完成向量化,没有直接用 LLM——因为嵌入模型便宜且快,适合高频计算。
向量A · 向量B / (|向量A| × |向量B|) 大于 0.92 => 认为内容高度重复0.92 这个阈值是实测调出来的。正常执行中相邻轮次输出通常有 0.7-0.85 的相似度,因为话题总是围绕同一任务;到了真正重复绕圈时,相似度经常冲到 0.95 以上。如果你把阈值设到 0.98,误判少了但漏判多了;设到 0.85,很多正常长任务的中间步骤会被误杀。0.92 是在我测试的 200 多组真实执行轨迹上找出的平衡点。
有一个反直觉的经验:语义检测不能只看相邻两轮,要看一个滑动窗口(比如最近 5 轮)。因为有的模型很鸡贼,它会隔一轮说点新话,下一轮又绕回去,相邻相似度不高,但窗口内的模式是重复的。滑动窗口能把这种"周期性重复"也识别出来。
四根腿合起来,运行时看门狗的基本结构就搭好了。接下来是最重要的一环:发现问题之后怎么熔断。
4. 死循环熔断器的阶梯式介入:从温柔提醒到一键杀停
"熔断"这个词借自电路保护中的保险丝——电流过大时主动熔断,保护整个电路。但如果每次都等到电流过大才熔断,电路已经受到冲击了。所以我的熔断器设计成阶梯式介入,按严重程度逐步升级,每种等级做不同的事。
4.1 第一级:软告警,不打断执行但记录现场
触发条件:执行时间达到软超时阈值,或语义相似度窗口连续 3 轮触发异常,或步数达到上限的 60%。
这个阶段的核心动作是"记录现场"。一旦进入软告警状态,我会立即触发运行时快照——把当前执行路径上的所有节点参数、已完成动作的历史链、上下文摘要、累计花费成本全部落盘。为什么记录现场如此重要?因为熔断后你可能需要复现问题或者人工接管,如果没有这些日志,你只能看到"任务是死的",但不知道它生前干了什么。很多排查工作做不下去,不是问题有多复杂,而是运行现场被后续日志覆盖了,根本没有留证。
软告警不做执行干预,系统继续运行。但状态面板上会标红提醒"该任务疑似陷入循环"。这也是为什么我说这个设计像"温柔提醒"——它先让你知情,而不是直接把你从睡梦中叫起来交罚款。
4.2 第二级:软中断,进入人工确认通道
触发条件:执行时间达到硬超时的 80%,或步数达到上限的 85%,或累计预估成本达到预设上限的 80%。
进入这个阶段后,熔断器会发起一个人工确认请求。任务的执行流会暂停在下一个安全节点(我称作 yeld 点),等待你的决定:继续(额外增加预算上限)、修改参数后继续、终止并保存现场。
这个"暂停在安全节点"的实现是关键技术问题。你不能随便切断正在执行的代码,那可能导致外部工具资源泄漏(比如已经建立的数据库连接没有释放)。我的做法是做检查点机制:任务执行循环每经过一个原子操作就检查一下自己当前的熔断等级,等级升到第二级时,当前操作完成后不再启动下一个操作,而是挂起等待指令。用俗话解释就是:不是一脚踩死刹车,而是先挂空挡滑行到路边再停。
人工确认通道怎么落?我用一个简单的本地接口加轮询实现:任务暂停后,编排器会把一个确认请求写入任务状态表,状态变成pending_decide;执行循环则退出到等待队列,每 10 秒检查一次状态表是否有更新。运行时控制台界面上会显示两个按钮:"继续执行"和"终止任务"。如果 5 分钟没收到指令,默认执行终止——防止人去开会了任务挂在那继续耗资源。
4.3 第三级:硬熔断,直接终止并标记账单
触发条件:执行时间超过硬超时上限,或步数超过max_steps,或预估成本超过上限。
到这一步就不留商量余地了。执行流被强制终止,当前任务状态标记为failed_by_fuse,同时把预估消耗成本记录到审计日志。注意这里我特意强调了"预估消耗成本",它和最终账单金额可能略有出入,但它能在 100 毫秒内给出近似值,足以支撑事后归因和成本复盘。
硬熔断还要做一件事:级联清理。因为 Agent 任务可能已经调用了外部工具——比如往数据库里写了一部分数据、发了部分邮件。即使执行流停了,这些外部副作用不会自动回滚。我的熔断器会遍历执行轨迹中的外部调用记录,对所有标记了reversible=true的操作执行补偿动作。比如写库操作,先记录反向 SQL;发邮件操作,先记录收件人列表,熔断时发送撤销通知(虽然做不到撤回,但至少人收到过一封"因系统异常,前序邮件作废"的说明)。这块容易遗漏,实际做的时候别省。
4.4 徽章机制:给熔断器一个"感情分"
有一个细节我单独拿出来说,因为它帮我少踩了很多坑:熔断器不能是一台六亲不认的冷血机器,它需要识别"哪些任务允许放飞自我"。
我在配置里维护了一个声明式规则列表,支持基于任务类型、任务发起人、模型供应商的分级白名单:
| 任务来源 | 默认权限 | 理由 |
|---|---|---|
| 用户手动触发 | 标准保护 | 实时交互任务,响应延迟直接影响体验 |
| 定时任务/批处理 | 严格保护 | 无人在场看护,宁可保守 |
| 内部调试模式 | 放宽保护 | 需要看到模型极限行为 |
| 标记为"实验性" | 半保护 | 允许超步数但记录异常到实验日志 |
每个任务启动前,熔断器会检查它的元数据标签并决定套用哪一档保护策略。这避免了一个尴尬局面:你写了个放飞的 A/B 测试任务,想看看模型在无保护状态下怎么表现,结果刚跑到第 30 步就被看门狗打断。分级后,实验任务可以跑得更野,但前提是它明确自报家门,而不是偷偷绕过保护。
5. 实操踩坑录:配置、自测与上线后的问题
讲了这么多设计,不落地都是纸上谈兵。这一章我从实操角度复盘几个最容易出问题的地方,如果你照着我的方案搭,这些坑绕开能省好几天。
5.1 形如虚设的步数计数器:你要数的是什么
第一个坑就是前面提过的"步数计数口径"。我最初实现时,把步数挂在"流程节点切换"事件上。结果生产环境跑了三天,熔断器一次都没触发过,我把日志捞出来一层层看才发现问题:某个工具节点内部有一次 while 循环,编排器的概念里它只是一个节点,但实际内部重试了四十多次模型调用。
这不是编排器的问题,是计数事件定义的粒度问题。修正之后,我把步数计数埋到三个位置:节点切换时、工具调用发起时、模型调用返回时。每一个都独立计数,取累计值跟上限做比对。瞬间,熔断器变得"灵敏"了很多——后来我统计发现,上线后触发熔断的任务里,有 40% 是靠工具调用次数抓到的问题,纯节点切换根本没那么多循环机会。
5.2 语义检测的维度灾难:向量化开销比任务本身还高
语义收敛检测虽然效果好,但写代码时容易失控。我最初版本每轮循环都会调用一次嵌入模型做向量化,结果一个 15 步的小任务就要额外打 15 次嵌入请求。计费虽然不贵,但延迟增加让人觉得笨重。
后来我加了几个优化:第一,只有执行步数超过 15 步(默认正常运行大多在这个数以内)才启动语义检测;第二,对每轮输出先做长度过滤,太短的内容直接跳过向量化;第三,向量缓存按任务 ID 存储,重复内容不重复计算。这三个优化叠加后,语义检测的额外开销降到了全任务的 5% 以内,而且没有影响熔断触发效果。
5.3 熔断测试不能只在测试环境跑,要搞"故障注入演练"
我刚开始对这个系统特别有信心,因为单元测试全绿。结果第一次上生产灰度,就遇到了一个没预料到的场景:某个慢任务走到了软超时阈值,看门狗记录完现场,但任务没有暂停,而是继续往前跑——原因是我在软告警分支里忘记返回状态码,导致流程不认为这是"异常",继续推进了执行循环。
这类问题写单元测试是测不出来的,因为你知道自己在测这个分支。真正能测出来的方法是故障注入演练:在测试环境故意构造一个必然会死循环的 Prompt 模板,让 Agent 去执行,然后观察看门狗能不能按预期时间触发软告警、软中断和硬熔断。我把这个过程做成了 CI 的一部分——每次构建跑一遍几秒钟的故障注入用例,保证熔断链路不会被后续改动弄断。
下面是我常用的故障注入样例,你直接拿去改也行:
系统提示词(刻意制造死循环): 你现在的任务是"完成将数字1加到100的迭代,每一步都输出中间结果"。 注意:无论计算结果是否正确,只要没有达到最终输出,你必须继续执行加法运算,不得结束。这种提示词没有任何合理的退出条件,让模型自己跑只会无限循环。把它喂给编排器,就是检验看门狗最好的试纸。如果配置正常,几步之内就应该触发软告警,然后按阶梯进入熔断。如果等了半天没反应,说明熔断链路有 bug,别上线。
5.4 线上流量比测试复杂得多:注意上下文压缩的干扰
上线之后还会遇到一个很有趣的现象:有的任务并不是死循环,但语义检测判定为高度重复。查了半天才发现,是上下文压缩机制在捣乱——当对话历史超过窗口时,编排器会把早期历史做摘要,摘要和最近几轮的表述在语义上高度接近,从而触发了相似度预警。
这种情况不算误报,因为任务确实在"重复加工同一个主题",但也确实不是需要熔断的紧急情况。我的对策是把语义相似度阈值从 0.90 调整到 0.92,同时把窗口从 3 轮扩大到 5 轮——因为上下文摘要导致的相似往往只持续一两轮,扩大到 5 轮后这种假阳性会被平均掉。
5.5 最终兜底:别让熔断器"熔断"了熔断器自己
最后一个原则性建议:熔断系统本身的失败不能影响主流程。我给熔断器代码做了大量降级设计——如果嵌入模型超时导致语义检测不可用,自动降级为只依靠步数、时间和成本三维检测;如果成本核算器拿不到模型单价(比如新增了未知模型),按最高单价计费保守处理;甚至熔断器自身崩溃了,也要保证任务执行循环能继续走完基本逻辑,顶多失去保护。
这个设计哲学可以概括为:保护系统是盾,不是矛。它不能反过来成为新的单点故障。你在引入我下面的示例代码时,也别忘了给熔断逻辑包一层捕获所有异常的 outer guard。
6. 代码骨架:一条 300 行左右的完整实现路径
前面写得比较概念化,这节直接给代码。我的实现目标是最短路径内跑通这套机制——不引入重量级框架,核心逻辑几百行就能落地。为了让阅读友好,我拆成几个类来说明。
6.1 看门狗状态机核心
# watchdog.py import time from enum import IntEnum class FuseLevel(IntEnum): OK = 0 SOFT_WARN = 1 SOFT_INTERRUPT = 2 HARD_KILL = 3 class Watchdog: def __init__(self, rules): self.rules = rules # 任务级规则 self.total_steps = 0 self.total_est_cost = 0.0 self.start_ts = time.time() self.level = FuseLevel.OK self.convergence_checker = ConvergenceChecker() def heartbeat(self, event): # 每次执行循环传递 step/emit_cost 事件 self.total_steps += 1 self.total_est_cost += event.get('est_cost', 0.0) elapsed = time.time() - self.start_ts self._update_level(elapsed) def _update_level(self, elapsed): cfg = self.rules if (elapsed >= cfg.max_execution_seconds or self.total_steps >= cfg.max_steps or self.total_est_cost >= cfg.max_cost): self.level = FuseLevel.HARD_KILL return if (elapsed >= cfg.max_execution_seconds * cfg.soft_timeout_ratio or self.total_steps >= cfg.max_steps * 0.85 or self.total_est_cost >= cfg.max_cost * 0.8): self.level = FuseLevel.SOFT_INTERRUPT return if (elapsed >= cfg.max_execution_seconds * 0.7 or self.total_steps >= cfg.max_steps * 0.6 or self.convergence_checker.detect_repetition()): self.level = FuseLevel.SOFT_WARN这个状态机的核心就是heartbeat方法,每轮执行循环都会喂一个事件进去。它的判定逻辑是显式比较,不依赖任何第三方库——因为看门狗自身要保持极简,越少的依赖越不容易出故障。
6.2 执行循环里的检查点挂起
# executor.py def execute(self, task): wd = Watchdog(task.rules) for event in self.run_loop(task): wd.heartbeat(event) if wd.level == FuseLevel.HARD_KILL: raise FuseError("HARD_KILL: 任务超过熔断阈值") elif wd.level == FuseLevel.SOFT_INTERRUPT: self.request_human_decision(task) # 异步请求人工确认 decision = self.await_decision(timeout=300) if not decision.get('continue'): raise FuseError("SOFT_INTERRUPT: 人工确认终止")很多 CDC(检查点连续性)设计的核心就是这里:任务每走一步就检查熔断等级,如果进入 SOFT_INTERRUPT 就停下来等人工决策。await_decision内部实现可以是一个简单的状态表轮询,也可以接入消息队列,取决于你的部署形态。
6.3 语义收敛检测的滑动窗口实现
# convergence.py class ConvergenceChecker: def __init__(self, window=5, sim_threshold=0.92): self.window = window self.sim_threshold = sim_threshold self.vectors = [] def add_embedding(self, vec): self.vectors.append(vec) if len(self.vectors) > self.window: self.vectors.pop(0) def detect_repetition(self): if len(self.vectors) < self.window: return False avg_sim = cosine_similarity_matrix(self.vectors).mean() return avg_sim > self.sim_thresholdcosine_similarity_matrix可以自己写,N 对向量两两求余弦相似度,窗口 5 的话就 10 次计算,开销可忽略。这里我用的阈值是整体均值,注意如果你执行的任务类型多样,也可以针对特定任务类型覆盖阈值参数。
6.4 成本核算的预估策略
# cost_estimator.py MODEL_PRICE_TABLE = { "claude-3.5-sonnet": {"input": 3.0, "output": 15.0}, # 每百万 token 美元 "gpt-4o": {"input": 2.5, "output": 10.0}, "deepseek-chat": {"input": 0.5, "output": 2.0}, } def estimate_cost(model, prompt_tokens, completion_tokens): price = MODEL_PRICE_TABLE.get(model) if price is None: # 未知模型按默认高价处理,万一没更新表也不至于熔断失效 price = {"input": 3.0, "output": 15.0} return (prompt_tokens / 1000000 * price["input"] + completion_tokens / 1000000 * price["output"])注意我用的单位是"每百万 token 美元",这是主流模型 API 的计价习惯,别用成每千 token。一个小数点差 1000 倍,在成本熔断开销上可能就是天壤之别。未知模型按最高档处理这个设计很关键——它保证熔断判断永远偏保守,宁可误杀任务也不漏报一个烧钱的洞。
6.5 注册与启用的最终形态
# main.py from watchdog import Watchdog, FuseLevel from executor import execute rules = { "max_execution_seconds": 900, "max_steps": 50, "max_cost_dollars": 3.0, "soft_timeout_ratio": 0.7, } task = { "id": "task-001", "prompt": "请分析财报中的关键风险点", "rules": {**rules, "level": "standard"}, } result = execute(task)这段代码把前面所有的类组合起来了:任务定义带上保护规则,execute内部自动初始化看门狗和执行循环。任务不主动传规则时,从配置中心拉取默认档。这就是整个熔断系统最简的落地形态。
7. 上线运营后的调整:从硬编码到动态策略
代码跑通只是第一步。真正把它用好,需要根据实际运行数据不断调整参数。我最后分享三个在运营过程中验证过有效的调整方向。
第一,默认阈值必须参考真实数据分布。上线跑一两周后,把正常任务的行为日志拉出来,看步数分布、耗时分布、成本分布,然后把熔断阈值放在 99 分位附近。我现在的默认参数就是这么调出来的——先用一个宽裕的预设跑,再根据真实情况收紧。一上来就设一个极严的参数,会导致大量合法任务被误杀,用户很快就不再信任这个系统了,保护机制等于形同虚设。
第二,熔断触发要回写知识库,沉淀成组织的运行常识。每触发一次熔断,我都要求执行现场附一个"归因标签",比如 "reason=重复执行某工具调用"、"reason=对话历史过长导致的判断漂移"。累积几个月后,你会有非常清晰的任务故障画像:哪些模型容易绕圈、哪些提示词结构容易触发死循环、哪些工具节点的时间波动最大。这些数据反哺给提示词模板的设计,才是熔断系统最长期的价值。
第三,熔断的恢复路径必须自动化。人工决策通道解决的是"执行流要不要停",但停完之后怎么办?如果任务是被误杀的,你得能重新入队重跑;如果确实有 bug,你得能自动跳过异常分支重试。我的方案是给任务装配重试策略:熔断后先检查任务是否幂等,幂等就直接重跑;不幂等就先回滚外部副作用,再重跑。这一层做得好,熔断系统才算完整闭环。
最后说点心得体会:自动化的 Agent 应用最怕的不是模型能力不够,而是"模型能力足够但不可控"。给编排器装上运行时看门狗和死循环熔断器之后,我才敢放心地让它在生产环境里跑那些不需要人盯着的长任务。也希望这份设计能帮你少踩一些坑——毕竟没人喜欢半夜被信用卡扣费短信叫醒。