Skip to content

1. 调用的四步 ​

任务要求"找出支付回调在哪处理"。模型没有直接回答,而是发起了一次工具调用。把它拆开看,四步。

第一步,声明进请求。 宿主在请求里放了一份工具清单,其中一条写着:名字是 search_code,说明是"在代码库里按正则搜索内容,返回文件路径与行号",参数是一个叫 pattern 的字符串。这份清单是模型的全部视野——它看不到工具的实现代码。

第二步,模型做选择。 模型读完清单,决定用 search_code,并生成一组键值:{"pattern": "handlePaymentCallback"}。它同时给出一个调用编号,用来在后续多轮里对应这次调用。

第三步,运行时校验后执行。 宿主拿到这组参数,先按结构校验(pattern 是否为字符串、是否必填),再按策略校验(这个路径在不在允许范围内、这次调用是否重复),然后才真正执行。执行权和校验权都在程序这一侧,不在模型这一侧。

第四步,结果回填。 执行结果被写回上下文,模型据此决定下一步:找到三处,读其中一处,或者继续换关键词搜。

四步里只有第三步与第四步是工具自己的责任。前两步出问题,改执行代码没有用——第一步出问题要改声明,第二步出问题多半是声明写得不够清楚。

这四步在协议层怎么表达,见《常见协议》的「Function Calling 的完整闭环」与「tool_use 与 tool_result」两节:OpenAI 用 tools[].function 加 tool_calls,Anthropic 用 input_schema 加 tool_use 与 tool_result 内容块。字段名不同,四步的结构相同。

1.1 为什么执行权必须在程序侧 ​

一个自然的疑问:既然模型能写代码,为什么不把参数直接交给 shell 或数据库执行?

因为参数错误会被放大成真实副作用。模型把路径写成 ../../etc/passwd,或者把 rm -rf /tmp/data 里的路径拼错成 /tmp,一次误生成就变成一次不可逆操作。中间的校验层防的是模型犯错,与恶意无关。

这条分工在《基础概念》6.3 节有一张表(LLM、Runtime、Tool Adapter、Environment 四者的职责)。这里要补的是为什么不能合并:合并之后,模型输出的每一个字节都有了执行效力,而模型对自己的输出没有可靠的校验能力——它不知道文件是否真的存在,也不知道这次删除是否已经在上一轮做过。


2. 工具是模型与真实世界之间的一条接口 ​

把上面四步抽象一层,会看到三个角色和一条接口。

角色它知道什么它不知道什么
模型有哪些工具、每个工具的参数结构工具怎么实现、环境现在什么状态
运行时全部工具的实现、权限策略、当前状态模型为什么选这个工具
环境真实状态与真实副作用有模型存在

工具是前两者之间的接口,也是模型认知环境状态的唯一途径。没有通过接口进来的信息,对模型来说等于不存在。

这句话有一个直接的工程含义:模型的世界由工具声明的质量决定。一个能读取日志的工具,让模型"知道"日志里有什么;一个只能统计行数的工具,让模型只能"知道"有多少行。同样一个模型,换一套工具,能做到的事就变了。

2.1 接口三段的职责 ​

一段完整的工具接口包含三部分,它们的职责不同。

段内容由谁产生由谁消费
声明名字、说明、参数结构工具开发者模型
参数一次调用的具体取值模型运行时
返回执行结果运行时与环境模型

三段之间有一条容易被忽略的约束:声明里承诺的返回结构,必须与真实的返回一致。声明说"返回标题、摘要、URL",实际返回的是纯文本,模型会在解析上出错,而错误发生在第四步,看起来像"模型理解能力不行"。

这条约束没法靠类型系统保证(返回值通常不受 schema 约束),只能靠测试与审阅。


3. 五类工具与两条分类轴 ​

工具不是一类东西。按"谁发起调用"与"作用在什么对象上"两条轴,可以摆成五类。

