给 7B 小模型当 Agent 框架:真机三轮排障,十条失败链逐个补

强模型当执行者(Executor)时,框架的很多设计是感觉不到的------它自己会读任务、会用工具、会看结果、会如实汇报失败。换成在本机跑的 7B 量化模型后,这些「免费的能力」全部开始收费。这篇记录我们一个桌面 AI 应用的多智能体协作在小模型真机上的排障全过程:每一层以为修好的东西,都怎么在下一层以新形式重新坏掉。

排查有个效率技巧:不要盯着界面猜,直接读应用数据目录里的 SQLite。协作运行表、桌面任务表、任务步骤表、角色交接表四张表按时间线串起来,每一步的决策、观察、载荷、错误都在,比看界面转圈快十倍。

第一层:模型什么都没拿到

症状:协作任务一启动,执行角色一个工具都不调,当轮直接失败。

先怀疑模型,读库发现交接载荷是对的;再读执行角色的请求消息,问题露馅:传给它的任务目标里只拼了子任务标题。子任务单上的背景说明、逐条验收标准、绑定了哪个知识库、可用工具目录------全在规划者产出的结构化对象里,适配器往下传时只取了标题。强模型光看标题能猜对八成,小模型看到「帮用户做职业规划」这种标题,手边没有任何工具描述,唯一合法动作就剩失败。

修法不是加提示词,是把完整上下文拼进决策消息:子任务标题 + 背景 + 验收标准 + 绑定库名 + 工具名与描述清单 + 硬规则。截图观察模式(screen)的决策消息同样补了这套------它之前也只有标题。

教训 1:弱模型不会主动要信息。它能用到的东西,必须确信已经出现在它眼前的上下文里,一个字段都不能假设它「应该知道」。

第二层:它调用了工具,但没看到结果

症状:工具确实调了,界面显示「已完成」,结论正文是空的,还一本正经地汇报成功。

查步骤详情,工具返回了完整正文。再查喂回模型的观察文本------只有一个工具名和「详细结果见历史步骤」。历史步骤里有什么?成功步的摘要也只记录了工具名。也就是说,工具返回的正文从来没有回注给模型,它是在盲调:调了检索,没看到检索到什么,然后基于「我调过检索了」这个事实宣布完成。

修法两处:构造观察时把最近一次动作的输出正文注入(限长,避免撑爆上下文),步骤摘要补上结果摘要而不是只有工具名。

教训 2:Agent 循环里「调用」和「看到结果」是两件事。工具执行成功不等于模型消费了输出,观察内容必须显式回灌。

第三层:它学会了检索,但学不会停

症状:模型开始用知识库了,然后把二十步预算全部花在检索同一件事上。

「简历经历怎么写」「简历的经历如何写」「简历经验写法」------每次换一个近义关键词,每一次都有结果,但它从不进入「基于已有资料写结论」的阶段,直到步数耗尽,任务预算触顶。

这次是双重约束。软的部分:每轮决策消息里实时展示预算账本------「已用动作 x/20、检索 n/3」,并在系统提示词里声明检索全任务累计上限 3 次,达到上限禁止再检索、必须立即给结论,资料缺口列进未竟事项。硬的部分:模型自由产出的「未竟事项」天然就是为这种时刻准备的交付通道------不能因为资料不够就卡死,带着缺口交付结构化结论,比烧光预算体面得多。

预算账本为什么必须每轮重发而不是只声明一次?小模型的注意力对「开头说过的规则」衰减极快,但对「上一条消息里的数字」高度敏感。「检索 3/3 次(已达上限,禁止再检索,本轮必须输出完整结论)」这句话出现在当轮观察里,遵守率远高于三轮之前的系统提示。

教训 3:不要让模型做开放式的「再查查」。给任何可重复动作一个明确预算,并把当前预算在每一轮都摆到它眼前。

第四层:它反过来------一次不查直接收尾

症状:绑定了知识库的任务,模型检索 0 次,直接输出 done 或者 fail。

提示词里写了「需要资料时请先检索」,没有用。于是加了这套版本里我最喜欢的确定性机关------零检索收尾门控:

  1. 决策循环收到 done/fail 时检查:可用工具含检索、历史中没有任何一次成功检索、纠正宽限还有余额;
  2. 三个条件同时成立,这次决策被驳回,模型收到的下一条观察是强制纠正文本:「你下一步唯一合法决策是调用检索工具」,并附上工具名;
  3. 纠正不计入连败次数(否则模型会觉得自己在受罚而加快放弃);
  4. 宽限两次。第三次它仍然坚持收尾,放行,尊重终态。

第四条是这个门控能长期存在的关键。任何「纠正模型」的机关都必须内置放弃条件,否则只是把一种死循环换成另一种。

门控本身是决策循环里的一个纯函数,输入足够小,行为完全可测:

text 复制代码
mandatoryRetrievalCorrection({ 决策, 可用工具, 历史成功检索数, 纠正次数 }):
  // 不在门控范围:模型选择继续调工具
  if 决策是工具调用:        return null
  // 没授权检索,收尾是合法选择
  if 可用工具不含检索:      return null
  // 已经查证过,允许基于证据收尾
  if 成功检索数 > 0:        return null
  // 宽限用尽,尊重模型终态(它可能真的判断无需资料)
  if 纠正次数 >= 2:         return null
  // 其余情况:done/fail 一律驳回,下一轮观察换成强制纠正文本
  return 强制纠正观察(含正确工具名与调用示例)

还有个细节:纠正轮的观察不进失败计数。连续失败熔断本来是保护机制(连续三次动作失败自动终止),如果驳回模型决策被记成一次失败,模型两轮纠正后就会撞到熔断阈值,表现为「被强制查资料反而被强制终止」。门控是框架在说话,不是模型又搞砸了一件事,记账必须分开。

