type
Post
status
Published
date
May 17, 2026
slug
agent42
summary
tags
Agent
category
icon
password
一、问题本质:工具调用幻觉不是“模型小错误”,而是“执行链路失控”
Agent 工具调用幻觉常见有几类:
编造工具名:上下文里没有get_user_balance_v2,模型却调用了它。编造 API 参数:真实参数是user_id,模型传了uid、accountId或多传role。参数类型错误:limit要整数,模型传字符串;date要YYYY-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 文档中,自定义工具可以用
allowedTools、disallowedTools、tools 等方式控制。文档特别区分了两层:availability 决定工具是否出现在 Claude 上下文中,permission 决定工具调用后是否被批准执行。(Claude Code)这给我们的启发是:
工具治理不要只做“执行时拒绝”。对危险工具,最好一开始就不要暴露给模型。对条件性危险工具,可以暴露但执行前审批。对绝对禁止工具,既不暴露,也不执行。
3. OpenCode:用 schema 和 permission 管理工具
本文中的 OpenCode 指
anomalyco/opencode。OpenCode 文档显示,它内置 build 和 plan 两类 agent,其中 plan 是只读分析模式,默认拒绝文件编辑,并在执行 bash 前请求许可。(GitHub)OpenCode 的工具文档也明确支持通过
permission 字段控制工具行为,例如 edit: deny、bash: ask、webfetch: 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 -exec、xargs 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 系统。
分享