类型调用方向作用对象例子
感知Agent 主动调用读外部状态网页搜索、知识库检索、读文件、搜文件内容
执行Agent 主动调用改外部状态执行命令、写文件、调外部接口、发邮件
协作Agent 主动调用另一个 Agent 或人创建子 Agent、给子级发消息、取消、列举
用户沟通Agent 主动调用人回复消息、发结构化卡片、发通知
事件触发Agent 注册、外部触发Agent 自己定时器、监控后台命令、接外部事件源

两轴不是交叉分类框架——每一类在"作用对象"上的取值都是专属的,两轴的作用是让人快速看清定位。

五类的设计重点各不相同,这是分类的实际用途。

感知工具控制输出量。 一次搜索可能返回几万字,一份文档可能几百页。这类工具的设计核心是"一次给多少、怎么翻页、超阈值怎么截",拿到与否是次要问题。

执行工具控制权限。 误删的文件无法恢复,错误的命令可能导致服务中断,不当的接口调用可能产生真实财务损失。错误代价高,安全约束是核心。

协作工具控制归属。 谁持有控制流、谁承担失败、谁能终止。这三件事没定清,协作就会停在某个环节不结束。

用户沟通工具控制触达。 人可能不在线,消息可能跨多个渠道。这类工具要处理的是"异步送达"与"重复打扰"的平衡。

事件触发工具控制注册与唤醒。 它有两个时刻:注册时 Agent 声明自己关心什么,触发时外部事件唤醒 Agent。这两个时刻的语义完全不同,设计要分开考虑。

第五类与前三类的差别最大:前三类是 Agent 说什么就做什么,第五类是 Agent 说了之后等着,什么时候发生不由它决定。

3.1 前三类之外的两类为什么单独说 ​

事件触发与用户沟通这两类,设计离不开事件驱动的异步运行时——它们要么在等外部事件,要么在向可能不在线的人投递。这部分内容与实时交互一起放在别处讨论,本篇只到前三类为止。


4. 一条能力做成什么形态 ​

五类说的是"这条能力属于哪一类"。还有一个更前置的问题:同一条能力,该做成什么形态。

以"部署应用"为例。三种做法都成立。

text
形态一,专用工具:
  deploy_app(service, version, env)
  —— 一个调用完成全部步骤,参数受结构约束

形态二,通用执行器:
  只给命令执行工具,模型自己拼:
  npm run build && docker build -t app:v1 . && kubectl apply -f deploy.yaml

形态三,Skill:
  写一份说明,列出六步流程与每步的判定条件,
  模型读完之后用通用执行器逐步执行

三者的成本结构不同。

形态常驻成本参数传递谁能改失败方式
专用工具每个数百字节到数 KB结构化,受 schema 约束改代码并部署参数错、权限拒绝
通用执行器一份定义,覆盖大量场景自由文本,需自行转义不改命令拼错、转义出错
Skill目录一行,正文按需加载自然语言描述改一份文档步骤理解偏、漏步

4.1 默认往通用一侧放 ​

一个四则运算计算器不如一个装了 sympy 的代码解释器。理由是模型本身有代码生成能力,给它一个执行环境等于给了一个元能力:一个解释器可以替代几十个专用工具,还能处理设计时没想到的情况。

即使确实需要专用工具,粒度也应偏向整合。extract_pdf_text、extract_docx_content、extract_pptx_content 三个工具的共性是从文档里取文本,更好的设计是一个 read_document 加一个 file_type 参数。整合降低了选择负担(只需理解"读文档就用它"),也让扩展新格式变成加一个枚举值。

4.2 四种情况退回专用 ​

通用性有它管不到的地方,四种情况值得保留独立工具。

安全与权限。 生产数据库写操作、支付、删除数据这类需要精细授权与审计留痕的操作,一个开放的代码执行器做不到。专用工具能在参数级别限制范围。

屏蔽平台差异。 文件搜索用命令也能做,但不同系统的参数与输出格式不同。专门的工具能给出统一的、带行号的清晰反馈,也省掉了模型自己处理差异的成本。

