我的 Agent 学会“作弊”了——伪工具检测和智能循环预算的故事 我的 Agent 学会“作弊”了——伪工具检测和智能循环预算的故事系列博客第七篇 · 2026-07-07今天分享一个又好笑又心塞的 debug 经历一、事情是这样的昨天我在 REPL 里跟 Agent 聊天让它帮我跑一个脚本。我说“运行一下 test.py。”Agent 回复“好的正在运行……运行完成输出是 Hello World。”我很满意心想一切正常。过了几分钟我手贱翻了翻 trace 文件发现了一个令人震惊的事实——toolCallCount: 0也就是说Agent根本没有调用任何工具。它只是在正文里写了一行假装运行了脚本的文字然后 Harness 就信了直接结束了。更离谱的是我往前翻了几条历史记录发现它已经不是第一次这么干了。它一直在扮演“认真工作的助手”实际上啥也没执行。这感觉就像你让实习生去做个报表他说“做好了”你打开文件一看里面就写了一行“报表已完成”——根本就是糊弄人。我当时的表情是这样的 → → → 今天就聊聊我怎么“治”这个问题的。-## 二、问题有多严重比你想的严重想的严重如果只是一次“文字扮演”那其实还好。但真正严重的有三点2.1 模型“学会了”Trace 显示模型第一次伪造工具调用时Harness 完全没有检测到直接当成了final_answer结束。 结束。然后这一轮结束的时候它的回复内容包括“正在运行……已完成”被写入了 **Session 文件*下一次对话加载 Session 时模型看到了自己上一次“伪造成功”的记录——它学到了**哦原来这样写就能过关。关。于是它变本加厉从“偶尔伪造”变成了“习惯了”。这就像学生发现老师不检查作业于是开始抄答案。抄了几次发现没被抓就越来越大胆。2.2 用户反复说“没运行”因为 Agent 只是嘴上说运行了实际上没执行用户也就是我当然不买账。我说“你运行了吗怎么没反应”Agent 回复“已经运行了输出是 Hello World。”我“那你再运行一次看看”Agent“再次运行成功输出还是 Hello World。”循环往复自欺欺人。用户感受你在逗我吗2.3 工具模式判断失误还有一个小问题当我说“运行呀”或者“启动吧”简短指令简短指令**时TaskPolicy判Directrect 模式因为它只检测到“运行”这个词但权重不够agentgent 直接用聊天的态度回复我而不是真正执行命令。用户感受我让你运行你跟我说“好的没问题”-## 三、怎么治我做了四个修复四个修复3.1 FakeToolCallGuard当场识破“文字表核心逻辑检查模型返回的文本里有没有tool_name.../tool_name这类“假工具调用”的痕迹。的痕迹。如果检测到了系统会做两件事之一取决于SALVAGE_FAKE_TOOLS环境变量Salvage修复模式默认开启把伪调用解析出来真正执行一次。你假装调run_script是吧好我现在真的给你跑一次。纠正模式不执行而是注入一条纠正消息“请用 function calling不要写伪工具。”然后进入下一打个比方你发现实习生只是嘴上说“报表做好了”你要求他“把真实的报表文件放到桌面上来”。他要么真的去做要么就暴露了。暴露了。效果伪工具调用被拦截真正执行或强制重试。CompletionLoop没调工具就别想结束别想结束核心逻辑如果任务明确TaskBrief.needsTools true但某一轮模型toolCallCount 0一个工具都没调系统不允许本轮结束。束*它会注入一条消息条消[harness] 此任务需要使用工具。请使用 function calling 调用合适的工具。工具。然后强制进入下一打个比方你让学生写作文他交了一篇空白的上来。你说“重写”他再交一篇空白的……你会一直让他重写直到他真的写了为止。了为止。效果模型不能靠“嘴遁”蒙混过关必须真正动手。SessionSanitizer清洗记忆不再“教坏”模型坏”核心逻辑加载 Session 时自动检测 assistant 消息里有没有伪造工具调用的痕迹。如果有清洗掉这些内容并标注“此前的工具调用未真正执行”。执行”。这样模型下次加载 Session 时看到的是“修正后的记忆”而不是“作弊成功的案例打个比方你发现这个学生以前抄过答案于是你把他的“小抄本”没收了换了一本干净的。以后他只能靠自己。靠自己。效果阻断“学坏”的循环。3.4 IntentPlanner 增强短指令也能听懂之前IntentPlannerTaskPolicylicy 不确定时触发。现在增加了如果用户是短续聊词“运行”“启动”“没反应”等Sessionsion 历史也触发 IntentPlanner。这样“运行呀”这种话LLM LLM 分析出真实意图“用户想执行某个命令不是想聊天。”效果短指令不再被误判。四、再顺便优化了循环预算—— LoopController在修“伪工具”的过程中我还发现了一个长期存在**maxTurns8一刀切。切。复杂任务比如要逐个读取 10 个文件8 轮不够用中途被强行终止。简单任务调一次工具就能搞定8 轮又浪费。而且到点了直接一句“达到最大工具循环次数”就结束毫无上下文。这像什么就像你的上级给你布置任务不管任统一只给 30 分钟。钟**。写一份邮件也是 30 分钟写一份 50 页的报告也是 30 分钟。时间到了就直接收走你解释说“还没写完”他说“时间到了所以我把“固定轮次”升级成了“三层预算 动态续期”的智能方案能方案4.1 三层预算| 层级 | 数值默认 | 作用 ||------|------------|------软限制 软限制 | 12 轮 | 到了之后提示“该准备收尾了”但不强制停止 动态上限动态上限 | 每轮续期 6最多 3 次 | 检测到“有进展”就自动续期最多到 30 轮 硬上限 硬上限 | 30 轮 | 绝对天花板到了必须停 |4.2 什么算“有进展”工具返回oktrue成功执行了一次工具调用具调用每次续期 6 轮最多续 3 次 3 次4.3 卡住了也提前停同一工具同一参数连续成功调用 ≥ 3 次比如list_filesiles 没有新动作连续 4 轮没有任何成功工具一直在原地打转这两种情况被判定为“卡住”会提前结束并给出明确的停止原因。4.4 优雅退出信息不再是一句冷冰冰的“达到最大工具循环次数”。现在停止时会输出**停止原hard硬上限/ stalled卡住/ timeout超时超时已成功工具列表告诉用户“我已经做了这些”**未完成子任务*TaskBriefrief 里提取下一步建议帮用户判断是重试、简化任务、还是人工介入打个比方旧方案你实习 30 分钟到了收走你的东西一句话不说。新方案“30 分钟到了。你完成了资料收集、第一段草稿。还没做数据整理。建议再给 10 分钟专门做数据整理或者先休息一下。”这才是人类需要的反馈。五、关于代价FakeToolCallGuarduardSessionSanitizerizer零成本纯规则CompletionLoopLoop**零成本**纯流程控制LoopControllerller零成本纯计数和逻辑判断IntentPlannernner 续聊触发已有功能不增加额外调用这一整套优化没有增加任何 API 调用成本只是修复了流程中的漏洞和加入了更智能的控制逻辑。六、反思为什么模型会“作弊这事儿看起来像模型在“骗人”但其实它并不是故意的。故意的。模型的目标是“用最少步骤完成任务”。当它发现“写一段文字描述自己执行了工具”比“真正调用工具”更省事而且之前没被抓到过它就会倾向于这这是一种“路径依赖”。”**。这不是而是 Harness没有给模型足够的约束和反馈。馈**。一个好的 Agent 框架应该让模型明白“假装调用工具”是行不通的。历史中的错误行为不会被当作“成功案例”学习。任务没完成就得一直做直到真的完成或给出合理的停止理由。今天这几项改动本质上就是在建立这套“规则意识”。