☰
SA-04 ACI工具设计方法论
2026/10/7 11:11:42 网站建设 项目流程

工具即提示:Agent-Computer Interface(ACI)设计方法论

系列导航:00 系列导航 · 01 单 Agent 总论 · 02 ReAct 原理 · 03 手写内核 ·04 ACI 工具设计· 05 上下文工程 · 06 两个增强变体 · 07 上线前清单
小提一句: 由于平台每日发布笔记数量限制,目前系列笔记还没全部上传。后续内容会持续更新,大家可以先点个关注、收藏,以免错过~


引子:这是整个系列投入产出比最高的一篇

第 1 篇给过一张表:单步成功率p决定长程任务的一切。

单步成功率 p10 步成功率20 步成功率
0.9035%12%
0.9560%36%
0.9882%67%
0.9990%82%

现在的问题是:怎么把 p 从 0.90 提到 0.98?

换更强的模型?能提一点,但贵,且很快遇到瓶颈。真正有效的手段是ACI(Agent-Computer Interface)设计,也就是把工具接口设计对。这是 Anthropic 在构建 SWE-bench Agent 时得出的头号经验,他们的原话是:

投入在工具定义上的提示工程精力,应当与投入在 system prompt 上的同等。

换句话说:工具描述不是文档,它是提示词的一部分,而且是每一步都会被重读的那部分。


一、ACI 与 HCI:模型是一种什么样的"用户"

Anthropic 用的类比是:像做人机交互(HCI)设计一样做 ACI 设计。这个类比很好,但要知道模型这个"用户"和人有几个本质区别:

维度人类用户模型"用户"
交互通道视觉 + 直觉 + 常识只有文本,且只能看到你放进上下文的文本
错误处理看到报错会试错、会查文档只能从错误信息里学,且这次学到的下次不一定记得(除非你还留在上下文里)
记忆长期记忆、跨会话本次上下文内的记忆,出了窗口就是陌生人
耐心 / 成本免费每读一个 token 都要钱和时间
常识有,能脑补缺失信息形式上"有",但脑补出来的就是幻觉
反馈循环可以问同事只能靠你返回的错误信息

由此得到 ACI 设计的第一原则:

不要假设模型能推断任何东西。任何它没有在上下文里看到的信息,对它都不存在;任何它看起来"推断出来"的东西,都可能是编的。

这条原则长出了下面全部 12 条军规。


二、工具定义的七要素模板

一个合格的工具定义应该长这样(以search_logs为例):

- search_logs 用途 :按服务名与时间范围检索错误日志,返回日志条目摘要。 何时用 :已经确定要查某个具体服务的日志时。 何时不用:尚未确定错误发生在哪一层(网关 / 服务 / DB)时——先用 query_metrics 定位层级。 参数 :service string 必填 服务名;若 5xx 产生于网关层,此处填 "gateway" since string 必填 绝对时间,格式 "YYYY-MM-DD HH:mm:ss"; 不知道当前时间请先调用 get_current_time level string 可选 枚举 error|warn|info,默认 error limit integer 可选 1-100,默认 20 返回 :最多 limit 条日志摘要,每行 "<时间> <服务> <消息>"; 若被截断会注明匹配总数。 常见错误:传入相对时间(如 "30分钟前")会导致解析失败,必须使用绝对时间。

七要素:

要素作用缺了会怎样
用途一句话说明它是干什么的模型靠猜
何时用正向触发条件该用的时候不用
何时不用负向边界不该用的时候乱用(最常被省略)
参数(含必填/枚举/默认值/单位/格式)消除参数歧义参数乱填、缺参数、格式错
返回(含形状与截断行为)让模型知道能拿到什么重复调用、误判"没有结果"
常见错误把踩过的坑写回描述同一个错反复犯
与其他工具的分界见"何时不用"工具混淆,选择准确率暴跌