使用频率极高。 高频操作值得一个专属入口,哪怕功能上已被通用工具覆盖。省下的是每次自己拼命令的出错概率。

参数结构复杂。 涉及嵌套对象、多字段联合校验、复杂类型约束的操作,结构化 schema 能有效引导模型正确传参;而命令行传参要处理引号与特殊字符转义,规则比 JSON 复杂得多,且各系统不同。

第四条值得展开:Skill 对参数的保真性要求更高。一个 Skill 里写"把参数以 JSON 写入临时文件,再用命令行读入这个文件",就是在绕开转义问题。这不是技巧问题,是形态选择带来的必然代价。

反过来说,Skill 对人类编写者更友好——不需要会编程,改一段文字比改代码再测试部署轻得多。这也是"变更频率"这一维的来源:流程还在频繁调整时用 Skill,稳定下来的底层操作做成工具。


5. 声明写在哪里、占多少 ​

无论做成哪种形态,只要是专用工具,它就必须有一条声明进请求。声明的成本是固定且每轮支付的。

设第 \(i\) 个工具的名字与参数结构占 \(s_i\) 字节、说明占 \(d_i\) 字节,\(n\) 个工具的总占用是:

\[c = \sum_{i=1}^{n}\left(s_i + d_i\right) \]

\(s_i\) 通常 80 到 150 字节(名字、参数名、类型、必填标记),\(d_i\) 通常 200 到 650 字节。说明占七成以上。

实际体量比直觉大。Claude Code 全部工具提示词文件合计约 135 KB,其中几个单项:

工具提示词体量里面写了什么
命令执行20.8 KB后台运行、超时、沙箱开关、提交规范、多条陷阱
子代理11.9 KB角色列表、分叉语义、并行要求、写法约束
PowerShell9.8 KB与命令执行对齐的 Windows 版本
待办列表9.5 KB使用时机、格式、何时更新
技能调用8.3 KB加载规则、调用形式、与工具的关系
延迟工具搜索4.3 KB两段式协议与失败策略

单轮请求的工具部分就要几十 KB。代入 \(n = 200\)、平均 450 字节,是 90 KB,在 200K Token 窗口里约占 11%——而这 11% 与当前任务是否相关无关。

成本随 \(n\) 线性增长,判断负担不是线性的。工具越多,模型越可能在相似工具之间选错或漏选。这解释了为什么单纯缩短说明解决不了问题:\(d_i\) 减半,\(c\) 减半,\(n\) 不变,选择困难照旧。

正确的优化顺序是先减少常驻数量(延迟加载),再考虑缩短长度。延迟加载单独成篇。

延迟加载的收益还有一个前提:每条工具本身的设计成立。设计有问题的工具,延迟之后只会更难被发现——排查时先要解决「它到底在不在当前工具集里」。


6. 说明怎么写 ​

说明的质量直接决定选得对不对。三份递进的定义,同一件事。

json
{
  "name": "search",
  "description": "搜索内容",
  "parameters": {"type": "object"}
}

第一份几乎无法使用:参数结构为空,模型不知道传什么;说明没有说清搜什么、返回什么。

json
{
  "name": "search_web",
  "description": "搜索公开网页",
  "parameters": {
    "type": "object",
    "properties": {"query": {"type": "string"}}
  }
}

第二份能跑了,但仍有缺口:多长的查询合适?返回几条?能不能搜内部文档?

json
{
  "name": "search_web",
  "description": "搜索公开网页。适合查找当前行业资料和竞品信息;不用于读取团队内部文档。返回标题、摘要、发布日期和 URL。query 必须是具体问题,不要传入整段任务描述。",
  "parameters": {
    "type": "object",
    "properties": {
      "query": {"type": "string", "description": "要搜索的具体主题"},
      "max_results": {"type": "integer", "minimum": 1, "maximum": 10}
    },
    "required": ["query"]
  }
}

