Lazy loaded image
Agent丨如何解决 Agent 工具调用时的幻觉问题:例如编造 API 或传错参数?
字数 6661阅读时长 17 分钟
2026-5-17
type
Post
status
Published
date
May 17, 2026
slug
agent42
summary
tags
Agent
category
icon
password

一、问题本质:工具调用幻觉不是“模型小错误”,而是“执行链路失控”

Agent 工具调用幻觉常见有几类:
编造工具名:上下文里没有 get_user_balance_v2,模型却调用了它。编造 API 参数:真实参数是 user_id,模型传了 uidaccountId 或多传 role参数类型错误limit 要整数,模型传字符串;dateYYYY-MM-DD,模型传“明天下午”。语义错误:schema 校验通过,但含义错了,比如把 delete_file(path) 用在只需要读取文件的任务上。越权调用:用户只让 Agent 查看代码,Agent 却执行 rm、提交代码、调用付费 API。结果幻觉:工具失败或返回空值,模型却假装工具成功,并编造结果。
从成熟 Agent 的设计来看,正确思路不是“写一个更强 Prompt 让模型不要犯错”,而是:
LLM 只负责提出工具调用意图;工具是否存在、参数是否合法、权限是否允许、是否真的执行,必须由 Agent Runtime 决定。
Anthropic 的工具调用文档也强调,Claude 会根据用户请求和工具描述返回结构化 tool call;对于 client tools,真正执行发生在应用侧,而不是模型内部。也就是说,工具调用本身应该被看成“模型提出的一份执行请求”,而不是可信命令。(Claude Platform)

二、标准工具调用链路与幻觉发生位置

工具调用幻觉通常不是单点问题,而是链路中多个环节缺失:
如果没有工具白名单,模型可以“编造工具”。
如果没有 JSON Schema / Zod / Pydantic 校验,模型可以“传错参数”。
如果没有权限系统,模型可以“越权调用”。
如果错误没有作为 Observation 返回,模型会“自行脑补工具结果”。
如果工具太多、描述太差,模型会“选错工具”。

三、为什么 Agent 会编造 API 或传错参数?

1. 工具描述本质上仍是自然语言提示

大多数 function calling / tool calling 系统会把工具名、描述、参数 schema 注入给模型。模型根据这些信息生成结构化调用。但模型并不天然知道你的后端真实 API,也不会自动保证业务语义正确。
MCP 规范中,工具由名称唯一标识,并带有描述其 schema 的元数据;这让模型能发现并调用外部系统,但也意味着工具描述质量、schema 精度和运行时校验都会直接影响调用可靠性。(Model Context Protocol)

2. 工具数量过多会造成路由混乱

当上下文中塞入几十、几百甚至上千个工具时,模型要同时完成“理解任务、选择工具、填写参数、规划下一步”,非常容易选错。MCP-Zero 这类研究方向就是为了解决“大量工具 schema 注入成本高且容易出错”的问题,它提出按需检索工具链,而不是一次性塞入所有工具;论文摘要称其在 APIbank 上减少了 98% token 消耗并保持较高准确率。(arXiv)

3. schema 只能保证“形状”,不能保证“业务正确”

{"path": "/etc/passwd"} 可能符合 schema,但不应该被允许读取。
{"amount": 1000000} 可能是合法数字,但不应该自动转账。
{"repo": "prod", "branch": "main"} 可能类型正确,但风险极高。
所以防幻觉必须分层:结构校验解决格式问题,权限系统解决执行边界,业务规则解决语义风险。

四、核心原则:Schema 是第一道门,Runtime 是最终裁判

OpenAI 的 function calling 文档明确建议开启 strict: true,这样函数调用会可靠遵循 schema;同时 strict mode 要求对象设置 additionalProperties: false,并要求 properties 中字段都出现在 required 中,optional 字段可用 null 类型表示。(OpenAI开发者)
Anthropic 也提供 strict tool use,用来约束工具输入参数;其结构化输出文档明确区分:JSON outputs 控制“模型说什么”,strict tool use 控制“模型如何调用函数”。(Claude Platform)
但严格 schema 不是终点。一个生产级 Agent 应该采用:
Prompt 约束 + Tool Schema + Runtime Validation + Permission Policy + Error Observation + Evaluation
而不是只依赖 Prompt。

