Agent 工具调用的 5 个坑与 8 条兜底铁律

0 评论 55 浏览 0 收藏 32 分钟

做 Agent 功能总卡在工具调用、报错分类和规则兜底?本文基于记账 Agent 项目实战,从 messages 四角色、完整调用链路到工具 Schema 设计,再到生产错误分类、高频 Badcase 和效果度量指标,一次性讲透从 Demo 到生产级 Agent 的必经之路。

很多人做 Agent 功能,卡住的地方惊人地一致:

看不懂工具调用的底层逻辑、分不清报错原因、不知道该怎么设计规则兜底。

市面上的教程要么纯代码、要么纯理论,很少有人从产品视角把这件事讲透:会话怎么存、工具怎么调、错误怎么分类、线上故障怎么治理、效果怎么衡量。

本文基于一个记账 Agent 项目 + 一线生产落地经验,从基础上下文、完整调用链路、工具设计规则,到全品类生产错误、高频 Badcase,以及度量 Agent 效果时该看哪些指标,一次性讲透。

一、Agent 和通用对话大模型,本质区别

通用对话大模型:只说话,不干活。 它依托训练时学到的知识直接生成回答,能力边界就是“文本进、文本出”,碰不到你的业务系统,也做不了任何实操动作。

工具调用型 Agent:模型只负责想,活儿由工具干。 记账、查订单、算数据、读写文件、调用接口 —— 这些真实业务动作全部脱离模型本身,由后端工具执行。模型在这里的角色,从“答题者”变成了“调度员”。

所以 Agent 落地最大的痛点来了:模型会幻觉、工具会报错、链路会出问题。 这三个坑,靠 Prompt 约束一个都解决不了,必须靠产品规则 + 工程兜底

总结:Prompt 是“告诉模型该怎么做”,兜底是“保证它做错了也出不了事”。两者不是二选一,是必须同时有。

二、看懂 Agent 会话上下文:messages 就是 Agent 的全部记忆

2.1 messages 是什么:四个角色

messages 是大模型对话的标准消息数组。它不是一个日志,它就是 Agent 的记忆本身—— 每一轮交互都往里追加,模型靠重读这整个数组来想起上下文、判断下一步该干什么。

OpenAI 协议定义了四种角色,这是所有 Agent 的底层运行规范:

总结:system 是底座规则,user 是用户诉求,assistant 是模型思考,tool 是执行回执。四个串起来才是一次完整的“办事记录”,缺任何一个,Agent 都会断片。

2.2 会话初始化:System Prompt 千万别写死

Agent 启动的第一步是初始化上下文,核心是塞入 System Prompt —— 不是开一个空白对话,而是先把业务环境搭起来

总结:用户说“今天”,如果你不在 Prompt 里告诉模型今天到底是几号,模型就只能靠猜 —— 它不知道“今天”是 2026 年还是 2025 年。 这是最低成本、最高收益的一条生产设计:写死日期的 Agent,上线第二天就开始记错账。

同理可注入的还有:用户 ID、所属组织、当前页面、可用功能权限、业务开关。

2.3 最大的认知误区:System Prompt 只能“告知”,不能“兜底”

❌ 新手想法:规则我写在 Prompt 里了,模型就会照做,不用再校验。

✅ 正确认知:System Prompt 只能做「告知与引导」,做不了「校验与兜底」。

能做的:定义身份、统一风格、告知可用工具、注入环境动态变量、声明基础业务约束。

做不到的:强约束模型行为。复杂场景(长对话、多工具、模糊指令)下,模型会无视规则、擅自篡改逻辑、违规调用工具、漏执行约束。

总结:凡是写进“业务红线”的校验,一律下沉到代码层。Prompt 负责让模型“尽量做对”,代码负责让它“做不对也出不了事”。

这一条,是 Demo 级 Agent 和生产级 Agent 的第一道分水岭。

2.4 做“清空对话”功能时,90% 的新人会踩这个坑

后果:Agent 瞬间失忆 —— 不知道自己是谁、不知道有什么规则、不知道今天几号。用户下一句话就可能触发一串错乱行为。

总结:清空会话的产品逻辑是「清用户记录、保系统规则」。 用户感知到的是“重新开始一段对话”,系统侧保证的是“规则底线一条不少”。

2.5 长对话的致命问题:上下文无限膨胀与 Token 超限

