把 Agent 从 Demo 搬进生产,最先暴露的不是「模型不够聪明」,而是系统工程问题:工具一多就乱选,任务一长就烧钱,结构化输出一错就全链路挂,反思不设刹车能在原地空转到天荒地老。
下面按四个真实踩坑场景,分别讲为什么有问题 和怎么兜底。
1. 生产环境挂了 10+ 个工具,准确率越来越低
为什么有问题
通常我们挂工具的方式是:在system prompt里写入工具名和描述
模型选工具,本质上是在做离散决策。工具数量从 3 个涨到 15 个,每步候选动作空间近似指数膨胀------不是「多几个 API 而已」,而是:
- 描述互相抢语义 :
search_docs和query_knowledge_base边界不清,模型随机漂移; - 错误工具链式放大:第一步搜错库,后面 5 步都在补锅;
- Prompt 被工具 schema 淹没:真正任务上下文反而被挤到后面,注意力稀释。
线上常见现象:工具 12 个时,首轮工具命中率从 85% 掉到 50% 以下,且越跑越偏。
怎么治
核心手段渐进式披露
核心指标 使用 Recall@5 / Precision@5 F1@5
现实落地方案,一线团队(如 OpenAI、微软、字节、百度等)近年普遍用RAG(检索增强生成)+ 工具索引的方式大幅提升工具命中 Top5 的准确率。具体做法不仅是控制数量,更强调召回/排序环节的精细设计:
先用 embedding 向量化每个工具(描述、入参、Schema、FAQ),模型根据当前 query 去算 top5 most relevant,再 shortlist 供 LLM/Router 二次甄选,适合工具≥10的业务复杂
常见架构图:
sql
User Query → Router(可RAG)→ 工具向量索引(召回 top5)→ 工具列表 → LLM/Planner → 执行
经验值:单步可见工具 ≤ 5 个,且每个工具的一句话描述必须回答「它和旁边那个有什么不同」。否则宁可少挂,也不要「先都挂上再说」。
2. 复杂任务 token 是普通对话的几十倍,成本激增
为什么有问题
对话 Chat 是「一问一答」;Agent 是「多轮推理 + 工具回灌 + 历史累积」。真正膨胀的通常不是 System Prompt 本身 ,而是后面不断堆上去的动态上下文:
- 每调一次工具,Observation 全文原样塞回 messages;
- 长任务不压缩,第 20 轮里还躺着第 1 轮的完整 diff、完整日志;
- 反思、规划、执行共用同一条历史,同一段代码被反复读进 prompt。
一个「修 Bug + 写测试 + 开 PR」的任务,token 到 普通问答的 30~80 倍并不夸张;无人值守跑 48 小时,账单可以非常难看。
怎么治
核心原则:System Prompt 保持稳定、尽量不动;压缩的是后续累积的对话与工具回灌。
可以把每次请求的上下文拆成两层理解:
| 层级 | 典型内容 | 策略 |
|---|---|---|
| 固定层(保留) | System Prompt:角色、规则、输出格式、当前阶段工具子集 | 全量保留;变更少,适合 Prompt Cache |
| 动态层(压缩) | 用户输入、Assistant 推理、Tool Observation、反思与中间结论 | 摘要、滑窗、指针化;只留决策结论 |
也就是说:不是把 System Prompt 越压越短,而是别让「跑出来的历史」无限长高。
(1)动态层怎么压
- 工具返回指针化:Observation 只留「摘要 + 路径/行号/链接」,原文进对象存储或向量库,需要时再按需拉取;
- Rolling Summary:每 N 步用一次小模型,把较早的 messages 收成一段「已完成 / 已决策 / 待办」摘要,替换掉原始长对话;
- 代码类结果只留 diff / AST 摘要,禁止整文件在多轮里反复注入;
- 滑窗:最近 2~3 轮保留原文保证连贯,更早的只留摘要块。
text
[System Prompt] ← 固定,每轮完整带上
[任务摘要 state] ← 滚动更新,替代旧历史
[最近 K 轮 messages] ← 短滑窗
[当前 user + tool 结果] ← 本轮新增,入库前先摘要
(2)硬预算(兜底)
即使做了压缩,也要设天花板,防止摘要失败或反思空转:
yaml
agent_run:
max_steps: 40
max_tool_calls: 25
max_tokens_per_step: 8000 # 单步动态层上限(不含 cache 命中部分)
max_total_tokens: 200000
max_wall_time: 30m
daily_budget_usd: 50
任一触发 graceful stop :输出当前 state 摘要 + 未完成项,而不是 silent fail。
(3)模型分级
固定层逻辑不变;动态层里「摘要、路由、工具选择」尽量用小/中模型,大模型只用在真正需要长推理的一步:
| 步骤 | 模型 | 原因 |
|---|---|---|
| 历史摘要 / 路由 | 小模型 | 便宜、够快 |
| 工具选择 | 中模型 | 结构化决策 |
| 复杂生成 / 终审 | 大模型 | 只在必要时调用 |
(4)可观测
- 每步拆分记录:
system_tokens(固定)、dynamic_tokens(动态)、cached_tokens、tool_name; - 看 动态层占比 是否随步数线性暴涨------若是,说明压缩没生效;
- 单任务 cost 超阈值告警;周报看 cost per successful task。
3. 模型输出的 JSON 不对,如何兜底
为什么有问题
生产里 JSON 失败是常态,不是 edge case:
- 包裹 markdown code fence:
json ... - 尾部多逗号、单引号、注释;
- 混进解释性文字:
Here is the result: { ... } - schema 缺字段、类型错(字符串
"123"vs 数字)。
一旦下游 JSON.parse 直接硬吃,整步失败,Agent 进入无意义重试循环。
怎么治
text
原始输出
→ ① 提取(去 fence / 正则抠 {...})
→ ② 解析(JSON.parse / json5)
→ ③ 校验(JSON Schema / Zod / Pydantic)
→ ④ 修复(LLM repair 或规则补全)
→ ⑤ 降级(更宽松的 struct / 纯文本 regex)
→ ⑥ 失败上报(带 raw output 进日志,人工可复盘)
原则:解析失败是预期路径,不是异常路径;流水线里要有专门的处理分支,而不是 try/catch 后重跑整 Agent。
4. Agent 爱反思:不设停止机制,能在反思里循环一整天
为什么有问题
ReAct / Plan-Execute 类 Agent 常带「反思」步骤:评估上一步、规划下一步。模型一旦进入自我怀疑模式:
text
我觉得刚才不够好 → 再想想 → 还是不够好 → 换个角度想想 → ...
没有停止条件时,这是合法的死循环 ------每一步都在消耗 token,却不产生新的工具调用或外部副作用。线上表现为:任务状态一直是 running,日志里全是 meta-cognition,算力白白蒸发。
怎么治
(1)反思必须有预算
yaml
reflection:
max_rounds: 2 # 同一子目标最多反思 2 轮
max_consecutive_reflect: 1 # 禁止连续两步都只反思不行动
require_action_after_reflect: true
(2)显式状态机,而不是自由对话
Reflect 是有入口条件的状态(失败、校验不通过、置信度低),不是默认每步都走。
(3)设定终止条件
- 重复检测:最近 3 条 assistant 消息 embedding 相似度 > 0.95 → 强制 Act 或 Escalate;
- 无进展检测:连续 K 步无新 tool call、无文件变更、无外部 API 写操作 → 终止;
- Same error twice:同一错误信息出现 2 次 → 停止重试,输出诊断报告。
4. 串起来:生产 Agent 的最小治理清单
上线前建议至少过一遍:
| 检查项 | 标准 |
|---|---|
| 工具 | 单步可见 ≤ 5;描述互斥;有 selection 评测 |
| 成本 | 总 token / 步数 / 墙钟 / 日预算 四限齐全 |
| 上下文 | 工具结果摘要化;定期 rolling summary |
| JSON | extract → parse → validate → repair → degrade 五层 |
| 反思 | 有 max_rounds;禁止连续空反思;无进展自动停 |
| 观测 | 每步 token、tool、cost、状态可追踪 |
Demo 追求「一次跑通」;生产追求「十次里有九次在预算内收敛,剩下一次可控失败 」。上面四个坑------工具爆炸、token 账单、JSON 脆断、反思空转------本质都是缺边界:缺工具边界、缺成本边界、缺格式边界、缺循环边界。把边界写进配置和状态机,Agent 才能从「能演示」变成「能生产」。