五、成熟框架中的关键做法

1. Claude Code:权限不是交给模型判断,而是由宿主系统执行

Claude Code 文档明确说明,它支持细粒度权限规则,可以配置 agent 允许做什么、不允许做什么;其规则包括 allow、ask、deny,并且 deny 优先于 ask,ask 优先于 allow。更重要的是,文档明确指出:权限规则由 Claude Code 执行,而不是由模型执行;Prompt 或 CLAUDE.md 只能影响模型想做什么,不能改变 Claude Code 允许什么。(Claude Code)
这点非常关键:
不要让模型自己决定“我有没有权限”。
模型只能提出动作。
Runtime 必须拦截、校验、审批、执行。
Claude Code 还把权限按操作风险分层:只读文件读取和 Grep 不需要审批,Bash 命令需要审批,文件修改也需要审批;这说明成熟 coding agent 会把“读、写、执行 shell”分成不同风险等级。(Claude Code)

2. Claude Code 自定义工具:区分“工具可见性”和“工具执行权限”

Claude Code Agent SDK 文档中,自定义工具可以用 allowedToolsdisallowedToolstools 等方式控制。文档特别区分了两层:availability 决定工具是否出现在 Claude 上下文中,permission 决定工具调用后是否被批准执行。(Claude Code)
这给我们的启发是:
工具治理不要只做“执行时拒绝”。
对危险工具,最好一开始就不要暴露给模型。
对条件性危险工具,可以暴露但执行前审批。
对绝对禁止工具,既不暴露,也不执行。

3. OpenCode:用 schema 和 permission 管理工具

本文中的 OpenCode 指 anomalyco/opencode。OpenCode 文档显示,它内置 buildplan 两类 agent,其中 plan 是只读分析模式,默认拒绝文件编辑,并在执行 bash 前请求许可。(GitHub)
OpenCode 的工具文档也明确支持通过 permission 字段控制工具行为,例如 edit: denybash: askwebfetch: allow,并且支持通配符控制多个 MCP 工具。(GitHub)
从源码视角看,OpenCode 的工具注册逻辑会把 Zod schema 转成 JSON Schema;如果插件工具的 Zod schema 不能产生 object JSON Schema,会直接抛错。(GitHub) 它的 MCP 工具适配逻辑还会把 MCP inputSchema 转成 AI SDK tool schema,并显式设置 additionalProperties: false,这正是减少模型乱传额外参数的关键做法。(GitHub)

4. OpenHands:确认策略 + 安全分析器

OpenHands 的 SDK 安全文档把 Agent action 控制分成两个互补机制:confirmation policy 和 security analyzer。前者决定是否需要用户确认,后者评估动作风险;文档中也给出 AlwaysConfirm()NeverConfirm()ConfirmRisky() 等策略。(OpenHands Docs)
这说明成熟 Agent Runtime 通常不会把“是否危险”完全交给模型,而是增加独立的安全分析层。

5. OpenClaw / OpenClaw Code Agent:计划审批、工作区隔离、可观测会话

OpenClaw 本体定位是运行在用户设备上的个人 AI assistant,可以跨渠道响应用户并控制 live Canvas。(GitHub) 其生态中的 openclaw-code-agent 会把 Claude Code 和 Codex 作为后台编码会话来管理,并增加 plan approval、session lifecycle、worktree isolation、merge/PR follow-through、显式 goal loop 等能力。(GitHub)
这类设计对工具幻觉治理很有价值:
先产出计划,再审批。
在隔离 worktree 中执行。
合并或 PR 前再做决策。
会话输出、成本、状态都可观察。
失败时可以恢复、暂停、终止,而不是让模型一口气改生产环境。