每一次请求大模型,都会把整个 messages 数组带上。对话轮次越多,数组越长 → Token 消耗越高、响应越慢、成本越贵,最后直接触发 Token 超限,会话中断。

行业通用解法是滑动窗口裁剪:永久保留 system,只留最近 N 轮对话。

但这里有一个很多人不知道的协议级大坑

OpenAI 协议规定:如果一条 assistant 消息带了 tool_calls,它后面必须紧跟对应数量的 tool 回执消息,否则接口直接返回 400。 而“保留最近 10 条”这种按截断的写法,很容易把 assistant 留下、把 tool 回执切掉——Agent 当场报错。

总结:裁剪不是“删旧消息”,是“删旧的完整轮次”。 一轮对话是一个整体,尤其是“模型说要调工具”和“工具说办完了”这两条,必须同生共死。

2.6 本章小结

  1. 底层逻辑:messages 四角色串联起“用户交互 → 模型决策 → 工具执行 → 结果回传”,是 Agent 连续任务的唯一载体。
  2. 能力边界:System Prompt 只做告知与引导,核心校验必须下沉到代码,绝不信任模型自律。
  3. 生产规则:清空会话只清用户历史、永久保留 System Prompt;长对话必须做配对式滑动裁剪。

三、工具调用核心原理:一条“帮我记一笔”的完整闭环

本章用一句真实指令 —— “今天打车花了45元帮我记上”—— 拆解从用户输入到最终应答的全链路。

3.1 工具调用的四个必填项

其中 tool_choice 是最容易被 PM 忽略、但最影响体验的一个开关:

产品视角:tool_choice 本质是“这一轮你给模型多大的自主权”。流程越固定、容错越低的地方(比如付款确认),越应该收紧到强制指定;越开放的对话,越适合 auto。

3.2 为什么生产环境一定要包一层 _create()

原生 SDK 只负责发请求、拿结果,没有任何异常处理。用户撞上限流,看到的就是一屏红色堆栈。

生产项目会自研一个统一入口,不改变请求逻辑,只在外面套三层能力:异常分类捕获 → 指数退避重试 → 用户友好提示。

精准重试:仅针对瞬态可自愈错误

可重试场景(瞬态错误,大概率自愈)

仅对临时波动类错误开启重试机制,这类问题多为服务瞬时状态异常,重试可自动恢复:

  1. 429 接口限流、请求频次超限
  2. 网络抖动、连接不稳定、瞬时断连
  3. 接口请求超时、服务瞬时响应延迟

禁止重试场景(永久错误,重试无效)

此类错误属于配置、权限、资源不存在等确定性问题,无论多少次重试都无法修复,触发后直接终止请求、返回异常即可:

  1. 401 密钥失效、鉴权失败、Token 过期
  2. 404 模型/接口资源不存在、路由错误
  3. 参数非法、请求格式错误等客户端固有问题

重试策略:指数退避 + 随机抖动,规避惊群效应

摒弃固定间隔重试模式,采用指数退避+随机抖动的工业级重试方案,兼顾容错性与服务稳定性。

  1. 指数退避:重试间隔随次数逐步拉长,示例间隔:8s→16s→24s,避免高频重试持续压迫服务,给后端充足的恢复时间;
  2. 随机抖动:在指数退避的间隔基础上,叠加随机时间偏移。有效解决多客户端同时触发重试的惊群问题,避免大量请求集中瞬间涌入,导致服务二次限流、雪崩崩溃,保障服务平稳恢复。

错误话术翻译:技术异常平民化,屏蔽底层细节

异常语义转换,屏蔽晦涩的底层技术报错,面向用户输出易懂、友好的提示文案。

用户无需感知 RateLimitError、网络超时、鉴权失败等技术堆栈信息,只需获取可理解的操作提示:

  1. 限流/超时类错误:统一提示「当前访问较繁忙,请稍后再试」
  2. 密钥/权限类错误:统一提示「权限已失效,请重新核验配置」
  3. 资源不存在错误:统一提示「当前访问的资源不存在,请检查配置」

核心原则:技术层精准报错、用户层友好提示,既方便研发排查问题,又不影响用户体验。

原生方法 vs 封装方法:原生 create() 是“发快递的”,封装后 _create() 是“发快递 + 丢件了自动补发 + 告诉你什么时候到”。参数一字不改,只在出事时多兜一层。