教训 4:软约束管不住的行为,用确定性前置校验拦;但拦截器自己要有预算上限,并且拦截不能产生惩罚信号。

第五层:它失败了,但失败本身把交接搞崩了

症状:执行角色连败后,规划者没有收到返工单,协作以一个含糊的「非法交接载荷」终止。库里面真实错误是模型通道连续 403。

链路是这样断的:连败时没有产出最终步骤,终态总结字段是空字符串;交接载荷经 zod 校验,空串不过,整包被拒;拒收发生在交接边界,执行阶段的真实错误信息随载荷一起被丢弃,界面只剩协议层报错。

修法:终态总结用「终态注记」兜底保证非空------模型没给总结,就用末次失败的真实原因(含 HTTP 错误)生成注记。同时把降级通道里一个空 catch 拆了:每次 fallback 失败都打印原因,取消信号已触发时立即上抛、不再重试。那个空 catch 是整个排障期最大的黑洞,403 被它吞了整整两轮排查。

教训 5:跨边界的结构化载荷,每个字段都要问「最坏情况下它是空串还是 undefined」;空 catch 里藏着的错误迟早会在最远处的校验里爆炸。

第六层:规划者乱派活,失败重生也走错门

还有两个性质类似的问题:

  • 乱挂桌面域。纯读资料、分析、规划、写作类任务,规划者给子任务附了桌面截图域,执行角色于是对着无关截图做视觉决策,必然失败。在规划者提示词里明令禁止,并把这条约束同时写进两个观察模式的上下文。
  • 失败后的「重新生成」走错通道。任务模式的失败重试走了普通对话的重新生成逻辑,必然报「没有可重新生成的用户消息」------任务模式的消息是系统占位的。改为显式走协作重发。顺带还修了一个多轮串卡:占位助手消息的 id 原来是空字符串,多个并行泳道全部挂在同一个键上互相覆盖,改为本地生成唯一 id。

另外,规划者协议失败降级为「原目标直派」时,早期实现把工具域一起丢了,执行者又回到零工具裸奔。降级单必须保留可用工具域;知识库则刻意留空,语义是继承助手绑定的全部库。

教训 6:降级路径是功能的一部分,不是异常分支。降级时丢掉的每个字段都要显式决定「默认值是什么」,空值在弱模型手里就是零能力。

收口:一张修复后的决策循环骨架

text 复制代码
每一轮:
  1. 拼决策消息 = 子任务完整上下文 + 工具目录 + 实时预算账本(动作 x/max、检索 n/3)
  2. 模型产出决策(工具调用 / 完成 / 失败)
  3. 前置确定性校验:
     - 零检索即收尾且宽限未用尽  → 驳回,注入强制纠正观察(不计连败)
     - 检索已达 3 次还想再检索    → 提示词侧已封死,账本同步提示
  4. 执行动作,观察 = 工具正文(限长)+ 历史结果摘要,而非工具名
  5. 终态出口:总结为空 → 末次真实原因兜底注记;交接载荷字段全部非空保证
  6. 任何异常:打印原因;已取消立即上抛,绝不静默重试

修复前后的行为对照,每一行都能在数据库时间线里复核:

场景 修复前(7B 真机) 修复后
纯知识任务首轮 零工具,直接 fail 收到完整上下文与工具目录,发起检索
检索后交结论 「已完成」但正文空白 观察含返回正文,结论引用证据片段
检索不顺利 换近义词到 20 步耗尽 3 次后按提示交付,缺口列入未竟事项
不想检索直接收尾 done/fail 被接受,白卷交付 前两次驳回纠正,第三次放行
通道 403 连败 空串致交接被拒,错误被吞 末次原因兜底注记,403 直达泳道卡
纯写作任务 被挂截图域,看无关屏幕失败 规划阶段禁止桌面域,走工具模式
失败后点「重新生成」 报「没有可重新生成的用户消息」 走协作重发,泳道卡重新跑一轮

修完之后用 7B 跑知识类任务,一轮通过。产出质量依然一般------正确但模板化,这是模型的天花板,框架不再为它负责。框架负责的部分变成了可验证的承诺:它不会零工具裸奔,不会盲调,不会无限检索,不会零查证交白卷,失败时一定带得走真实原因。

注意这些承诺没有一个写在提示词里。提示词负责「争取」,确定性代码负责「保证」------这是这次补课最核心的分界。

相关推荐
打工仔折腾 AI3 小时前
LangChain 工具调用实战:从 Tool 定义到 Agent 执行链路拆解
java·前端·人工智能·后端·python·langchain·ai agent 实战
for_ever_love__5 小时前
LangChain 上线部署:把链和 Agent 变成能对外服务的 API
langchain·部署·fastapi·rest api·服务·上线·langserve
染指11106 小时前
145.Agent-LangGraph基础概念和安装和简单代码示例
java·服务器·数据库·langchain·agents
一木 之林7 小时前
LangChain 第6章 Agent 智能体核心:create_agent 统一入口与 ReAct 循环
前端·react.js·langchain
小王师傅667 小时前
LangChain 系统化学习入门
学习·langchain
for_ever_love__8 小时前
LangChain 多工具协作:一个 Agent 不够用时怎么办
人工智能·langchain·langgraph·协作·多agent·多工具·工具编排
for_ever_love__8 小时前
LangChain 效果评测:没有评测,优化就是在瞎调
人工智能·学习·langchain·rag·评估·langsmith·评测指标
BD_Marathon8 小时前
ChatPromptTemplate初始化的六种参数类型
langchain
寻道码路9 小时前
AI Agent 工程化开发实战(一):Agent 开发的四个层次——积木、运行时、薄壳、套件
人工智能·langchain·大模型·ai agent·langgraph·ai工程化·deep agents