六、生产级防幻觉架构:七层防护

第 1 层:工具注册表必须是唯一事实源

不要让工具散落在 Prompt、代码注释、README、MCP Server、后端 OpenAPI 文档里各自维护。生产系统应该有一个统一的 Tool Registry。
一个工具定义至少包含:
工具名:稳定、唯一、不可动态编造。
描述:说明什么时候用,什么时候不用。
输入 schema:JSON Schema / Zod / Pydantic。
输出 schema:返回结构、错误结构。
权限标签:read、write、network、shell、payment、delete。
幂等性标签:read-only、idempotent、mutation。
风险等级:low、medium、high、critical。
owner/version:方便审计和灰度。
示例:
关键点是 extra="forbid",这相当于 JSON Schema 里的 additionalProperties: false:模型多传字段时直接拒绝,而不是默默吞掉。

第 2 层:开启 strict tool calling / structured output

对于支持 strict schema 的模型和 SDK,应默认开启 strict。
OpenAI 文档建议 function calling 使用 strict: true,并说明 strict mode 会借助 Structured Outputs 来保证函数调用遵循 schema。(OpenAI开发者) OpenAI 的 Structured Outputs 文档也要求对象必须设置 additionalProperties: false,否则不能使用严格结构输出。(OpenAI开发者)
工具定义示例:
但要注意:
strict schema 只能降低“格式幻觉”。
它不能自动判断“这个 API 是否该被调用”。
所以还必须有权限系统、业务校验、执行审计。

第 3 层:运行时二次校验,错误作为 Observation 返回

不要让 schema validation error 直接中断整个 Agent。更好的方式是把错误转成工具观察结果,让模型修正参数或换工具。
Anthropic 的 Claude Code 自定义工具文档也说明,如果 handler 抛出未捕获异常,Agent loop 会停止;如果 handler 捕获错误并返回 isError: true,Claude 可以看到错误并重试、换工具或解释失败。(Claude Code) MCP 规范也把错误分为 protocol error 和 tool execution error,后者可通过 isError: true 返回,例如 API 失败、输入校验错误、业务逻辑错误。(Model Context Protocol)
推荐包装器:
这里有三个要点:
未知工具不能执行。
参数错误不能自动修正后执行高风险动作。
工具失败要返回给模型,但不能泄露内部堆栈、密钥、路径等敏感信息。

第 4 层:工具 RAG / Tool Router,减少上下文中的工具数量

如果一个 Agent 同时有 200 个工具,模型选错概率会明显上升。更好的做法是:
先用 Tool Router / RAG 从 Tool Registry 检索候选工具。
每轮只暴露最相关的 3~10 个工具。
对高风险工具默认不进入候选集,除非任务明确需要。
对 MCP 工具按 server、domain、risk 做分层检索。
OpenCode 社区 PR 中就出现了 MCP lazy loading 思路:配置 mcp_lazy 后,MCP 工具不会自动全部加载进上下文,而是通过 mcp_search 按需发现和调用。该 PR 的配置描述写明:启用后 MCP tools 不会自动加载到 context,而是使用 mcp_search on-demand 发现并调用。(GitHub)
推荐路由流程:
工具检索可以基于:
工具名 embedding。
工具描述 embedding。
参数字段名 embedding。
最近成功调用日志。
当前任务类型。
用户权限和租户权限。
工具风险等级。

第 5 层:权限系统要和工具 schema 分离