3.3 完整六步闭环

前置规则:开启主循环 run_agent_turn,最大工具交互轮次 = 5。这是防死循环的硬保险。

步骤 3:模型返回结构化工具调用指令 模型识别出这是记账需求,不做文本回答,而是返回一条 tool_calls。关键字段:

模型返回的参数示例:

{“date”: “2026-09-21”, “item”: “打车”, “amount”: 45, “remark”: “”}

这一步的立场是:绝对不信任模型输出。先解析、再校验、才执行,非法参数一律拦截。

为什么必须二次请求?

因为第一次请求时,模型只知道”我要调工具”,还不知道工具执行的结果。只有把回执塞回 messages 再问一次,模型才能说出人话:”已记好,2026-09-21 打车 45 元。” 这一步是新手最常漏的:只调工具不回传,用户看到的就是一片沉默。

步骤 6:轮次上限兜底 超过 5 轮仍在调工具,强制截断,输出纯文本应答。杜绝死循环和 Token 打爆。

3.4 头号踩坑:arguments 是字符串不是字典

总结:模型给你的不是“一个包裹”,是“一张写着包裹信息的纸条”。你不能直接从纸条上拿东西,得先把纸条翻译成包裹。

四、工具 Schema 设计:别再让研发随手写了

很多 Agent 的幻觉、错调工具、参数缺失,根因不是模型笨,是工具说明书没写清楚

Schema 不是后端接口文档,它是写给模型看的 PRD。 它的读者是大模型,不是人。

4.1 写进 PRD 的记账工具 Schema 模板

写 Schema 的三条实操经验

  1. description 要写“给模型看的人话”:amount 别只写“金额”,要写“正数,单位元,不带货币符号” —— 你多写这半句,就能少一半的“45元”字符串报错。
  2. required 只是提示,不是保证:模型照样会漏传,后端必须二次校验。
  3. 工具名要动宾结构、语义唯一:book_expense_record 比 add 好,模型不容易编造。

4.2 约束优先级铁律

总结:把 Schema 和 Prompt 当成“提高命中率的手段”,把代码校验当成“最后一道闸门”。闸门不能省。

4.3 生产最优实践:动态工具路由

Demo 可以把全部工具一次性灌给模型,生产不行。工具越多 → 模型选择越难、幻觉率越高、Token 越贵。

价值:少即是准。工具从 30 个降到 3 个,幻觉率通常能掉一个大台阶,Token 也省一大截。

4.4 工具多了怎么办:三种进阶方案

PM 要做的权衡:全量加载架构简单但贵且不准;动态路由省钱减错,但多了一个“路由判断”的故障点 —— 一定要给路由加监控指标(路由命中率、漏召回率),否则你会遇到“用户说记账但一个工具都没加载” 的诡异投诉。

五、生产级错误分类:两套体系,用途完全不同

Agent 的线上报错治理,PM 必须掌握两套分类法。很多人只知其一,所以写 PRD 时说不清、复盘时归不了因。

维度一:按「能不能原样重试」分 → 给研发写代码、给 PM 写异常兜底策略用

维度二:按「错误发生在链路哪一环」分 → 做评测、复盘 Badcase、迭代优化用

5.1 维度一:按「是否可原样重试」分类(写 PRD 用这套)

判定标准只有一句话:同样的参数再发一次,能不能好?

5.1.1 瞬态可重试错误(Transient)

判定:参数没错、逻辑没错,错在网络和服务商的临时抖动,原样重试大概率自愈。

典型场景:网络抖动 / 连接失败、429 限流、请求超时、服务商 5xx。

处理策略:循环重试 + 指数退避 + 最大次数上限,超次数给友好提示。(对应 3.2 的 _create())

产品价值:这类错误用户完全无感知,是体验最优的兜底。写 PRD 时把“重试次数、退避间隔、兜底文案”三项写死即可。

5.1.2 参数 / 模型生成错误(Validation Error)

判定:绝对不能原样重试。错在模型输出本身(参数非法、格式错、工具名编造),再发一次它还会犯同样的错,必须让模型自己改。

典型场景:JSON 解析失败、必填字段缺失、类型不匹配、金额为负数、日期格式错、调用不存在的工具名。

处理策略(核心差异化设计):把结构化错误信息包装成 role=tool 的回执,塞回上下文让模型自己修正,同时用最大轮次防死循环。