其中“何时不用”是我最想强调的一条。我们在自己系统里做过统计,加上负向边界后,工具误选率下降最明显的就是这一项。因为模型犯的错往往不是"不知道该用什么",而是"两个都像对的"。


三、12 条军规

A 组:命名与边界

  1. 工具名是自解释的动宾短语,且两两互斥。
    反面教材:get_data/query/fetch_info三个工具同时存在,模型(和人类)都无法区分。判断标准:把工具名和描述盖住用途字段,只看名字能否猜出该不该用。
  2. 每个工具显式写"何时不用"。
    见上。尤其是能力有重叠的工具对(如search_logsvsquery_metrics),必须互相点名:“若你要找的是数值随时间的变化趋势,用 query_metrics 而不是本工具。”
  3. 能合并就合并,不能合并就切干净。
    高频共现的两个操作(如"查指标"和"查该指标的历史基线")应合成一个带参数的工具,而不是两个。切分的原则是按"决策语义"切,不按"后端接口"切。别把你们微服务的边界直接暴露给模型。

B 组:参数设计

  1. 格式贴近模型在自然文本中见过的形式。
    模型的先验来自互联网文本。它见过无数次"2026-09-30 14:20:00",几乎没见过"2026-09-30T14:20:00.000+08:00|slot:3"。用前者。
  2. 不要制造"格式开销"(format overhead)。
    Anthropic 的原话:避免让模型做它不擅长且需要精确计数的体力活。三类典型陷阱:
陷阱例子后果
要求精确计数“传入要修改的行号范围”模型数错行数,改错地方
要求复杂转义“把这段代码的字符串转义后传入”转义漏一个反斜杠,语法错
要求重排/排序“按依赖顺序传入模块列表”顺序错,执行失败

修法一律是:让工具接受更宽松的输入,由代码做规范化。比如让工具接受"文件路径 + 函数名"而不是"行号"。

  1. 参数要自带防错能力(Poka-yoke)。
    见第 4 节,这是本组最重要的一条。

C 组:返回设计

  1. 返回"可据以决策的信息",而不是原始数据 dump。
    模型需要的是"这个指标在 t-11m 突增",不是 1800 个采样点。让工具在服务端做降采样、聚合、异常标注,把计算放在工具里,而不是放在模型的上下文里。
  2. 截断必须显式标注,并告诉模型怎么拿更多。
    第 3 篇的truncate()已经处理了这点。补充:截断信息里最好带一句"如需查看第 N 到 M 条,调用read_range(handle, start, end)"。
  3. 结构化优先,但保持自然语言可读。
    纯 JSON 对模型友好但费 token;纯文本省 token 但易误读。折中:用简短的自然语言行 + 明确的字段名,如<时间> <服务> <消息>的固定行格式。模型对固定行格式的解析准确率极高,且比 JSON 省 30~50% token。

D 组:错误与反馈

  1. 错误信息必须可操作:说清"错在哪 + 怎么改"。
❌✅
Error 400[ToolError] search_logs 的 since 参数需要绝对时间("YYYY-MM-DD HH:mm:ss"),收到 "30分钟前"。请先调用 get_current_time 获得当前时间再换算。
File not found[ToolError] read_repo_file 找不到 "/src/db/pool.py"。仓库中的候选路径:["order-service/db/pool.py", "common/db/pool.py"]。路径必须是从仓库根开始的绝对路径。

第二条里那个"候选路径"是神来之笔:把纠错所需的信息直接放进错误里,模型一次就能改对,而不是把"找不到文件"当成一次探索去反复试。

  1. 空结果必须区分"真的没有"与"查询条件错了"。
    []是最糟糕的返回:模型无法判断是"没有错误日志(好事)“还是"服务名写错了(坏事)”。返回:
