工具遗忘是大模型应用落地过程中一个非常典型却又容易被忽视的问题。表现出来的形式多种多样:有的模型在对话进行到十几轮之后,突然忘记某个工具的确切参数名,开始凭空编造;有的模型重复调用同一个查询接口,明明上一轮已经拿到了结果;还有的模型在执行多步骤任务时跳过了关键步骤,直接给出最终答案。这些现象的背后,其实是模型对工具定义和执行历史的记忆出现了退化。本文将从问题成因、操作手册设计和步骤记忆机制三个角度,系统讲解如何缓解工具遗忘。

工具遗忘的典型表现与根本原因
先看一个实际场景。假设我们给模型配备了三个工具:查询订单、查询库存、创建工单。任务开始时模型调用一切正常,但当对话超过二十轮,用户突然要求创建工单时,模型可能传入一个根本不存在的参数order_status,或者把创建工单的接口名写成查询工单的名字。这就是典型的工具定义遗忘。
另一种情况是步骤遗忘。模型在执行一个五步流程时,第三步的查询结果已经返回,但到第五步模型又把第三步重新执行了一遍,因为中间穿插了大量用户的追问和闲聊内容,模型对已执行步骤的感知被稀释了。这种重复调用不仅浪费 token,还可能引发副作用,比如重复下单、重复发送通知。
从技术层面分析,原因主要有三点。第一,上下文窗口中工具定义通常被放在 system 消息里,随着对话增长,工具定义与当前生成位置的距离越来越远,注意力权重自然下降。第二,历史工具调用结果往往是长文本,比如一个包含几十个字段的 JSON,这些冗长内容挤占了有效上下文空间,让模型更难聚焦关键信息。第三,模型对自身执行历史缺乏显式的结构化记录,只能依赖对原始对话的隐式记忆,这种记忆在长上下文中非常脆弱。
用结构化操作手册固化工具知识
解决工具定义遗忘最直接有效的办法,是在提示词中构建一份结构化的操作手册。所谓操作手册,就是把所有工具的定义、参数说明、调用时机、注意事项,用高度格式化的方式重新组织一遍,并在关键节点主动注入到上下文中。
一份好的操作手册应该包含几个部分:工具清单总表、每个工具的调用条件、参数速查表、常见错误示例。其中参数速查表尤其重要,因为它把分散在各处工具定义中的参数名集中到一张紧凑的表格里,模型在生成调用时可以直接对照,大幅降低编造参数的概率。
## 工具操作手册(每次调用前必须核对) ### 工具清单 1. query_order(order_id: string) - 查询订单详情 2. query_stock(sku: string) - 查询商品库存 3. create_ticket(order_id: string, reason: string) - 创建售后工单 ### 调用规则 - 创建工单前必须先调用 query_order 确认订单存在 - 禁止编造参数名,只能使用上方列出的参数 - 已成功调用的工具不要重复调用,除非用户明确要求刷新数据 ### 参数速查 | 工具 | 参数 | 类型 | 必填 | |------|------|------|------| | query_order | order_id | string | 是 | | query_stock | sku | string | 是 | | create_ticket | order_id, reason | string | 是 |
光有手册还不够,注入时机同样关键。推荐的做法是每隔 N 轮对话,或者在检测到用户意图发生变化时,把操作手册的核心部分追加到最新一条消息附近,而不是让它一直停留在 system 消息里。这样做相当于把远处的知识搬到了注意力焦点附近,成本很低但效果显著。实测中,这种就近注入的方式能将参数编造类的错误率降低一半以上。
另外要注意手册的长度控制。手册本身也是上下文的一部分,如果写得过于冗长,反而会加重注意力负担。原则是只保留模型容易出错的信息:参数名、调用顺序约束、禁止事项。那些模型本来就能从工具 schema 中理解的内容,不需要重复。
构建步骤记忆机制追踪执行进度
解决了工具定义遗忘,接下来要处理步骤遗忘。核心思路是给模型维护一份显式的执行清单,每完成一步就更新一次状态,并在每次生成响应前把当前清单展示给模型。这份清单就是步骤记忆。
工程实现上,可以在系统层面维护一个任务状态对象,每当工具调用成功返回,就由代码自动更新状态,而不是依赖模型自己去回忆。然后在构造每轮请求时,把这个状态序列化成简洁的文本注入上下文。
# 维护任务步骤状态
task_state = {
"goal": "处理用户订单售后",
"steps": [
{"name": "查询订单", "tool": "query_order", "status": "done",
"result_brief": "订单存在,状态为已发货"},
{"name": "确认库存", "tool": "query_stock", "status": "done",
"result_brief": "SKU-123 库存充足"},
{"name": "创建工单", "tool": "create_ticket", "status": "pending"}
]
}
def build_step_memory(state):
lines = ["## 当前进度(禁止重复已完成的步骤)"]
for i, s in enumerate(state["steps"], 1):
mark = "已完成" if s["status"] == "done" else "待执行"
brief = f",结果:{s['result_brief']}" if s.get("result_brief") else ""
lines.append(f"{i}. {s['name']} [{mark}]{brief}")
return "\n".join(lines)注意上面的代码中有一个细节:每个已完成步骤只保留了result_brief这样一句摘要,而不是原始的完整返回结果。这就是步骤记忆的第二个价值,它同时完成了上下文压缩。原始的工具返回值可能是几百行的 JSON,但模型后续真正需要的往往只是一句结论。用摘要替代原文,既保留了关键信息,又释放了大量上下文空间,一举两得。
注入策略上,建议把步骤记忆放在距离模型生成位置最近的地方,通常是用户最新消息之后、模型回复之前。同时配合明确的指令,比如告知模型在执行任何工具调用前必须先阅读当前进度清单。这种做法本质上是用外部显式记忆弥补模型内部隐式记忆的不足,是当前 Agent 框架中普遍采用的成熟方案。
组合策略与效果验证
把操作手册和步骤记忆结合起来,就形成了一套完整的抗遗忘方案。整体流程是:任务开始时注入完整操作手册,对话过程中周期性注入精简版手册,每次工具调用后更新步骤记忆并压缩历史结果,每轮请求前注入最新的进度清单。
验证效果时,建议构建一组多轮任务的测试集,重点统计三个指标:参数编造率、重复调用率、步骤跳过率。一个实用的经验是,先跑一遍不带任何记忆机制的基线,记录各项错误率,再逐步加入操作手册、步骤记忆,观察每个机制带来的增量收益。多数实践表明,操作手册对参数编造的抑制效果最明显,步骤记忆则主要解决重复调用和步骤遗漏,两者互补性很强。
最后提醒一点,工具遗忘无法被百分之百消除,因为它的根源在于模型的注意力机制本身。工程上能做的是持续降低错误率,同时在系统层面增加兜底逻辑,比如对工具调用的参数做 schema 校验、对重复调用做拦截确认。只有提示工程与代码校验双管齐下,多轮任务的稳定性才能达到生产可用的水平。