很多团队把“工具能不能用”写在 Prompt 里,例如:
“不要删除文件。”
“不要执行危险命令。”
“只读模式下不要修改代码。”
这不够。Claude Code 文档已经说得很清楚:权限由 Claude Code 执行,而不是由模型执行。(Claude Code)
生产系统至少需要四种权限策略:
deny:绝不允许,最好不暴露给模型。
ask:模型可提出调用,但必须用户确认。
allow:低风险工具可直接执行。
scoped allow:只允许特定参数范围,比如只允许读当前 repo 下的文件。
示例:
对于 shell 工具,不能只做简单字符串匹配。要考虑:
&&;|、反引号、$() 等命令组合。
find -execxargs rm 等间接执行。
脚本写入后再执行绕过限制。
相对路径、软链接、环境变量展开。
子 Agent 是否继承父 Agent 权限。
OpenCode 的一些 issue 也暴露了子 Agent、MCP 权限传递、schema 适配等边界问题,这恰好说明工具权限不是“有一个 permission 字段”就结束,而是需要覆盖 subagent、batch call、hook、MCP adapter、workspace 等完整执行路径。(GitHub)

第 6 层:高风险动作引入 Human-in-the-Loop

不是所有工具都应该自动执行。推荐按照风险分级:
风险级别
例子
策略
low
搜索文档、读文件、查天气
可自动执行
medium
写临时文件、运行测试、调用内部查询 API
可自动或轻审批
high
修改代码、执行 shell、发邮件、创建 PR
用户确认
critical
删除数据、转账、生产变更、权限修改
多重确认 / 禁止自动执行
OpenHands 的 ConfirmRisky() 就是这种思路:只有风险动作需要确认,并结合 security analyzer 评估动作风险。(OpenHands Docs)
Agent 执行前给用户看的确认信息不能是模型随便写的自然语言,而应该由 Runtime 从结构化 tool call 生成:
这样用户确认的是“真实待执行动作”,不是模型改写后的摘要。

第 7 层:语义校验与业务规则校验

schema 校验通过后,还要做 semantic validation。
例如:
常见业务校验包括:
文件路径必须在 workspace 内。
API endpoint 必须在 allowlist 内。
金额必须低于阈值。
用户 ID 必须属于当前租户。
日期必须是绝对日期,不能让模型传“明天”。
mutation 操作必须带 idempotency key。
生产环境操作必须 dry-run 后再确认。

七、针对“编造 API”的专门解决方案

1. Tool name 必须 enum 化

模型输出的工具名必须匹配注册表中的名字:
不要做模糊匹配自动纠正,例如把 search_documentation 自动映射到 search_docs。低风险场景可以建议模型重试,高风险场景不要自动执行。

2. 不要把后端 API 名直接暴露给模型

模型不应该直接调用内部真实 API:
更推荐包装成稳定工具:
并强制 preview → confirm → execute 两阶段。

3. OpenAPI / MCP / Tool Schema 自动生成,但必须人工审查

可以从 OpenAPI 自动生成工具 schema,但不要把所有接口原样暴露给 Agent。需要做:
删除危险接口。
合并过细接口。
改写工具描述。
增加参数约束。
增加输出 schema。
增加权限标签。
增加 few-shot 正反例。

八、针对“传错参数”的专门解决方案

1. 参数字段名要直观,不要内部黑话

坏例子:
好例子:
OpenAI Structured Outputs 文档也建议 schema key 命名要清晰直观,并为重要字段提供清楚的 title 和 description。(OpenAI开发者)

2. 枚举值尽量 enum 化

不要让模型自由填写:
应该改成:

3. 复杂参数拆成多个小工具

坏设计:
好设计:
工具越大,参数越多,模型越容易填错。工具应该更像“稳定原子能力”,而不是万能 API。

4. 时间、路径、金额、权限类参数不能让模型自由解释

用户说“明天”,Runtime 应该先解析成绝对日期,再传给工具。
用户说“当前项目”,Runtime 应该绑定成 workspace ID,而不是让模型猜路径。
用户说“我的账号”,Runtime 应该从 auth context 注入 user_id,而不是让模型填写。

九、错误反馈设计:让 Agent 能纠错,而不是继续幻觉

工具错误返回要满足四点:
结构化。
可恢复。
不泄露敏感信息。
明确告诉模型下一步怎么修正。
示例:
这比直接抛异常好,因为模型能基于 Observation 修复参数。

十、Prompt 仍然有用,但只能作为软约束