匹配 0 条。可能原因:(a) 该服务在此时间窗内确无 error 级日志; (b) service 名称不正确(已知服务:order, gateway, payment)。 建议先调用 list_services 确认服务名。
  1. 把线上踩过的坑写回工具描述。
    工具描述应该是一个活的文档。每当你从 trace 里发现一类新的工具误用,就往描述的"常见错误"里加一条。这是 ACI 唯一正确的迭代方式——不是拍脑袋重写,而是由失败案例驱动。第 4 节的混淆矩阵和第 7 节的评测,就是在告诉你该往哪儿加。

四、Poka-yoke:让错误在接口层面不可能发生

“防错”(poka-yoke)源自丰田生产体系,指的是通过设计让错误无法发生,而不是靠操作者小心。

Anthropic 的经典案例:他们的 SWE-bench Agent 在移出根目录后,用相对路径频繁出错。修法不是"在 prompt 里提醒模型用绝对路径",而是把工具参数改成强制绝对路径。此后该错误归零。

这个案例的精髓在于:约束放在代码里,而不是放在提示里。提示是建议,代码是物理定律。

一批可以直接抄的 poka-yoke 清单:

风险弱做法(提示)强做法(接口)
路径写错“请使用绝对路径”参数 schema 要求以/开头,收到相对路径时自动基于仓库根解析并在返回中告知解析结果
时间写错“请使用绝对时间”提供get_current_time工具;参数接受"30m"这类枚举化相对偏移并由服务端换算
枚举写错描述里列出枚举用 JSON Schemaenum约束,非法值直接拒绝并回候选
删改误操作“请谨慎操作”dry_run参数默认 True;写操作标记side_effect=True走授权钩子
返回过大“结果可能很长”强制分页 + 上限(如limit ≤ 100),超限直接拒绝并提示收窄条件
参数单位歧义“window 单位是分钟”参数名带单位:window="30m",或只接受带单位的字符串
危险命令“不要执行 rm”工具层做命令白名单,非白名单直接拒绝

所以:每当你想在 prompt 里写"请注意……"的时候,先问一句:这个约束能不能放进代码?能放进代码的,绝不留给提示。


五、工具数量:为什么"全集"是个坏主意

5.1 甜点区

⚠️ 经验值,缺严格定量研究,但在多个团队复现过:< 10 个精选工具,通常优于 50 个全集。

原因有三:

  1. 选择准确率随候选数下降。这是最直接的原因:候选翻倍,误选率不是线性上升,而是在语义相近的工具上集中爆发。
  2. 工具描述本身是固定 token 开销。算一笔账:
20 个工具 × 平均 150 token = 3000 token 工具定义 每一步都要重发(除非命中前缀缓存) 12 步 × 3000 = 36,000 token —— 纯工具描述,不含任何任务内容

这还没算它对注意力的稀释。
3. 工具越多,边界越难写干净。50 个工具里不可能两两互斥,必然有重叠,重叠就是误选的温床。

5.2 超过 10 个怎么办:三层路由

按需加载

长尾层 · 按需检索(不常驻,可 >20 个)

提供 search_tools(query)
让模型按名字取用

领域层 · 按任务类型预加载(3~5 个)

任务路由时确定
例如代码类任务 → 加载 git / diff / test

核心层 · 常驻上下文(5~8 个)

高频、跨任务通用
search / read / finish ...

⚠️ 第三层有个真实风险:模型不知道自己不知道什么。它不会去search_tools一个它压根没听说过的工具。所以长尾层只适合"模型大概率知道该能力存在、只是不知道确切名字"的场景(如公司内部几十个数据接口)。如果能力本身模型没概念,检索工具救不了你。

5.3 与 MCP 的关系

MCP(Model Context Protocol)解决的是工具接入的标准化(怎么把外部系统接进来),不解决工具选择的质量问题。恰恰相反,MCP 让接入变得极其容易,反而使"工具全集"问题更严重:能接不等于该接。接入之后,本文的裁剪与路由逻辑依然要自己做。


六、返回值的形状:一个被严重低估的杠杆

同样的信息,返回形状不同,模型的下一步决策质量可以差很多。四条具体建议:

6.1 给"结论"而不是"原料"。
query_metrics返回 1800 个采样点 vs 返回"降采样 6 点 + 一句『在 t-11m 发生突增(斜率 > 20×)』"。后者可能只有 50 token,但包含了模型真正需要的决策依据。让工具承担计算,让模型承担判断。

6.2 空结果要给归因建议。(见军规 11)

6.3 大内容给句柄,不给全文。

❌ Observation: <20000 token 的完整日志> ✅ Observation: 匹配 187 条,已存入 handle=log://a3f1。前 5 条预览: t-11m order ConnectionPoolExhausted: timeout waiting for connection ... 调 read_range("log://a3f1", start, end) 可读取指定区间。

这同时解决了三个问题:上下文膨胀、注意力稀释、“模型以为自己看完了全部”。

6.4 让返回自带"下一步提示"。
在返回末尾加一行[下一步建议] 若需确认该变更的 diff,调用 read_repo_file(path, ref="v2.14.0")。这属于"引导"而非"控制",模型可以忽略它,但在它没主意时非常有效。⚠️ 注意别滥用:加太多会让返回变长,且可能诱导模型放弃自主判断。


七、怎么评测你的工具集:一个 60 行的评测脚本

ACI 改得好不好,不能靠感觉。下面这个脚本能测出工具选择准确率和参数质量,跑一次几分钟:

importjson,asyncio,collections# 评测用例:只测"给定上下文,模型该选哪个工具"CASES=[{"ctx":"订单服务 5xx 突增,需要确认是从哪一分钟开始涨的。","expect_action":"query_metrics","expect_args_keys":["service","metric"]},{"ctx":"已确认错误产生于网关层,现在要看网关在这一时段的错误日志。","expect_action":"search_logs","expect_args_keys":["service","since"]},{"ctx":"我怀疑是 v2.14.0 改了连接池配置,想看这个文件的这个版本。","expect_action":"read_repo_file","expect_args_keys":["path"]},# ... 建议 >= 50 条,覆盖每个工具的正例与易混淆反例]PROBE="""你有以下工具: {specs} 当前情况:{ctx} 请只输出一个 JSON 动作块,表示你的下一步: ```json {{"action": "...", "args": {{...}}}} ```"""asyncdefeval_aci(tools,llm,cases=CASES):specs=tools.render()hit=0arg_ok=0unparsable=0confuse=collections.Counter()forcincases:raw=awaitllm(PROBE.format(specs=specs,ctx=c["ctx"]))thought,call=parse_output(raw)# 复用第 3 篇的解析器ifcall.name=="__unparsable__":unparsable+=1continueifcall.name==c["expect_action"]:hit+=1else:confuse[(c["expect_action"],call.name)]+=1# 混淆对ifall(kincall.argsforkinc["expect_args_keys"]):arg_ok+=1n=len(cases)print(f"工具名准确率 :{hit/n:.1%}")print(f"必填参数完整率:{arg_ok/n:.1%}")print(f"格式非法率 :{unparsable/n:.1%}")print("\n最易混淆的工具对(期望 → 实际):")for(exp,got),kinconfuse.most_common(10):print(f"{exp}→{got}{k}次")return{"action_acc":hit/n,"arg_acc":arg_ok/n,"unparsable":unparsable/n}

怎么用这个结果:

指标健康阈值不达标时怎么办
工具名准确率≥ 95%(核心工具 ≥ 98%)看混淆对,给这两个工具加"何时不用";仍不行就合并它们
必填参数完整率≥ 95%参数描述里把必填参数的作用写清楚,或提供默认值
格式非法率≤ 2%换更强的模型,或改进解析层(第 3 篇)

那个混淆矩阵是本脚本最有价值的输出。它会精确告诉你哪两个工具在模型眼里长得一样。这是你改写描述的唯一依据。我见过最典型的混淆是search(语义检索)与lookup(精确查找),修法是合并成一个带mode参数的工具。