第三份写清四件事:使用场景、排除场景、返回内容、输入粒度。

四件事里最容易被省略的是排除场景。写"不用于什么"看起来浪费字节,但它解决最常见的一类误选——模型知道这个工具能搜,就什么都搜。反例说明往往比能力说明更有用:多数调用失败的根因在于模型不知道工具不能做什么。

6.1 参数用具体例子 ​

抽象规范不如一个能直接套用的例子。写"timestamp:RFC3339 格式",模型在专注一件事时能理解;但它在同时处理多个工具、从历史里提取信息、权衡多个决策时,只能分出一小部分注意力确认格式。

改写成"timestamp:RFC3339 格式,例如 2024-03-15T14:30:00Z",模型可以直接照抄形状。手机号同理:"E.164 格式(国家代码加号码,无空格),例如 +8613888888888"。

6.2 返回格式要说清 ​

"返回 JSON 数组,每个元素含 title、url、snippet 三个字段"这类说明能减少解析错误。

对耗时长的工具,注明代价有助于规划调用顺序:"此工具要下载完整页面,大型网站可能五到十秒;只需要元信息时改用 get_page_metadata"。

6.3 附带一到五个调用示例 ​

schema 能描述类型,描述不了调用方式与典型参数组合——时间戳是秒还是毫秒、过滤条件怎么嵌套,这些隐式约定靠例子最容易传达。

加入示例后,调用准确率往往明显提升,在一些基准上从约 72% 提升到 90%(具体数值因任务而异)。

6.4 已知陷阱写在说明里 ​

Claude Code 的搜索工具说明里写了几条具体陷阱:正则里的字面大括号要转义、跨行匹配要显式开启、结果被截断时考虑更精确的模式。这几条把常见错误前置到了错误可能发生的地方,比事后调试便宜得多。

6.5 命名也是区分度 ​

工具数量超过几十个时,名字的信息量开始决定选择质量。

svc1_search、svc2_search 这类名字没有领域词,模型只能靠说明区分,而说明在长列表里容易被跳过。search_issues、search_docs、search_web 把范围写进名字,选择就变成模式匹配。

命名与分组解决的是两件事:分组解决同名冲突,命名解决同名共存时的可读性。

6.6 一条调试原则 ​

模型频繁选错工具时,先检查说明,不要先怀疑模型。多数选择错误的根因在于说明不清——适用范围模糊、缺反例、参数含义含糊。改说明的投入产出比通常远高于换一个更强的模型。


7. 参数怎么传才不失真 ​

参数错误分两类,schema 只管第一类。

类型例子谁能发现
形状不合法必填缺失、类型错、枚举越界schema
语义不正确路径存在但指错文件、查询词太宽、时间范围反了工具实现或模型自己

第二类无法在 schema 层面拦截,只能靠三件事降低概率。

枚举优于自由文本。 只有几种合法取值时用枚举声明。模型从枚举里选比从零生成稳得多,schema 层面就能拒绝非法值。

必填最小化。 只把真正必须的参数设为必填。必填项越多,模型越可能在信息不足时编造。其余参数给合理默认值。

一次调用完成一件事。 如果完成一个语义动作需要三次调用(创建、配置、启动),考虑合并。每次调用都是一次往返,往返多了中途出错概率上升。

第三个原则有个例外:合并会让单个工具的语义变复杂、说明变长。判断标准是能否用一句话说清"它做什么"。能,就合并;不能,就保持分开。

7.1 静默转换:一种比功能缺失更隐蔽的错 ​

工具在执行前悄悄"修正"模型的输入,是一种比功能缺失更隐蔽的反模式。

一个真实案例:某编辑工具接收 old_string 与 new_string,在文件里精确匹配替换。参数传递层会把中文弯引号静默转成英文直引号。于是出现一种模型无法理解的失败——它用读取工具看到文件里有弯引号(读取工具原样返回,没做转换),于是原样传入;但参数层已经转成直引号,与文件内容不匹配,工具返回"未找到"。模型反复试、反复失败,因为它无法理解为什么工具找不到自己明明看到的内容。