推荐系统 Prompt 写法:
但要记住:
Prompt 是引导,不是安全边界。
真正的边界必须在 Runtime。

十一、完整参考实现:防幻觉 Tool Runtime


十二、评估:怎么知道工具调用幻觉真的减少了?

不要只看主观体验。建议建立一套 function calling eval。
Berkeley Function Calling Leaderboard 是专门评估 LLM function/tool calling 能力的基准,覆盖串行、并行、多函数、多语言、真实场景,并评估模型在没有合适函数时能否 abstain。其 V4 页面也说明 BFCL V4 评估模型准确调用函数/工具的能力,并包含真实世界数据且会周期更新。(gorilla.cs.berkeley.edu)
企业内部可以构建类似指标:
指标
含义
Tool Selection Accuracy
是否选对工具
Unknown Tool Rate
是否编造不存在工具
Argument Schema Pass Rate
参数是否通过 schema
Semantic Validation Pass Rate
参数语义是否正确
Permission Violation Rate
是否尝试越权
Recovery Rate
参数错误后能否修正
False Success Rate
工具失败后是否编造成功
Human Approval Burden
需要人工确认的比例
Tool Cost / Task
每个任务平均工具调用成本
End-to-End Task Success
任务最终是否完成
测试集要包含反例:
用户故意要求调用不存在 API。
用户给模糊时间、路径、金额。
上下文中有多个相似工具。
工具返回空值。
工具返回错误。
工具 schema 版本升级。
MCP server 临时不可用。
子 Agent 尝试绕过父 Agent 权限。
Prompt injection 要求忽略工具规则。

十三、常见反模式

反模式 1:只靠 Prompt 禁止幻觉

这只能降低概率,不能提供保证。必须有 schema 和 runtime validation。

反模式 2:工具失败直接抛异常

如果工具异常直接打断 Agent,模型无法基于错误恢复。更好的方式是返回结构化 isError: true

反模式 3:把所有工具一次性塞进上下文

工具越多,选择越难,token 越高,冲突越多。应该使用 Tool RAG、工具分组、按需加载。

反模式 4:schema 过宽

这等于允许模型乱传参数。

反模式 5:参数自动纠错后直接执行

例如模型传了 /prod/db,系统自动修正为 /production/database 并执行删除。这很危险。低风险读操作可以建议重试,高风险写操作必须确认。

反模式 6:让子 Agent 拥有比父 Agent 更高权限

多 Agent 系统中,权限必须可传递、可收敛、可审计。否则父 Agent 不能写文件,却可以派一个子 Agent 去写文件,这就是权限绕过。

十四、落地清单

工具定义层
工具名稳定唯一;schema 严格;additionalProperties: false;字段名清晰;枚举值 enum 化;输出也有 schema。
模型调用层
开启 strict tool calling;限制每轮候选工具数量;不要暴露无关或危险工具;必要时用 Tool RAG。
运行时层
工具白名单;schema validation;semantic validation;权限判断;审批;sandbox;dry-run;幂等 key;审计日志。
错误反馈层
不要让异常直接中断;返回结构化错误;告诉模型是否可恢复;不要泄露 stack trace 和 secrets。
安全层
读写分离;shell 高风险;生产操作禁止自动执行;路径限制在 workspace;子 Agent 继承父 Agent 权限。
评估层
建立内部 BFCL 风格测试集;统计 unknown tool rate、argument error rate、permission violation rate、false success rate。

十五、结论

解决 Agent 工具调用幻觉的关键,不是“相信模型会变聪明”,而是把 Agent 设计成一个可验证、可拦截、可恢复的执行系统。
模型可以产生意图。
Schema 约束格式。
Runtime 校验事实。
Permission 控制边界。
Sandbox 限制影响面。
Observation 驱动纠错。
Eval 持续发现回归。
成熟 Agent,比如 Claude Code、OpenCode、OpenHands、OpenClaw Code Agent,都在不同层面体现了这个趋势:工具调用不是单纯 function calling,而是一整套工程化的 action governance 系统。
回到首页