⚠️ 提醒:这个测的是单步工具选择,不等于任务成功率。它可以作为 p 的一个快速代理指标,但不能替代端到端评测(第 7 篇)。


八、案例:OpsAgent 的工具改造前后

改造前(典型的"API 文档式"定义):

- query_metrics(service, metric, window): 查询指标 - search_logs(service, since, limit): 搜索日志 - read_file(path, ref): 读文件 - run_cmd(cmd): 执行命令 - finish(answer): 结束

问题:query_metrics和search_logs边界不清;since没说时间格式;read_file没说路径基准;run_cmd完全开放(危险);没有任何"何时不用"。

改造后:

- query_metrics 用途:查询服务的监控时序(已降采样),用于判断指标的形态(突增/渐变/周期)与起始时间点。 何时不用:要查的是具体日志文本 → 用 search_logs;要看代码 → 用 read_repo_file。 参数:service string 必填;metric string 必填(枚举:http_5xx_rate|p99_latency|qps|error_count) window string 可选 默认 "30m",格式 "<数字><m|h|d>" 返回:降采样后的 "时间-数值" 序列(≤12 点)+ 一句形态判断(如"在 t-11m 突增")。 - search_logs (见第 2 节七要素模板) - read_repo_file 用途:读取代码仓库中某文件在某版本的内容。 参数:path string 必填,**必须是仓库根的绝对路径,以 / 开头**; ref string 可选 版本/tag/commit,默认当前主干 返回:文件内容(>800 行时截断并注明);不存在时返回 3 个最相近的候选路径。 - run_readonly_cmd 用途:在受限 shell 中执行**只读**命令(ls/git show/git log/git diff/cat)。 何时不用:任何会修改状态的操作(部署、回滚、写文件)——本工具会直接拒绝,请改为给出建议等人工执行。 参数:cmd string 必填 返回:stdout(截断至 2000 字符);非白名单命令返回拒绝原因与白名单。 - finish 用途:当你已有足够证据支撑结论时调用。 参数:answer string 必填 根因 + 证据链(每步引用 Observation)+ 建议动作

四处关键改动:

  1. metric改成枚举,消除"指标名乱编"。
  2. read_repo_file的路径约束 + 候选路径纠错,消灭"文件找不到"的反复试错。
  3. run_cmd→run_readonly_cmd+ 白名单,权限最小化从工具定义层面就完成了。
  4. 每个工具都有"何时不用",消除混淆。

这套改动,配合第 7 篇的评测,通常能把单步成功率往上推 5~10 个百分点。按第 1 篇的表,那是 20 步任务从 36% 到 67% 的差距。这是整个系列里最便宜的一次改进。


九、小结

这一篇的核心就一句:工具定义不是文档,是每一步都会被模型重读一遍的提示词,所以值得你投入跟 system prompt 同等的心力。

落到执行层面,最便宜也最确定的收益来自两件事。一是把七要素模板连同"何时不用"当铁律,先把工具边界写死;二是用那份评测脚本跑出混淆矩阵,盯住模型究竟在哪两个工具之间犯晕,再照着改描述。注意别凭猜测迭代——混淆矩阵是唯一可靠的依据。

下一篇聊上下文工程。长程任务里它是另一个容易被低估、但同样直接决定 p 的杠杆。

上一篇:03 手写生产级ReAct内核
下一篇:05 长程 Agent 的上下文工程:截断、摘要压缩与检索回放 - 需要明天上传笔记,今天的发布限额到了


参考

  • Anthropic,Building Effective Agents, 2024-12(ACI、poka-yoke、绝对路径案例、工具定义与提示工程同等投入)。
  • Yao et al.,ReAct: Synergizing Reasoning and Acting in Language Models, ICLR 2023, arXiv:2210.03629。
  • Liu et al.,Lost in the Middle: How Language Models Use Long Contexts, TACL 2024(上下文长度与注意力衰减)。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询