为什么你的 AI Agent 总是不听话?从 Prompt 到 Tool Design 的深度分析
AI Agent 能跑通 Demo,但一上真实场景就频繁出错——工具不调用、参数传错、执行到一半跑偏、甚至把之前的信息覆盖掉。这些问题不是单个环节能解决的,从 Prompt 结构、工具设计到上下文管理都有陷阱。本文从实际踩坑经历出发,拆解 Agent 行为失控的几种典型表现,并给出可落地的改进思路。
过去一年里试了不少 AI Agent 相关的项目,从最早的 Function Calling 原型,到后来接入 MCP 协议做多工具调度。最常听到的反馈是:Demo 跑得挺顺,一旦放到真实场景里,错误率就上来了,而且不是某一个环节的问题,是整个系统看起来“不太聪明”。
更具体的表现是:
- 明明提供了工具列表,模型偏不用,非要瞎编
- 用了工具,但参数传得一塌糊涂
- 执行到一半,突然把上下文搞混了,忘了前面说过什么
- 工具数量一多,决策质量明显下滑
这些问题有一个共同点:不是模型能力不够,而是模型和外部世界的接口没有设计好。
模型不调用工具:为什么能用工具却偏要“硬答”
这是最常见的抱怨。给了工具定义,也明确说了“需要的时候可以调用”,但模型就是选择直接生成文本,哪怕它编出来的内容明显不对。
一个典型的例子:给模型接了一个查询订单状态的工具,用户问“我的订单 12345 现在到哪了”,模型直接回复“您的订单正在配送中”,而不是调用工具去查真实数据。
根本原因:模型本质上是一个文本生成器,调用工具对它是“额外动作”。 如果 Prompt 里没有明确区分“可以直接答”和“必须用工具”两种情况,模型倾向于走省力的路——直接生成。
一个有效的改进:
你是一个订单查询助手。用户询问任何订单状态时,你**必须**先调用 get_order_status 工具,基于工具返回的数据组织回复。禁止凭训练数据猜测订单状态。
这个 Prompt 做的几件事:
- 约束行为边界:明确什么条件下必须调用工具
- 禁用备选路径:明确禁止猜测,断掉模型“硬答”的选项
- 明确数据来源:让模型知道唯一可信来源是工具返回
如果工具是必调的,可以用 required 参数。部分模型支持将特定工具标记为强制调用,这比任何 Prompt 都直接。
另一个容易被忽略的问题:模型不知道工具能解决什么问题。 工具描述写得模糊,比如 get_order_status 的描述只有一行“获取订单状态”,模型无法判断什么时候该用它。正确的做法是让工具描述包含触发条件和参数说明:
查询指定订单的当前物流状态。当用户询问订单物流、配送进度、包裹位置时,应调用此工具。
参数: order_id (string, 必填) - 订单编号
参数错误:大模型真的不懂你的业务
工具调用了,但参数传得不对。最典型的是时间格式:用户说“上个月的订单”,模型直接传 last_month,而工具要求的是 YYYY-MM-DD 格式的日期范围。
这其实是模型对业务规则的“无知”。模型看过海量文本,知道“上个月”是什么意思,但它不知道你的工具函数签名长什么样。
解决方案不是让模型更聪明,而是让工具定义更“笨”一点。
第一种方式是在描述里直接写明格式要求:
{
"name": "get_orders",
"description": "查询订单列表。当用户询问订单查询、历史订单时调用。",
"parameters": {
"start_date": {
"type": "string",
"description": "起始日期,格式必须为 YYYY-MM-DD,例如 2026-01-01"
},
"end_date": {
"type": "string",
"description": "结束日期,格式必须为 YYYY-MM-DD,例如 2026-07-31"
}
}
}第二种方式更稳妥:让工具自己处理模糊输入。 工具函数内部做一层解析,接收自然语言后再做转换,或者干脆让模型输出一个相对时间表达式,由工具侧去解析。后者相当于把“语义理解”从模型侧下沉到了工具侧,减少了模型出错的机会。
还有一个容易被忽视的点:模型对 JSON Schema 的理解参差不齐。 复杂的嵌套对象、数组结构、枚举值,模型的出错率会显著上升。参数结构越扁平,模型越不容易犯错。能用 string 就不用 object,能用平铺的字段就不用嵌套。
幻觉:工具返回了数据,模型却在添油加醋
工具调用成功了,返回的数据也是对的,但模型在组织回复时往里加了东西——而且加的内容可能是错的。
这是典型的“工具调用后的幻觉”:模型拿到了真实数据,但在表述时加入了自己的推测。比如工具返回的是订单状态 SHIPPED,模型回复时自作主张加了“预计明天上午送达”,而这个信息工具根本没返回过。
核心问题:模型分不清“工具返回的确定事实”和“基于训练数据的推测”。 它把自己对物流的理解混进了真实数据里。
在 Prompt 里可以明确约束:
基于工具返回的数据组织回复,不要添加任何工具未返回的信息。如果用户询问的内容工具数据中没有,明确告知“该信息暂未获取到”。
也可以结合 UI 设计来解决:直接展示原始数据,让用户自己看。 模型只负责解释字段含义,不做额外推测。
上下文污染:Agent 的记忆在悄悄变糟
这是多轮对话中 Agent 最容易出问题的地方。
一个场景:用户先问“帮我查一下订单 12345”,Agent 调用工具返回了结果。接着用户说“这个订单的收货地址改一下”,Agent 需要记住刚才查的是哪个订单、哪些是已确认的信息、哪些是新的指令。
如果上下文管理没做好,Agent 可能会混淆。比如用户改完地址后,Agent 忘了这个订单之前的状态,又去调用查询工具重新获取一遍,或者在改地址时用错了订单号。
还有一种污染更隐蔽:历史轮次中带有错误的推理过程,被模型带入了后续上下文。 比如第一轮 Agent 错误地判断某个订单是待发货状态,虽然通过工具纠正了,但“待发货”这个词的激活痕迹可能还在上下文里,影响后续判断。
基础的上下文管理策略是滑动窗口:保留最近 N 轮对话,超出部分截断。但损失可能刚好是关键的。
更精细的做法是对上下文做摘要:每轮或每几轮,用模型生成一个状态摘要,包含当前任务进度、已确认的事实、待处理事项。下轮对话时,Agent 的上下文由“当前用户输入 + 历史摘要 + 本轮工具调用记录”组成。成本更高,但能有效防止遗忘和混淆。
还有一些针对性措施:
- 在工具调用过程中记录状态变更,而不是依赖模型记忆
- 每轮开始前重置推理上下文,只保留必要的变量
- 明确区分“系统角色指令”、“历史对话”、“工具结果”三个上下文区域
工具数量过多:选择太多反而不会选了
给 Agent 接了十几个工具,心想功能越全越好,结果模型在工具选择上频频出错。
这是认知负荷问题。当工具数量超过某个阈值(实际经验大概 8-10 个),模型的选择准确率就会明显下降。具体表现为:
- 频繁调用错误的工具
- 在两个相似工具间反复横跳
- 选择了正确的工具但参数张冠李戴
- 干脆不调用工具
原因是:模型在做工具选择时,本质是在做一次“分类决策”。候选类别越多,决策越容易出错。
几种应对方式:
分组路由。给工具打上标签(例如“查询类”“更新类”“外部 API 类”),先用一个轻量的分类模型或规则做一级路由,确定走哪一组后再把该组的工具列表给模型。这样每次模型面对的候选工具不超过 5 个。
语义相似度前置过滤。根据用户输入的 embedding,在向量库里匹配最相关的 3-5 个工具,把不相关的过滤掉。
显式标记必选工具。对于某些场景,强制绑定特定工具,不交给模型决策。比如“订单查询”的意图一旦被识别,直接触发对应的工具调用,不经过模型选择。
分场景定制 Agent。不要让一个 Agent 做所有事。不同场景使用不同的 Agent 实例,每个 Agent 只配 3-5 个高频工具。代码生成一个 Agent,数据分析一个 Agent,运维操作一个 Agent。虽然增加了系统复杂度,但每个 Agent 的可靠性会明显提升。
这些问题的共同根源
回头来看,这些问题看起来分散,但有一个共同点:把模型当成了“什么都能处理”的黑盒,而没有把它当作一个需要精心设计接口的组件。
模型不调用工具,是因为工具在它眼里不是“功能”,而是“一堆额外的选项”。
参数传错了,是因为模型对业务规则的理解不如你对工具函数的理解准确。
幻觉和上下文污染,是因为模型没有做事实核查和状态管理的内置能力。
工具太多会出错,是因为模型的选择能力是有限的。
这些都是模型的特性,不是缺陷。 理解这些特性之后,要做的事情不是“等模型变聪明”,而是调整 Agent 的设计模式:
- 工具描述写成“给模型看的说明书”,而不是给自己看的 API 文档
- 上下文管理当成状态管理来做,而不是让模型自己记
- 工具选择用路由机制辅助,而不是完全依赖模型判断
- 模型能做什么、不能做什么,在系统设计阶段就想清楚,而不是出了问题再补 Prompt
Agent 的可靠性,本质上是工程设计问题,不是模型能力问题。