1. 从一次线上事故说起:为什么我把判断逻辑从大模型里搬了出来
去年年底我负责的一个智能客服 Agent 上线第二周就出了状况。用户问“帮我查一下上个月的订单”,Agent 回复了一段非常礼貌但完全无关的话,大意是“我理解您想查询订单,建议您登录官网查看”。问题是,这个 Agent 明明接了订单查询工具,它却选择不调用,而是自己“编”了一个回答。
排查日志发现,大模型在那一轮推理中判断“用户意图不够明确”,于是决定先追问而不是执行。但它的追问话术又跑偏了,变成了一个泛泛的建议。这个案例让我意识到一个根本问题:我把太多判断权交给了大模型。
这不是个例。在 Agent 开发中,很多人(包括当时的我)会习惯性地把“要不要调用工具”“调用哪个工具”“参数怎么填”“结果怎么处理”全部塞进 prompt 里,让大模型一次性输出决策。这种做法在 demo 阶段看起来很优雅——一个 prompt 搞定所有逻辑。但一旦进入真实业务场景,问题就会集中爆发。
这篇文章想聊的就是这件事:Agent 的判断逻辑到底该怎么分层。我会把自己踩过的三个坑完整还原出来,包括当时的错误做法、排查过程、根因分析,以及最终落地的分层方案。如果你正在做 Agent 开发,或者准备从零搭建一个 AI Agent,这些经验应该能帮你少走不少弯路。
核心关键词:Agent、大模型、Agent 架构、Agent 开发、工具调用、判断分层。
2. 第一个坑:把“意图识别”和“工具选择”混在一个 prompt 里
2.1 当时的做法:一个万能 prompt 包打天下
最开始我的 Agent 架构非常简单:用户输入 → 拼接一个系统 prompt → 大模型输出 JSON → 解析 JSON 执行工具。系统 prompt 大概长这样:
你是一个智能助手,可以调用以下工具: - query_order: 查询订单,参数 user_id, date_range - refund: 申请退款,参数 order_id, reason - human_transfer: 转人工 请根据用户输入判断意图,选择合适的工具,并提取参数。 输出格式:{"tool": "...", "params": {...}}这个方案在测试集上准确率大概 85%,看起来还行。但上线后真实用户的输入千奇百怪,准确率直接掉到 60% 左右。
2.2 问题表现:意图对了,工具选错
最典型的一类错误是:用户说“我上周买的东西怎么还没到”,大模型正确识别了意图是“查询物流”,但它选择了query_order而不是query_logistics。因为 prompt 里没有query_logistics这个工具,它只能从现有工具里挑一个最接近的。
另一类错误更隐蔽:用户说“算了,帮我退了吧”,大模型判断意图是“退款”,但参数提取时把order_id填成了上一轮对话里提到的另一个订单号。这是因为大模型在长上下文里对指代消解做得不够稳定。
2.3 根因分析:大模型在做它不擅长的事
这里涉及一个关键认知:大模型擅长语义理解和生成,但不擅长精确的枚举选择和状态跟踪。
意图识别是语义理解,大模型做得很好。但“从 N 个工具里选一个”本质上是一个分类问题,而且工具数量越多、工具描述越相似,大模型的准确率下降越明显。至于参数提取中的指代消解和状态跟踪,这需要维护对话状态,而大模型每次推理都是无状态的(除非你把完整历史塞进去,但那样又会引入噪声)。
我后来做了一个对比测试:把工具选择从大模型里拿出来,改成一个基于规则 + 轻量分类模型的独立模块,准确率从 60% 提升到 92%。而意图识别仍然交给大模型,准确率保持在 95% 以上。
2.4 调整方案:意图识别归大模型,工具选择归规则引擎
调整后的架构变成了两层:
第一层,大模型只负责输出意图标签,不负责选工具。比如用户说“我上周买的东西怎么还没到”,大模型输出{"intent": "logistics_query"}。
第二层,一个规则引擎根据意图标签映射到具体工具。映射关系是人工配置的,比如logistics_query → query_logistics,order_query → query_order。如果某个意图对应多个工具,再根据槽位填充情况做二次判断。
这样做的好处是:大模型的输出空间从“所有工具的组合”缩小到了“有限的意图标签”,准确率大幅提升。而且意图标签和工具的映射关系是显式的、可审计的,出问题时能快速定位是意图识别错了还是映射配错了。
提示:意图标签的数量建议控制在 20 个以内。超过 20 个后,大模型的意图分类准确率也会开始下降,这时候需要考虑做意图的层级分类。
3. 第二个坑:让大模型自己决定“要不要调用工具”
3.1 问题场景:该调用的时候不调用,不该调用的时候乱调用
解决了工具选择的问题后,我遇到了第二个坑:大模型在“是否调用工具”这个判断上非常不稳定。
有些问题明明需要查数据库才能回答,大模型却直接用自己的知识编了一个答案。比如用户问“我的会员等级是什么”,大模型回复“通常会员等级分为普通、白银、黄金……”,完全没有调用query_member_level工具。
反过来,有些问题明明可以直接回答,大模型却非要调用工具。用户说“你好”,它调用query_user_info;用户说“谢谢”,它调用log_interaction。这些无效调用不仅浪费 token,还增加了响应延迟。
3.2 排查过程:大模型的“自信”是问题根源
我一开始以为是 prompt 写得不够清楚,于是加了很多强调:“如果问题涉及用户个人信息,必须调用工具”“如果只是打招呼,不要调用工具”。但效果有限,大模型仍然会时不时地“自作主张”。
后来我意识到,这个问题的根源在于:大模型对“自己知不知道答案”的判断是不可靠的。它不知道自己不知道什么。当它遇到一个需要实时数据的问题时,它会倾向于用训练数据里的通用知识来回答,而不是承认“我需要查一下”。
这是一个认知层面的问题,不是 prompt 能完全解决的。
3.3 解决方案:用前置规则做“工具调用触发器”
我的做法是在大模型之前加一层轻量的规则判断,我把它叫做“工具调用触发器”。这一层不依赖大模型,而是基于关键词、正则、以及简单的分类模型来判断“这个问题是否必须调用工具”。
具体来说,触发器分三类:
第一类,强制调用。如果用户输入命中了某些关键词(如“我的订单”“我的余额”“查询”“帮我查”),直接标记为必须调用工具,跳过“是否调用”的判断。
第二类,强制不调用。如果用户输入是纯问候、纯感谢、纯闲聊(通过一个轻量分类模型判断),直接走通用回复,不进入工具调用流程。
第三类,交给大模型判断。对于不确定的情况,仍然让大模型决定,但会在 prompt 里明确告诉它“如果你不确定,请选择调用工具”。
这个方案落地后,无效调用减少了约 70%,而该调用没调用的情况减少了约 85%。
3.4 一个容易忽略的细节:工具调用的“成本”也要考虑
在调整过程中我还发现一个细节:不同工具的调用成本差异很大。有些工具是查本地缓存,毫秒级返回;有些工具要调外部 API,可能几百毫秒甚至超时。如果让大模型无差别地决定调用,它不会考虑这些成本差异。
所以我在触发器里加了一个优先级配置:对于高成本工具,触发条件更严格;对于低成本工具,可以适当放宽。这个配置是写在规则里的,不交给大模型判断。
4. 第三个坑:把“结果处理”也交给大模型
4.1 问题表现:工具返回了正确结果,大模型却处理错了
这个坑是最隐蔽的。工具调用成功了,返回了正确的数据,但大模型在生成最终回复时把数据用错了。
举个例子:用户问“我最近一笔订单的金额是多少”,工具返回{"order_id": "12345", "amount": 299.00, "currency": "CNY"}。大模型生成的回复是“您最近一笔订单的金额是 299 元”。看起来没问题。
但如果工具返回的是{"order_id": "12345", "amount": 29900, "currency": "CNY", "unit": "cent"},大模型可能会忽略unit字段,直接说“29900 元”。这就是典型的“结果处理错误”。
另一类问题是:工具返回了多个结果,大模型在汇总时漏掉了部分信息,或者把不同订单的信息混在一起。
4.2 根因:大模型对结构化数据的处理不可靠
大模型对自然语言的理解很强,但对结构化数据的处理并不稳定。它可能会忽略某些字段,可能会把数字格式搞错,可能会在多个结果之间混淆。
而且这个问题很难通过 prompt 解决,因为你不可能在 prompt 里穷举所有字段的处理规则。
4.3 方案:结果处理用模板 + 大模型只做“润色”
我的做法是把结果处理分成两步:
第一步,用代码做结构化处理。工具返回的 JSON 先经过一个格式化模块,把关键字段提取出来,按照预定义的模板填充。比如订单查询的模板是“您最近一笔订单(订单号:{order_id})的金额是 {amount} {currency}”。
第二步,大模型只负责润色。把填充好的文本交给大模型,让它根据对话上下文调整语气和表达方式,但不允许它修改任何数字和关键信息。
为了确保大模型不“自由发挥”,我在 prompt 里加了明确的约束:“你只能调整语气和连接词,不能修改任何数字、日期、订单号等关键信息。如果模板文本已经完整,直接输出即可。”
这个方案落地后,结果处理的错误率从约 15% 降到了 2% 以下。
4.4 补充:什么时候可以让大模型多做一点
当然,并不是所有场景都适合用模板。如果工具返回的是非结构化文本(比如一篇文章、一段日志),那大模型的处理能力就很有价值。这时候可以让大模型做摘要、提取关键信息、生成自然语言回复。
判断标准很简单:如果结果是结构化的、字段明确的,用代码处理;如果结果是非结构化的、需要语义理解的,交给大模型。
5. 分层之后的 Agent 架构:各司其职,各尽其能
5.1 整体架构图(文字描述)
经过三次调整,我的 Agent 架构最终变成了这样:
用户输入 → 前置触发器(规则 + 轻量分类)→ 意图识别(大模型)→ 工具选择(规则引擎)→ 工具执行(代码)→ 结果格式化(代码)→ 回复润色(大模型)→ 输出
每一层只做自己擅长的事:
- 前置触发器:判断是否需要调用工具,用规则和轻量模型,快且稳定。
- 意图识别:理解用户语义,用大模型,准确率高。
- 工具选择:意图到工具的映射,用规则引擎,可审计。
- 工具执行:实际调用,用代码,可靠。
- 结果格式化:结构化数据处理,用代码,精确。
- 回复润色:自然语言生成,用大模型,灵活。
5.2 各层的职责边界与协作方式
这里的关键是职责边界要清晰。大模型不负责枚举选择,不负责状态跟踪,不负责结构化数据处理。它只负责语义理解和自然语言生成。
规则引擎不负责语义理解,它只做确定性的映射和判断。代码不负责生成自然语言,它只做结构化的数据处理。
各层之间通过明确定义的接口通信。比如意图识别的输出必须是预定义的意图标签之一,工具选择的输入必须是合法的意图标签,输出必须是预定义的工具名之一。任何一层输出不符合预期,都会被下一层拒绝并触发降级逻辑。
5.3 降级逻辑:当大模型不可用时怎么办
分层架构还有一个好处:降级更容易。如果大模型服务不可用,我可以降级到纯规则方案。虽然准确率会下降,但核心功能仍然可用。
具体来说,如果意图识别的大模型调用失败,我会用一个基于关键词的兜底分类器。如果回复润色的大模型调用失败,我会直接输出格式化后的模板文本。这些降级逻辑都是预先配置好的,不需要临时写代码。
6. 实操建议:怎么判断哪些逻辑该交给大模型
6.1 一个简单的判断框架
经过这几个坑,我总结了一个简单的判断框架。对于任何一个判断逻辑,问自己三个问题:
第一,这个判断是语义理解还是枚举选择?语义理解交给大模型,枚举选择交给规则。
第二,这个判断需要状态跟踪吗?如果需要维护对话状态、用户状态、订单状态,交给代码。
第三,这个判断的错误成本高吗?如果错误成本高(比如涉及金额、权限),用规则 + 代码,不用大模型。
6.2 常见场景的分配建议
| 场景 | 建议方案 | 理由 |
|---|---|---|
| 意图识别 | 大模型 | 语义理解,大模型擅长 |
| 工具选择 | 规则引擎 | 枚举选择,规则更稳定 |
| 参数提取 | 大模型 + 校验 | 大模型提取,代码校验 |
| 是否调用工具 | 前置规则 + 大模型兜底 | 规则快且稳,大模型处理边界情况 |
| 结果格式化 | 代码 | 结构化数据处理,代码精确 |
| 回复生成 | 大模型 | 自然语言生成,大模型擅长 |
| 多轮对话状态 | 代码 | 状态跟踪,代码可靠 |
6.3 一个反直觉的经验:大模型不是越多越好
很多人做 Agent 时会倾向于“能交给大模型就交给大模型”,觉得这样更智能。但我的经验恰恰相反:大模型用的地方越少,Agent 越稳定。
当然,这不是说大模型不好。大模型在语义理解和自然语言生成上的能力是无可替代的。但在其他环节,规则和代码往往更可靠、更可预测、更便宜。
我现在做 Agent 的原则是:能用规则解决的,不用大模型;能用代码解决的,不用大模型;只有语义理解和自然语言生成,才交给大模型。
7. 踩坑之后的一些零散心得
7.1 日志要记全,不然排查全靠猜
这三个坑能排查出来,全靠日志记得全。我现在的日志会记录:用户输入、前置触发器的判断结果、大模型的原始输出、规则引擎的映射结果、工具调用的入参和返回值、格式化后的文本、最终回复。每一层都有日志,出问题时能快速定位是哪一层出了问题。
7.2 测试集要覆盖边界情况
我最初的测试集都是“正常”的用户输入,比如“查一下我的订单”。但真实用户的输入千奇百怪,有错别字、有省略、有指代、有情绪。后来我专门建了一个边界测试集,覆盖了这些情况,才发现了很多之前没注意到的问题。
7.3 不要追求一次到位,迭代才是常态
我的 Agent 架构不是一开始就设计成这样的,而是踩一个坑改一点,慢慢演化出来的。所以如果你刚开始做 Agent,不用追求一步到位。先跑起来,遇到问题再调整,迭代几次之后架构自然会清晰。
7.4 大模型的上下文长度不是越长越好
我试过把完整对话历史都塞进 prompt,结果发现大模型反而更容易混淆。后来改成只保留最近几轮对话 + 关键状态摘要,效果更好。上下文长度和准确率不是线性关系,找到合适的长度比一味加长更重要。
7.5 工具描述要写清楚,但不要指望大模型能记住所有细节
工具描述要写清楚参数含义、返回值格式、使用场景。但不要指望大模型能记住所有细节,尤其是工具数量多的时候。关键信息还是要靠代码校验和规则兜底。
8. 最后再说两句
Agent 开发这件事,说到底是在“智能”和“可控”之间找平衡。大模型带来了智能,但也带来了不确定性。把判断逻辑分层,让大模型只做它擅长的事,其他交给规则和代码,是我目前找到的最好的平衡点。
这个方案不是完美的,它需要更多的配置和维护成本。但相比线上事故带来的损失,这些成本是值得的。而且随着 Agent 规模变大,分层架构的优势会越来越明显。
如果你也在做 Agent 开发,欢迎交流。踩坑不可怕,可怕的是踩了坑不知道为什么踩的。希望这篇文章能帮你少踩几个。