4.4 ACI:像设计人机界面一样设计工具
一句话结论:工具是 Agent 和世界之间的契约。你给人的软件设计界面(HCI),给模型的软件设计"上下文界面"(ACI)——用户是模型时,容错率更低,所以设计要更防呆。
读这篇之前
需要先读 4.1–4.3。这一篇是工具设计的"设计观":不是讲怎么写代码,而是讲怎么让模型用得顺手。
一、HCI vs ACI:一个关键类比
我们很熟悉 HCI(Human-Computer Interaction,人机交互):按钮要够大、错误提示要清楚、危险操作要二次确认。
ACI(Agent-Computer Interface)是同一件事,只是用户从人变成了模型。 [原文 4629]
| 维度 | HCI(人) | ACI(模型) |
|---|---|---|
| 用户 | 人 | 模型 |
| 阅读能力 | 会浏览、会试错、会查文档 | 只能看上下文中给它的内容 |
| 容错方式 | 有经验、能举一反三 | 按模式匹配,容易钻牛角尖 |
| 反馈 | 能看到界面变化 | 只能看到你回传的文本 |
| 学习成本 | 可以培训 | 改不了,只能改你的设计 |
最后一行是关键:人可以被培训,模型不能。 所以当模型用错工具时,唯一能改的是你的设计。
工具和传统 API 的根本区别
| 传统 API | Agent 工具 | |
|---|---|---|
| 调用者 | 程序员(读过文档) | 模型(只看到 description) |
| 参数错误 | 编译期/运行时捕获,程序员修正 | 运行期出现,必须靠信息回填让模型自修 |
| 设计目标 | 完备、正交、通用 | 不易误用、错误可自解释 |
| 文档 | 给人看,可以很长 | 给模型看,要短、要准、要含边界 |
一句话:传统 API 追求"能力完备",Agent 工具追求"不易用错"。
二、四条设计原则
原文给出的四条 ACI 原则,每条都对应模型的一个具体弱点:[原文 4629]
原则一:给模型足够的 Token 空间想清楚
问题:如果你要求工具调用格式极其紧凑(比如必须在一行内完成复杂决策),模型没有"思考空间",容易出错。
做法:允许甚至鼓励模型在调用工具前先输出思考内容。这正是 ReAct 的 Thought 字段存在的意义(3.3)。
{
"thought": "用户问的是天气,我需要调 get_weather。城市是北京,日期未指定,默认今天。",
"action": "call_tool",
"tool_name": "get_weather",
"tool_args": {"city": "北京"}
}
这条原则的本质:把"隐式推理"变成"显式文本",让推理过程成为后续 token 的依据(3.3 讲过的机制)。
原则二:格式贴近模型的训练数据
问题:如果你发明了一套独特的格式(比如用 <<<>>> 包裹参数),模型没见过,只能靠推理去理解,容易错。
做法:用模型熟悉的格式。
| 差 | 好 |
|---|---|
| 自定义标记语言 | JSON |
| 特殊分隔符 | Markdown 标题 |
| 奇怪的缩进约定 | 常见 YAML / 列表 |
这条原则的依据在 2.1:模型擅长"检索"训练数据里的模式,不擅长"推理"陌生的规则。用熟悉的格式,等于让它做擅长的事。
原则三:避免不必要的格式开销
问题:如果工具返回值里塞了大量模型用不到的信息(时间戳、请求 ID、冗长的嵌套结构),既浪费 Token(2.1),又增加噪声(2.2)。
做法:工具返回只给模型需要的。
差:{
"code": 0, "msg": "success",
"requestId": "a3f8...", "timestamp": 1761..., "cost": 12,
"data": { "list": [...], "total": 42, "page": 1 }
}
好:{
"items": [...],
"total": 42,
"note": "共 42 条,已返回前 20 条"
}
这份"归一化"工作属于工具层的职责(2.5 第五节)。
原则四:Poka-yoke(防呆设计)
这是四条里最有价值的一条。
Poka-yoke 来自制造业:通过设计让"错误根本无法发生"。用在工具上就是——与其报错,不如让它不可能填错。
| 差(容易错) | 好(防呆) |
|---|---|
status: string "状态" |
status: enum(active, disabled) |
path: string "文件路径" |
path: enum(允许的 5 个路径) |
format: string "格式" |
三个独立工具:export_pdf / export_md / export_html |
action: string + data: object |
拆成 create_x / update_x / delete_x |
核心思想:把"选择"从开放集合变成封闭集合。
原文提到一个真实案例:SWE-bench 里的某个改动就是一个防呆设计——把原本需要模型自己拼的完整路径,改成让模型只选文件名,路径由工具内部处理。错误率显著下降。[原文 4629]
三、Think Tool:让 AI 先想后做
原文介绍了一个非常简洁但有效的技巧。
问题场景:Agent 在长任务中会犯错,因为它在"边做边想",缺少一个"停下来梳理"的环节。
Think Tool 是什么:一个不执行任何操作的工具,模型调用它只是"把想法写出来"。[原文 4629]
name: "think"
description: "用于梳理思路、检查约束、规划下一步。不产生任何副作用。"
parameters:
thought: "你的思考内容"
它的实现极其简单——工具实现就是"把输入原样返回"。
为什么有效?
| 机制 | 解释 |
|---|---|
| 强制"想"变成显式 Token | 模型后续输出能看到这些思考(3.3) |
| 打断"惯性执行" | 长任务中提供一个"检查点" |
| 让约束被重新提及 | 在 think 里复述规则,等于重申约束 |
什么时候该用:任务步骤多、约束多、或者发现 Agent 常在某类判断上出错时。简单任务不需要。
四、用 Agent 优化 Agent 的工具
原文还给了一个进阶思路:工具描述可以自动优化,流程三步:[原文 4629]
| 步 | 做什么 |
|---|---|
| Prototype | 写一版初始工具描述 |
| Evaluate | 用评测集跑,统计"该调没调"和"参数正确率" |
| Optimize | 让模型分析失败案例,提出描述改进 |
五个优化时的检查点:
- 模型是否在该调用时调用了?
- 模型是否在不该调用时误调了?
- 参数是否填对了?
- 是否反复调用同一个工具(说明结果没被正确理解)?
- 描述里是否缺少边界说明?
这里的关键前提是 Evaluate:没有评测集,优化就是瞎改(第 13 章)。
五、常见误区
误区一:工具设计是后端工程问题。
它是交互设计问题。用户是模型,交互通道是 description 和返回值。
误区二:防呆设计限制了能力。
恰恰相反,它把"可能出错的空间"消掉,让能做的事更可靠。能选三个值比能填任意字符串更强大,因为前者几乎不会错。
误区三:Think Tool 是多余的调用,浪费 Token。
它在复杂任务上减少的返工远多于它的成本。简单任务别用,复杂任务值得。
误区四:工具返回值给全一点,模型自己会挑。
模型不擅长"过滤",它会把整段都当真。返回要精。
误区五:工具描述写得越像技术文档越专业。
模型的读者不是程序员。它需要的是"什么时候用",不是"内部实现"。
误区六:ACI 只对弱模型重要。
强模型的错误率更低,但结构性误用的风险是一样的。防呆设计的收益对所有模型都存在。
六、动手:四条 ACI 原则的对照改造
把 4.4 的四条原则各举一个改造实例。
原则一:给模型思考空间
差:要求模型只输出 {"action": "..."}
好:允许 {"thought": "...", "action": "..."}
改造点:加一个 thought 字段。成本几十 Token,收益是决策质量明显提升(3.3)。
原则二:格式贴近训练数据
差:用自定义分隔符包裹(如三个尖括号 + BEGIN / END)
好:用 JSON 或 Markdown 标题
理由:模型见过无数 JSON 与 Markdown,没见过你的自定义标记。
原则三:避免不必要的格式开销
差:返回 {"code":0,"msg":"ok","requestId":"...","timestamp":...,
"data":{"list":[...],"total":42,"page":1}}
好:返回 {"items":[...],"total":42,"note":"共 42 条,已返回前 20"}
改造点:删掉 requestId、timestamp(模型不需要,而且会破坏缓存)。
原则四:防呆(最有效)
差:{"format": "string"} ← 模型可能填 pdf / markdown / PDF / MD
好:三个工具 export_pdf / export_md / export_html
或者 {"format": {"enum": ["pdf","md","html"]}}
改造点:把开放集合变成封闭集合。这条能一次性消掉一整类错误。
一个判断
如果你的工具经常"参数传错",先问:能不能让这个参数根本没法传错?
一个来自真实项目的例子
某系统有个工具 export(format, path, options)。上线后 30% 的调用失败。
改造方式:
| 原设计 | 改造后 |
|---|---|
format 自由文本 |
enum: [pdf, md, html] |
path 任意路径 |
固定输出到 exports/ 目录,只传文件名 |
options 对象(6 个可选字段) |
拆成 2 个布尔参数:include_toc、include_images |
失败率从 30% 降到 2%,一次改造,没改模型、没改提示词。
这就是防呆的威力:它消灭的是"出错的可能性",而不只是"把错误报出来"。
七、一个补充:防呆的三个层次
防呆不只有"改枚举"一种。三个层次从弱到强:
| 层次 | 做法 | 效果 |
|---|---|---|
| ① 约束参数 | 枚举、范围、格式校验 | 拦住格式错 |
| ② 消除参数 | 能从上下文推断的,就不要让模型传 | 消灭一整类错误 |
| ③ 消除工具 | 用更少的工具覆盖更多场景 | 消灭选择错误 |
第二层最容易被忽略
差:delete_file(path, confirm, dry_run)
→ 模型要传 3 个参数,每个都可能错
好:delete_file(path) 且默认 dry_run
→ 真正执行需要单独的 confirm_delete(path)
把"确认"从参数变成"第二次调用",模型就不会忘记传 confirm: true。
第三层的例子
差:5 个工具 read_file / read_json / read_yaml / read_csv / read_xlsx
好:1 个 read_file,按扩展名自动解析
工具少了,选择错误就消失了。
注意与 4.5 的张力:合并工具会降低"选择粒度"。判据是"副作用是否相同"——相同则合并,不同则拆开。
八、一个补充:ACI 的最小实践清单
如果只做三件事来改善 ACI,做这三件:
| # | 动作 | 收益 |
|---|---|---|
| 1 | 给所有参数加示例值 | 参数错误率下降最明显 |
| 2 | 把开放集合改成枚举 | 消灭一整类错误 |
| 3 | 在描述里写"不适用时用谁" | 误选率下降 |
这三件都不需要改代码,只改元数据。
顺序建议:先做第 2 件(收益最大、改动最小),再做第 1、3 件。
一个反例:把 ACI 做成大工程
错:专门设计一套工具描述 DSL、写生成器、加校验层
收益不成比例。ACI 的改进绝大多数来自"元数据写得好",而不是"框架复杂"。
判据:如果你花了三天做工具系统的框架改造,但描述还是"搜索"两个字,那顺序反了。
九、一个补充:ACI 与提示词的关系
一个容易混淆的问题:工具描述和系统提示,该谁写"什么时候用工具"?
| 位置 | 该写什么 |
|---|---|
| 工具描述 | 这个工具本身何时用、不用、返回什么 |
| 系统提示 | 整体的工作流程(先做什么、后做什么) |
分界线:"工具自己的属性"放描述,"任务的编排"放系统提示。
错:系统提示里写"search_code 用于查找函数定义"
→ 工具改动要改两处,容易不一致
对:描述里写;系统提示只写"第一步先定位代码,用 search_code"
这样工具可以独立演进——这是 8.5 讲的"协议层与业务层分离"的同一条原则。
十、一句话收尾
ACI 的全部努力,都是在弥补一个事实:模型没有常识,只有模式。
人会自然地"知道"不该把 ../../etc/passwd 传进文件路径参数;模型不会——它只看到"这里该填一个路径字符串"。
所以防呆不是限制,是把人类的常识编码进接口设计。
十一、一个补充:工具集的"年龄"问题
工具集和代码一样会老化。三个老化信号:
| 信号 | 含义 |
|---|---|
| 描述里出现过时的功能名 | 实现改了,描述没跟 |
| 有工具的实现已经没人记得 | 无人维护 |
| 有工具被"绕过"而不被使用 | 它已不适用 |
第二个最危险:一个没人维护但有权限的工具,是最容易出事的地方(14.1)。
建议:每季度做一次"工具清点",把没人维护的工具先降权(缩小权限)再决定去留。
十二、最后一条
防呆的最高境界是"让工具根本不存在犯错的可能"——不是拦住错误,而是让错误无处发生。
参数要自由文本 → 改成枚举;动作要拼装 → 拆成固定工具;路径要用户填 → 由系统决定。
每消掉一个"可错空间",就少一整类需要排查的问题。
十三、下一篇
工具设计的原则讲完了。但还有一个问题越来越现实:当工具从 5 个变成 50 个,会怎样?
能力膨胀不是线性成本。下一篇讲"工具爆炸"——以及它对应的解法。