这是新手和老手最大的分水岭:新手遇到参数报错就弹窗“失败”;老手把错误还给模型,让它自己改完继续把事办了。用户感知从“失败了”变成“它自己修好了”。

5.1.3 永久不可恢复错误(Fatal)

判定:权限、配置、资源、硬规则的不可逆故障,重试和修正都没用,直接终止。

典型场景:401 密钥失效、403 权限不足、404 资源不存在、触发安全护栏(高危操作/预算超限)、上下文 Token 溢出。

处理策略:终止循环、不重试、屏蔽技术堆栈、给可读提示,严重问题留痕并转人工。

5.1.4 三类错误策略对照表

5.2 维度二:按「错误发生在哪一环」分类(复盘用这套)

这套不用于写代码,用于回答一个问题:这次线上事故,到底该怪谁?该改哪?

特别提醒第 ④ 类:被规则拦住不是故障,是护栏生效了。复盘时要把它们单独剔除,否则你会把“系统正常拦截”误判成“系统不稳定”,把指标做歪。

5.3 两套分类怎么配合用

  1. 写 PRD、对接研发、定兜底逻辑 → 用重试维度:定义重试次数、退避策略、终止逻辑、提示文案。
  2. 做评测、版本复盘、Badcase 归因 → 用链路维度:判断是模型不行、接口不稳,还是规则不全,精准找到迭代靶点。

总结:前者管“这次怎么扛过去”,后者管“下次怎么不再发生”。

六、高频 Badcase 复盘 + 上线必改清单

Agent 线上 90% 的模型类故障,集中在五个场景。