写入方向的问题更麻烦:模型本意写入弯引号(中文排版的正确选择),参数层却换成直引号。模型以为写对了,实际文件已被改。它若再读一次验证,看到的又是转换后的直引号,于是陷入困惑。

7.2 静默注入:模型不知情地多传了参数 ​

另一类保真性违规是工具在模型不知情时追加参数。某工具的 git commit 会自动附加一个标记参数,用于标识提交由 AI 生成。如果用户的 Git 版本较旧、不支持该参数,这个被静默注入的参数就会让提交报错。模型可能反复调整提交信息措辞、尝试不同参数组合,但无论怎么改都会失败——因为失败的原因不在它的输入里。

两类问题指向同一条原则:模型感知到的世界与工具操作的世界之间,不能存在系统性偏差。

确实需要规范化输入时(例如统一编码),必须在说明里写明,并在返回里告知模型。否则工具的"智能修正"制造的是一次模型无法自行诊断的故障。


8. 结果怎么返回 ​

工具返回一段长字符串是最常见的设计缺陷。模型要自己从中提取状态、结论、后续动作,而它可能读错,尤其在内容被截断时。

四段式把这件事故意拆开:

json
{
  "status": "success",
  "summary": "找到 5 个结果,其中 3 个与会议纪要相关。",
  "items": [
    {"title": "企业协作趋势报告", "url": "https://example.com/report", "date": "2027-02-11"}
  ],
  "artifact": "artifacts/search/call_01.json",
  "truncated": false
}
字段作用谁用
status决定下一步动作模型判断继续、重试、改参、报告
summary一两句话给结论模型默认读这段
items结构化数据需要精确判断时
artifact原始产物路径出错后追溯

truncated 这个布尔值看起来多余,但它解决一个具体问题:模型无法从内容判断"是不是全部"。显式声明截断,模型才知道该不该继续取。

8.1 搜索类只返回候选 ​

搜索的结果应当是结构化的候选列表(标题、位置、摘要片段),由模型先浏览再决定深入读哪一条,不必把拼接后的全文一次性给出。

结果多时提供分页或游标:默认返回前若干条,并注明总数与获取下一页的方式。翻不翻页由模型决定,不是由工具替它决定。

8.2 读取类支持偏移与上限 ​

读文件、读日志这类工具应支持偏移与条数上限,按需读取指定片段。

必须截断时,明确标注省略了多少、如何继续读。静默截断是危险的——模型会以为自己看到了全部,基于不完整的信息做判断。

8.3 失败分类决定恢复动作 ​

失败不能只回一个错误字符串,因为不同的失败需要不同的恢复动作。

分类含义恢复动作典型错误
可重试临时故障稍后重试,通常带退避限流、锁冲突、网络抖动
需改参参数非法或模式错误改参数再调正则语法错、枚举越界
环境问题依赖缺失、路径不可达、权限拒绝换路径或报告环境文件不存在、权限不足
中止超时、调用方取消可重试,也可放弃超时、取消信号

四类混在一起时,模型的默认行为是重试,而"需改参"这一类重试多少次都不会成功。

一个正面例子:某个实现的搜索工具让四类错误码各自对应一种恢复动作,并且明确区分"成功且有结果"(退出码 0)与"成功的空搜索"(退出码 1)。把空结果与失败分开,模型才不会把"查无此项"当成"工具坏了"。


9. 权限与副作用的三层 ​

schema 只能检查形状。真实防护要三层。

text
模型生成参数
    ↓ 第一层:输入验证 —— 形状、必填、范围、路径是否越界、命令是否含拼接
    ↓ 第二层:权限与策略 —— 工作目录、允许的动作、重复动作、风险等级
    ↓ 第三层:独立审查 —— 对不可逆动作做第二视角判断
