tool 设计反模式
昨天(Day 41)确立了 context engineering 的总命题:agent 的输入是有限 token 预算,工具描述是预算的主要消费者之一。
阶段: B5 · tool/context engineering + 指标树(Day 41-50) 标签: #tool-design #anti-patterns #action-space #error-contract
今日导引(由浅入深)
昨天(Day 41)确立了 context engineering 的总命题:agent 的输入是有限 token 预算,工具描述是预算的主要消费者之一。
今天顺着这条线往里钻一层——工具不只是 token 成本,它还是 agent 的动作空间:工具设计得好不好,直接决定模型选对/选错动作的概率。在 B1→B18 曲线上,这是从「度量输入」过渡到「度量动作面质量」的一步。
明天(Day 43)会把这些质量诉求抽象成指标树的叶子;Day 45 则会据今天标注的反模式真正动手重设计 v2 工具集。
今天的「最小可判定产出」是:一份逐工具的反模式标注清单 + 一个可离线跑通的 baseline harness(真实通过率需 key,标「待跑」)。
1. 机理精读
定义。 工具(tool)是 agent 能调用的外部动作的接口声明(name + description + 输入 schema + 返回契约)。
一组工具构成 agent 的动作空间;空间的形状(多大、多歧义、多易错)直接影响策略的错误率。坏工具设计抬高错误率,不是因为模型笨,而是因为动作空间本身充满陷阱。
五类坏味(参考 Anthropic《Writing effective tools for agents》2025-09)。
- 重叠工具(overlapping tools):两个工具职责交叉,模型面临「该选哪个」的选择困难,等价于在动作空间里制造了多个近义入口,徒增决策噪声。
- 模糊 description(vague description):描述没说清「何时用 / 何时不用」,模型只能猜调用时机——这是工具误用的头号来源。
- 胖参数(fat schema):
inputSchema过宽(自由 string、无 enum、无 required 约束),取值域太大,模型更容易填出非法或语义错误的参数。 - 隐式状态(implicit state):工具行为依赖外部不可见状态(如「上次调用设置过的某个开关」),破坏了无状态可推理性,让同样入参产生不同结果。
- 无错误契约(no error contract):失败时不返回结构化反馈,模型拿不到「为什么失败、下一步怎么办」的信号,无法自我恢复。
为什么这样分类。 这五类分别对应动作空间的五个维度:入口唯一性(重叠)、时机可判定性(模糊 description)、参数可约束性(胖参数)、状态可推理性(隐式状态)、失败可恢复性(错误契约)。
把坏味按维度切开,重设计时就能逐维施治(Day 45 的三原则正是对前三类的针对性修复)。
一个粗糙直觉:设单步选错概率为 p,一个任务平均 k 步工具调用,则全程不出错的概率约 (1-p)^k。重叠/模糊工具把单步 p 抬高,k 越长放大越狠——这解释了为何「动作面清晰」对多步 agent 比对单步 chat 重要得多(呼应 Day 39 的 pass^k 脆弱性)。
关键权衡:工具数量 vs 表达力。 工具拆得越细、约束越严,单工具越不易错,但工具数变多又抬高 context token 与选择面(回到 Day 41 的信噪比)。
所以反模式治理不是无脑加约束,而是在「动作面尽量小且清晰」与「能力覆盖足够」之间取平衡——这与 Day 40「能简单就别复杂」一脉相承。
与相邻概念的边界。 本仓的 toolRegistry.ts 实现的是 MCP「工具暴露给 LLM 的契约层」的进程内形状(schema 校验 + tools/list + tools/call),它是机制;今天讨论的反模式是这套机制上设计层的好坏判据。
机制保证「非法参数被 reject」,但拦不住「合法但歧义的工具让模型选错」——后者只能靠设计层(description 写负例、enum 收窄)解决。
2. 推导 / 手算 / 代码走读
代码日:对照 src/agent/mcp/toolRegistry.ts 标注反模式,并走读 baseline harness 的离线可验路径。
toolRegistry.ts 提供的「机制层防线」(这些已绿,是反模式治理的下界):
validate(schema, value):递归校验,支持enum(value not in enum)、minLength、minimum/maximum、required(缺字段报required property missing)。- 含义:给参数加 enum/required 就能在调用前挡掉非法值——Day 45 收窄 schema 的可执行性正来自这里。
register(spec, handler):重名抛already registered、非法 name 抛错、inputSchema.type !== 'object'抛错。- 含义:杜绝「静默覆盖」这种隐式状态来源(反模式 4 的机制层防线)。
call(name, args):先validate,失败抛McpCallError(INVALID_PARAMS, errs);handler 抛错包成INTERNAL_ERROR。- 含义:这是结构化错误契约的最小实现(带 code + message + data)。坏味 5「无错误契约」在机制层已被堵住,但工具 handler 自己若返回模糊错误文本仍会复发。
handle(req):JSON-RPC 2.0 dispatch,永不抛错、所有失败折成response.error——给模型稳定的失败信封。
baseline 一侧走读 scripts/run-agent-eval.ts + src/agent/eval/tasks.ts:
EVAL_TASKS共 29 条;与「工具/动作」直接相关的探针样本:planning-tool-selection:给两个工具getTokenPrice/getWalletBalance,问「查 0xABC 持有多少 ETH 调哪个、传什么参」,codeCheck 断言选 balance 工具 + 传地址——测「工具歧义」反模式;robustness-tool-failure:价格 API 返回 503,要求「重试/报故障且不得编造价格」——测「错误契约缺失」反模式。
scripts/run-agent-eval.ts的 key 行为:- 无 key 时是 dry-run:打印「Would run 29 tasks on …」并 exit 0;
- 有 key(默认 provider
deepseek,DEEPSEEK_API_KEY)时才真正调用runTaskEval并落 JSON 报告; - 所以「harness flow 本身可离线验证」字面属实——离线能跑通流程,只是不出真实通过数。
反模式标注表骨架(今天要填的产出形状):
| 工具 | 重叠 | 模糊desc | 胖参数 | 隐式状态 | 无错误契约 | 重设计动作(→D45) |
|------|------|---------|--------|---------|-----------|-----------------|
| … | ✓ | | ✓ | | | 与 X 合并 + 加 enum |
每命中一类坏味打一个 ✓,最后一列写 Day 45 的处置动作(合并/写负例/收窄)。命中「重叠」且与另一工具职责交叉的,配成对登记——它们是 Day 45 合并的输入。
3. 今日实战
- 逐个对照
src/agent/mcp/toolRegistry.ts当前注册的工具,在笔记/审计文件里给每个工具打上命中的坏味标签(重叠 / 模糊 description / 胖参数 / 隐式状态 / 无错误契约)。 - 重点标注 Day 45 要合并的重叠工具对,以及 description 缺「何时用/何时不用」的工具——为重设计列待办。
- 用 DeepSeek-V3(OpenRouter provider,已 wired)跑
src/agent/eval/tasks.ts中 tool-use 子集,经scripts/run-agent-eval.ts/pnpm eval:agent记录当前 baseline 通过数。 - 无 key 时先用离线 fixture 跑通 harness,产出 completion% 占位 + commit,证明 flow 可用。
- 把标注表里命中「重叠」的工具配成对、命中「胖参数」的标出待 enum 化的字段——直接作为 Day 45 重设计的输入清单(避免明天再回头体检一遍)。
4. 今日实测 / 产出
- 反模式标注清单可离线产出(纯设计层判断,无需 key)。
- baseline = N/5「待跑(需 OPENROUTER_API_KEY);可先用离线 fixture 跑通 harness 出 completion% + commit」。
- harness flow 本身可离线验证(无 key 时 dry-run,打印 Would run 29 tasks)。
(诚实状态:标注清单可立即产出;真实 baseline 通过数依赖模型 key,标「待跑」,未升级为已完成。)
5. 常见误区 / 陷阱
- 把 endpoint 1:1 映射成工具:每个 REST endpoint 包一个工具,制造大量重叠 + 胖参数,是最常见的旧 wrapper 思维(见 SOTA 黑名单)。
- description 只写"做什么"不写"何时用/何时不用":模型缺的是调用时机判据,不是功能说明。
- 以为机制层校验能救设计层歧义:
validate()能挡非法参数,挡不住「两个合法工具二选一选错」——歧义只能靠设计治。 - 拿 dry-run 的占位数当真实 baseline:无 key 时 harness 不调模型,completion% 是 fixture 占位,不能当作模型能力证据。
6. 学习资源(每条带 YYYY-MM)
- Anthropic, Writing effective tools for agents(2025-09)——五类工具设计原则与反模式的主线来源。
- Anthropic, Effective context engineering for AI agents(2025-09)——工具声明作为 context 预算主要消费者的上位视角(承接 Day 41)。
- Anthropic, Building Effective Agents(2024-12)——「能简单就别复杂」,约束动作空间的总原则。
- 本仓代码:
src/agent/mcp/toolRegistry.ts(validate/call/ 错误契约)、src/agent/eval/tasks.ts(planning-tool-selection、robustness-tool-failure)、scripts/run-agent-eval.ts(dry-run / 真跑)。 - MCP specification(2026-07-28 定稿,RC 2026-05-21)——stateless + 零信任 schema 校验,与「结构化错误契约」方向一致的规范背景。
SOTA检查 (2026-06 更新)
- 当前主流方案:Anthropic tool-writing 指南(2025-09)仍 current;2026 共识是「少而清晰的工具 + 显式错误契约 + 收窄 schema」。
- 是否仍 SOTA:是。MCP 2026-07-28 规范也强调 stateless 与零信任校验,与「结构化错误契约」方向一致。
- 过时黑名单(AVOID):把每个 API endpoint 1:1 映射成工具的旧 wrapper 思路(制造重叠 + 胖参数);description 只写功能不写时机;handler 返回纯文本错误而非结构化反馈。
- 下次复查点:OpenRouter 上 DeepSeek-V3 的模型 id 当周重验(id/价格会变,影响 baseline 复现);关注 MCP 规范对工具描述/错误契约是否新增约束。
衔接
- 昨天:Day 41 — context engineering 范式(工具声明是 token 预算的主要消费者)。
- 今天:工具是 agent 的动作空间,五类反模式(重叠/模糊/胖参数/隐式状态/无错误契约)抬高错误率。
- 明天:Day 43 — 指标树与 North Star(把工具/动作质量诉求抽象成可测叶子指标)。