6.1 Badcase 1:arguments 返回非法 JSON

  • 现象:{“date”:”2026-09-21″,”item”:”打车”,”amount”:45 —— 末尾括号没了。
  • 根因:模型输出截断/格式幻觉,json.loads() 直接抛 JSONDecodeError,链路中断。
  • 修法:try-except 捕获 → 结构化错误回传模型 → 自动重试,并用最大轮次兜底。
  • PRD 要写:JSON 解析失败属于“参数生成错误”,禁止原样重试,最多自纠 2 轮。

6.2 Badcase 2:模型调用不存在的工具名

  • 现象:只定义了 book_expense_record,模型却调 add_expense。
  • 根因:工具映射表里没有这个函数,链路直接断。
  • 修法:后端前置校验工具名,返回“工具不存在 + 可用工具列表”;同时用动态路由减少工具数量,从源头降幻觉。
  • PRD 要写:工具名校验失败时,回传的提示里必须列出所有可用工具名。

6.3 Badcase 3:缺必填参数

  • 现象:required 写了 date/item/amount,模型只给了 item。
  • 根因:Schema 的 required 只是提示,没有强制力。
  • 修法:业务层做完整性校验,捕获缺失字段,把“缺了什么 + 格式要求”回传模型。
  • PRD 要写:所有 required 字段必须在后端再校验一次,缺失即回传自纠,不得直接报错给用户。

6.4 Badcase 4:参数类型不匹配

  • 现象:amount 应该是数字,模型给 “45元”。
  • 根因:模型把语义单位拼进了数值。
  • 修法:强制类型校验 + 格式清洗(去单位、去千分位)+ 数值转换,非法即拦截。
  • PRD 要写:金额类字段统一约定“数字、单位元、不带符号”,并在 Schema description 里写死这句话。

6.5 Badcase 5:一次返回多组 tool_calls

  • 现象:用户一句简单需求,模型一次生成 2 个工具调用。
  • 根因:代码只取 tool_calls[0],第二个被静默丢弃 → 部分动作没执行,用户以为都办了。
  • 修法:必须遍历数组逐个执行;同时考虑幂等性(同一工具被重复调用时不能写两遍账)。
  • PRD 要写:多工具调用时是否需要并行、是否要求顺序、重复调用的幂等键是什么。

6.6 八大上线必改清单(可作评审验收标准)

七、指标体系:怎么证明你的 Agent 真的变好了

这一章回答那个最容易被问住的问题:“你说优化过了,数据呢?”

先明确使用边界,避免落地误用:

本章可以直接复用:完整指标框架、指标定义口径、埋点字段规范

不可直接照搬:各类指标的目标 / 健康阈值。

例如 “工具执行成功率 ≥98%” 仅为行业通用参考值。不同业务的原始基线差异极大:有的业务基线只有 92%,有的业务天然就能达到 99.5%。直接套用外部的健康阈值,只会得到两类结果:要么目标遥不可及无法达成,要么指标达标,但对业务没有实际价值,属于无效 KPI。

Agent 的指标要分四层看,缺任何一层都会得出错误结论 —— 比如只看成功率,你会漏掉”成功了但记错账”这种最伤用户的错误。

因此下文所有表格统一拆为两列:

  • 定义(可直接复用):指标含义、统计口径、埋点逻辑,拿来就能落地;
  • 健康线参考(必须基于自身业务基线校准):仅作参考基线,上线前需要根据业务现状重新设定目标。

7.1 四层指标

7.2 每次调用要埋哪些字段

没有埋点,上面所有指标都算不出来。一次工具调用至少要记这些

session_id, turn_id, round_index # 定位:哪次会话、第几轮、第几次工具循环

tool_name, tool_call_id # 调了什么工具

args_raw # 模型原始输出(复盘必需,别只存解析后的)

parse_ok, validate_ok, exec_ok # 三段式:解析→校验→执行,各自成功与否

error_type # 四分类:结构/语义/执行/规则拦截

retry_count, latency_ms # 重试次数与耗时

prompt_tokens, completion_tokens # 成本

final_status # 成功 / 自纠成功 / 终止 / 转人工

PM 的关键动作:在 PRD 里就把埋点字段列成表。上线后再补埋点,等于前面的数据全部作废。

7.3 一周复盘模板

  1. 抽样:随机取 100 条会话(不要只取报错的,会失真)。
  2. 归因:按第 5.2 的四类打标,统计各类占比。
  3. 定靶:只挑占比最高的那一类,本周就改这一件事。
  4. 验证:改完对比前后 7 天的对应指标(比如语义错误占比 18% → 11%)。
  5. 沉淀:把典型 case 写进评测集,下次发版前跑一遍回归。

有个坑提醒:每次改 Prompt 或 Schema 都要跑回归集。Agent 的优化经常是按下葫芦浮起瓢—— 修好了日期理解,金额格式又坏了。没有回归集,你根本不知道自己是在优化还是在破坏。

八、上线前 12 条自查清单

这 12 条按「上下文 / 工具 / 链路 / 工程与运营」四组排列,覆盖从第一轮对话到线上故障的全链路。可以直接当 PRD 的验收标准用,评审时逐条核对一遍。

上下文

1. System Prompt 是否动态注入了当前时间、用户信息?

2. 清空会话是否保留了 System Prompt,并同步清理了业务态?

3. 长对话是否做了滑动窗口裁剪?裁剪是否保证 tool_calls 与回执配对?

工具

4. Schema 的 description 是否写清了格式、单位、取值范围?

5. 工具是否做了动态路由(>10 个工具时)?

6. 是否所有 required 字段都有后端二次校验?

链路

7. 是否设置了最大工具轮次(3~5)?

8. 是否遍历了 tool_calls 数组(而非只取第一个)?

9. 参数报错是否走”回传模型自纠”而不是直接失败?

10. 高危操作是否有二次确认 / 幂等设计?

工程与运营

11. 是否区分了可重试与不可重试错误?是否有指数退避 + 抖动?

12. 埋点字段是否齐全?是否已有 ≥50 条的评测集和回归流程?

写在最后:给产品经理的三句话

  1. Agent 产品能力的核心,不是把 Prompt 写得多漂亮,而是把”模型会犯错”当成既定事实来设计系统。
  2. 凡是业务红线,一律代码兜底。 Prompt 和 Schema 只负责提高命中率,不负责守门。
  3. 没有埋点和评测集的 Agent 优化,都是玄学。 先能看见,才谈得上变好。

附录 A:名词速查

附录 B:参考资料

OpenAI Function Calling 官方指南:https://platform.openai.com/docs/guides/function-calling

OpenAI Chat API 消息结构规范:https://platform.openai.com/docs/api-reference/chat/create

OpenAI 错误码与异常规范:https://platform.openai.com/docs/guides/error-codes

本文由 @summer 原创发布于人人都是产品经理。未经作者许可,禁止转载

题图来自作者提供

更多精彩内容,请关注人人都是产品经理微信公众号或下载App
海报
评论
评论请登录
  1. 目前还没评论,等你发挥!