执行
    ↓ 结果:状态、摘要、结构化数据、产物路径

9.1 第一层:快速失败 ​

检查所有参数的合法性:路径里有没有 ../ 造成的越界、命令参数里有没有分号或管道符拼接的额外命令、类型与格式对不对。

关键是快速失败——发现异常立即拒绝,不尝试"智能"修正。修正会引入第 7 节讲的那类系统性偏差。

9.2 第二层:权限与策略 ​

文件操作限制在特定工作目录;命令执行维护禁止清单;外部接口检查配额与速率。

禁止清单只是最基础的防护,不能作为唯一手段——攻击者可以用变形命令绕过字符串匹配。更稳的做法是结合语义解析,理解命令的实际意图。

这一层还要处理两件常被漏的事。

重复动作。 同一个删除请求被执行两次,结果通常比执行一次更糟,而且模型不知道为什么失败。工具要么做幂等,要么显式拒绝重复。

不可逆动作。 删除、覆盖、对外发送。这类操作建议独立授权:把"不可逆"作为工具的一个属性,由外层策略决定是否需要额外确认,不必每次调用都问人。

9.3 第三层:独立审查 ​

对不可逆且影响重大的操作,加一个独立视角。

事前审批:执行前,一个模型提议行动,另一个独立模型审查并批准。两个模型应来自不同家族、能力相近——不同来源带来认知多样性,能力相近才能互相理解。提示词上,两者的底层规则一致,关注点不同:提议方强调完成任务,审批方强调风险与规则。

审批失败后不应简单重试,而应把拒绝理由作为一次工具结果加入轨迹。从模型视角看,这就像一次调用失败返回了错误与修正建议——它已经具备处理这类输入的能力。

事后验证:操作完成后,用不同模态检验结果。生成了文档就渲染成图看排版;改了配置就在沙盒里跑一遍看是否生效。换模态的价值在于避开同一盲区。

并行旁路校验:主模型决定执行某个调用时,一个轻量的独立调用同时判断这次调用是否安全。它只读结构化字段(工具名与参数),不读主模型的自由文本——一旦读,藏在文本里的诱导话术就能影响判断。

审查对象不同,对模型能力的要求也不同:审查开放式思考需要能力相近的模型,判断"这条命令是否危险"这类分类问题,轻量模型足够。

并行旁路校验还要配一个拒绝熔断:连续多次拒绝时不应无限重试,应转人工判断。

9.4 让安全检查在体验上隐形 ​

一种做法是把"展示"与"放行"拆开并行:界面先显示"正在读取文件",后台同时做安全检查。用户感受到的是进度,不是等待。


10. 完整样例:代码搜索工具 ​

三处都做到位的工具不多,代码搜索是其中一个。把它的设计拆开,能看清前几节的判断如何变成具体选择。

10.1 声明 ​

九个可调项,其中三项直接服务于"控制返回体积"。

参数取值默认作用
输出模式内容、文件路径、计数文件路径决定返回什么形状
结果上限整数,0 表示不限250决定返回多少条
偏移整数0从第 N 条开始

默认输出模式选"文件路径",不选"内容",这个选择很关键:多数搜索的真实目的是"找到是哪些文件",内容可以在确定文件之后按行读取。默认给路径,把"读内容"变成一次显式选择。

说明里还有两条效率提示:按文件类型过滤比按通配符更高效(前者用语言类型索引,后者要遍历路径);多行模式默认关闭,需要跨行匹配时显式开启。

另有一条隐含过滤:版本控制目录(.git、.svn、.hg、.bzr、.jj、.sl)自动排除。这类目录里的内容几乎总是噪声,排除它们相当于给结果做了一次免费去重。

10.2 参数 ​

三个参数之间的相互作用写得很细:上下文行数(前、后、前后)只在内容模式下有效,其他模式忽略;行号默认开启;偏移与结果上限的语义是"先跳过 N 条,再取 M 条",等价于命令行的 tail -n +N | head -N。

这种"参数只在某模式下有效"的设计有代价:模型可能填了无效组合。但它比重载三个不同工具更省声明空间——一个工具加九个参数,比三个工具各带三个参数占得少。

10.3 返回 ​

截断的表达方式值得单独看,两条规则。

第一,只有在真的发生截断时才报告上限值。没有截断时不报告,避免模型误以为"有更多结果"而去翻页。

第二,上限为 0 表示不限,但说明里明确写"谨慎使用——大结果集浪费上下文"。

第一条是第 8 节 truncated 字段的更精细版本:不只告诉"是否截断",还告诉"当前限的是多少",让模型能判断该不该用偏移继续取。

结果体积还有一个上限:内容超过约 20 KB 时落盘,模型拿到的是预览加路径。没有这个上限的会话里,密集使用搜索会让工具结果占到几万 Token。

10.4 三处在这个工具上的配合 ​

声明用默认值压低体积(默认只给路径、默认 250 条上限)。 参数用枚举把选择收窄(三种输出模式,不是自由文本)。 返回在真的截断时才报告上限,避免误导。

三处配合的结果是:同一个工具在"只想看看有哪些文件"与"要读具体匹配行"两种场景下都能用,而不需要两个工具。

代价是参数多、交互复杂。一个只有三个参数的工具更容易被正确使用,但它无法同时服务两种场景。这个取舍与第 7 节讲的"合并工具"是同一条判断:合并后能否用一句话说清它做什么。这个工具能——"按模式搜索代码并返回匹配"。


11. 四家对照 ​

维度Claude CodeCodexOpenCodeDeepSeek Harness
声明体量最大(单工具 20 KB 级)中等较小中等
返回声明最大结果字符数按内容类型选策略schema强制输出结构声明
失败表达错误文本错误码错误类型四类错误码
权限粒度工具名级策略与命名空间权限动作级执行守卫
结果截断20,000 字符触发落盘截断加外置2,000 行 / 50 KiB超内联上限写外置

一处值得单独说:DeepSeek Harness 的输出声明是强制的,且出现在"不得泄漏到模型请求中"的字段清单里。这个设计把"工具返回什么"定为强制约定:调用方不必猜返回结构,宿主可以按声明的结构校验。

Claude Code 走的是另一条路:把大量约束写进说明(例如搜索工具的那几条陷阱),正确使用靠说明保证。代价是说明很长,收益是模型在失败前就知道该避开什么。


12. 失败模式 ​

四类,按四步里的位置归类。

选错。 症状:模型反复试不同工具,或选一个明显不合适的。成因:名字或说明区分度不足。修在声明。

填错。 症状:参数被 schema 拒绝,或参数合法但结果无用。成因:自由文本参数、必填项过多、说明没给例子。修在参数段。

被静默改掉。 症状:模型反复失败且无法理解原因。成因:参数层做了转换或注入。修在传递层。

读错。 症状:模型把部分成功读成成功,或把截断当完整。成因:返回是长文本、截断没标注、失败没分类。修在返回段。

四类里第三类最难发现,因为它不报错,只是效果持续变差。一个检查方式:拿说明里承诺的返回字段,与最近一次真实调用的返回逐项对照,看是否都真的存在。这个检查很便宜,但很少有团队做。

12.1 诊断顺序 ​

拿到"工具用不对"的反馈之后,按这个顺序查。

  1. 看是选错还是填错。选错查声明,填错查参数。
  2. 选错的话,用"明显该选它"与"明显不该选它"各测一次。两次都对,说明是长列表里的干扰问题,不是说明问题。
  3. 填错的话,看是 schema 拒绝还是执行失败。前者改 schema,后者看失败分类。
  4. 都不是的话,查返回。模型是不是把部分成功读成成功,或把截断当完整。

四步走完基本能定位。跳过第一步直接改说明是最常见的误修——填错参数时改说明不会有帮助。