第 10 章 AI AgentAgentic Patterns工具设计

第 10 章 工具接口哲学:让 Agent 能用、好用、用得起

工具的脾气,就是 Agent 的天花板

第二部分我们把上下文管明白了。从这一章开始,进入第三部分"工具与环境",让 Agent 真正"动手做事"。

动手靠工具。而一个残酷的事实是:你给 Agent 什么工具,它就有什么能力;工具接口设计得别扭,Agent 再聪明也白搭。 人用 API 靠读文档、靠经验、靠 IDE 的自动补全,模型用 API 全靠接口本身"长什么样"。一个为人类设计的、层层嵌套的接口,对模型来说就是一场灾难:它要么报错,要么胡说。

这一章讲三个模式,回答"怎么设计工具,Agent 才好用":

  1. LLM-Friendly API Design(LLM 友好的 API 设计):把接口设计成"模型一看就懂"。
  2. Code-First Tool Interface(代码优先的工具接口):与其让模型一步步调用工具,不如让它写代码来编排工具。
  3. Code-Over-API(代码优于直接调用):数据处理别走上下文,在沙箱里用代码搞定。

模式一:LLM 友好的 API 设计(LLM-Friendly API Design)

问题

接口设计通常只考虑人类:版本号藏在 Release Notes 里、函数参数靠 IDE 提示、错误信息写给开发人员看。这些设计对人没问题,但对模型就是三座大山:

  • 版本不透明:模型调用 API 时不知道自己在用哪个版本,按旧版参数调用新版接口,直接失败。
  • 间接层太深:代码库七拐八绕,一个函数要穿过三四层抽象才找到真正的实现,模型很难推理"这个接口到底是干嘛的"。
  • 错误信息没用:报错只说"调用失败",不告诉模型错在哪、怎么改,模型无从自我纠正。

一个直觉验证:ReAct 这类框架(Yao 等人,ICLR 2023)的核心就是把"推理"和"动作"交替起来,让模型边想边调工具。可一旦工具接口本身让人困惑,模型的每一步推理都建立在错误的理解上,想得越多错得越远。

方案

在设计和改造接口时,把"模型会怎么读这个接口"当成一等公民。 具体动作:

  • 显式版本:把版本号做成模型"看得见"的东西。比如在函数名、端点路径、参数 schema 里直接体现(create_vpc_v2/api/v2/vpcs),而不是只在文档里提一句。
  • 自描述功能:函数名、参数名、类型 schema(JSON Schema / OpenAPI)、文档都要能直接说明"这个 API 干什么、怎么用"。模型没有 IDE,它读的就是这些名字和 schema。
  • 简化交互:能用一次调用解决的,别拆成三步;能扁平就别嵌套,减少模型出错的机会。
  • 清晰的错误信息:错误响应要"可行动":告诉模型哪里错了、参数应该是什么、下一步怎么办。
  • 减少间接层:把代码和库的结构压到两层间接以内,而不是 n 层,让模型能推理代码库。
# 面向人的设计(模型容易翻车)
POST /resources
body: { type: "compute", spec: { size: "large", image: 42, net: { sg: "web", vpc: "main" } } }
# 出错时只返回: {"error": "bad request"}

# 面向模型的设计(看一眼就懂)
POST /v2/compute_instances
body: {
  "instance_type": "m4.large",        # 参数名直白,带版本
  "image_id": "ami-0421",             # 类型清晰
  "security_group_id": "sg-123",      # 扁平,不嵌套
  "vpc_id": "vpc-456"
}
# 出错时返回:
{"error": {"code": "INVALID_INSTANCE_TYPE",
           "detail": "m4.large 不存在,可选: m4.xlarge / m5.large / t3.medium",
           "hint": "把 instance_type 改成其中一个有效值"}}

证据

  • 来自 Cursor 的实践(Lukas Möller 的分享,emerging):API 设计已经在朝"让模型更舒服"的方向调整,比如把版本号做得对模型可见、把代码结构压到两层间接。
  • Gorilla(Berkeley,2023)证明:微调模型可以学会"按文档调用 API",但前提是接口文档得够好,它才会调用得够准。
  • MCP(Model Context Protocol)本身就是在做这件事:用标准化的工具 schema 让模型理解工具。

怎么用

  • 当"Agent 能不能可靠调用工具"决定成败时,先审视接口设计。
  • 窄工具面开始:先暴露少量、参数校验严格的工具,别一上来铺一大堆。
  • 给工具调用加可观测性:记录延迟、失败、回退路径,你才知道哪个接口在坑模型。
  • 错误信息当成"给模型写的调试信息"来设计,这是投入产出比最高的动作。

取舍

  • 好处:工具调用成功率明显上升,失败率下降。
  • 代价:接口设计要花心思,会和"人用的老接口"产生集成耦合;要为不同环境维护不同的接口形态。

模式二:代码优先的工具接口(Code-First Tool Interface / Code Mode)

问题

上一节的思路是"把接口改得让模型好调用"。但就算接口再友好,让模型一个工具一个工具地调用,本身就有硬伤

  • Token 浪费:每次工具调用,中间结果都要回灌进模型上下文。经典 MCP 模式是这样的:
LLM → 工具#1 → 大 JSON 响应 → LLM 上下文
LLM → 工具#2 → 大 JSON 响应 → LLM 上下文
LLM → 工具#3 → 大 JSON 响应 → LLM 上下文
→ 最终答案
  • Fan-out 爆炸:批量场景直接崩。处理 100 封邮件做个性化外联:传统 MCP 要 100 次独立工具调用,每封邮件的元数据可能上千 token,还没开始干活上下文就 10 万+ token,直接溢出。

方案

与其让模型调用工具,不如让模型写代码来编排工具。 这个思路叫 Code Mode:加一层"临时执行层"(ephemeral execution layer),模型生成代码,代码在沙箱里一次性编排多次工具调用,只有最终结果回流到上下文。

分工是这样的:

  • MCP Server 管持久层:凭据、鉴权、限流、配额、Webhook、长连接、安全策略。这些"脏活"留在服务器。
  • Code Mode 管临时层:多步编排、数据转换、业务逻辑。一次执行搞定,中间结果不进上下文。

架构骨架:

  1. Schema 发现:运行时动态拉取 MCP server 的工具 schema。
  2. API 转换:把 MCP 工具 schema 转成带类型和文档注释的 TypeScript 接口。
  3. LLM 感知:模型拿到完整的 TypeScript API 文档,知道有哪些工具可用。
  4. 临时代码生成:模型生成一段代码,在一次脚本里编排多次工具调用。
  5. V8 Isolate 执行:轻量、安全的沙箱,执行完即销毁,无持久状态。
  6. 受控绑定:安全的桥接,真正持有凭据和逻辑的仍然是 MCP server。
# 传统 MCP:一次调用一次回灌
rows = tool_call("spreadsheet.getRows", sheet="abc")
# 1000+ token 塞进上下文
for row in rows:
    result = tool_call("sendEmail", to=row.email, body=row.text)
    # 又是 1000+ token 回灌

# Code Mode:写一段代码,沙箱里跑完
def 批量外联(contacts):
    vpc = create_vpc(name="demo")                    # 第一次工具调用
    igw = create_internet_gateway(vpc.id)            # 第二次
    sg  = create_security_group(vpc.id, ssh=True)    # 第三次
    inst = launch_ec2(type="m4.large", vpc=vpc.id)   # 第四次
    tag_resources([vpc, igw, sg, inst])              # 第五次
    return {"instanceId": inst.id, "ssh": inst.ssh}  # 只有这一个结果回流

注意关键点:模型知道该写什么代码,不是因为它猜,而是因为它拿到了完整的、强类型的接口文档。schema 从 MCP server 动态生成,接口是"喂"给它的,不是它编的。

什么时候该用 Code Mode

  • 流程型问题:步骤能提前画出来。比如基础设施开通、数据管道编排、批量操作。
  • Fan-out 场景:对 100+ 条数据跑 for 循环,比让模型连调 100 次工具强得多;N 越大,传统方式越慢,Code Mode 反而更快。
  • 自调试:生成的代码自带错误处理、日志、重试逻辑(CaMeL 风格的"Agent 自己改自己作业")。

反模式(别用):

  • 开放式研究循环:每一步都要决定"下一步干嘛"的问题,Code Mode 很难提前写死。
  • 执行中途需要智能:比如给 100 个联系人写"个性化"邮件,正文必须靠模型在循环里逐条生成。这又退回传统 Agent 模式,Code Mode 的省 token 优势全没了。
  • 高度动态的工作流:步骤顺序严重依赖中间结果的不可预测变化时,传统 MCP 的逐步方式更合适。

证据

  • Cloudflare Code Mode(2025,established):核心数据是 Anthropic 报告的在 1 万行电子表格上 token 从 150K 降到 2K(75 倍)。
  • Anthropic 工程团队的 Code-Over-API 分析(详见模式三)。
  • CaMeL(Beurer-Kellner 等人,2025):代码优先的工具使用还能做形式化验证和污点分析(安全场景的静态检查)。

取舍

  • 好处:多步工作流 token 省 10-100 倍;批量场景又快又稳;凭据留在 MCP server,不进模型上下文,更安全。
  • 代价:要搭 V8 isolate 沙箱基础设施;执行成败依赖模型的代码生成能力;动态研究场景和"执行中途要智能"的场景不适用;运行时错误要单独处理。

模式三:代码优于直接调用(Code-Over-API)

问题

当 Agent 直接调用 API 或工具时,所有中间数据都必须流经模型的上下文窗口。处理电子表格、过滤日志、变换数据集这类"数据重"的工作流,代价高得吓人:拉 1 万行电子表格再过滤,光搬数据就能吃掉 15 万+ token,又慢又贵。

方案

别直接调工具,写代码让代码去调工具。 数据处理、过滤、变换都在执行环境里完成,只有结果回流到模型上下文。

核心洞察和模式二同源:模型"写代码去调用 API"比"直接调用 API"更擅长。因为训练数据里有海量开源代码,模型见过几百万种"用代码调接口"的写法。

# 直接调 API:1 万行数据全进上下文 → 15 万 token
rows = api_call("spreadsheet.getRows", sheet_id="abc123")
filtered = [r for r in rows if r.status == "active"]   # 还要再花 token 处理
return filtered

# Code-Over-API:数据处理在沙箱里,只有摘要回流 → 约 2K token
def process_spreadsheet():
    rows = spreadsheet.getRows(sheet_id="abc123")      # 工具调用发生在执行环境
    filtered = [r for r in rows if r.status == "active"]  # 过滤在代码里,不进上下文
    print(f"总 {len(rows)} 行,活跃 {len(filtered)} 行")   # 只给模型看摘要
    print(f"前 5 条活跃行: {filtered[:5]}")
    return filtered

模型看到的是日志输出和返回值,完整数据集永远不会进入它的上下文。

怎么用

  • 最适合:数据重的工作流(电子表格、数据库、日志)、多步变换或聚合、中间结果不需要模型逐条检查的场景、对 token 成本敏感的应用。
  • 前置条件:安全沙箱执行环境;执行环境能访问工具/API;要有资源限制(CPU、内存、时间)防止失控执行。
  • 实施步骤:模型分析任务 → 写代码(在环境内调工具 + 过滤变换聚合 + 只打印摘要/样本 + 返回最终结果)→ 沙箱执行 → 只有日志和返回值回流。

执行环境有几种选择:

  • V8 Isolate:毫秒级启动、内存极小、隔离强(Cloudflare Code Mode 的选择)。
  • 容器:2-5 秒启动、语言灵活(Modal、Docker)。
  • 虚拟机:完全隔离,适合破坏性操作(Cognition/Devon 这类)。

取舍

  • 好处:token 大幅下降(报告案例 150K → 2K);延迟更低(大上下文调用少了);数据处理的天然契合;中间数据被关在执行环境里。
  • 代价:要建安全执行基础设施;比直接调用工具复杂;模型得能写出正确的代码;调试变难(错误发生在执行环境,不在上下文里);需要监控、资源限制和沙箱。

三个模式怎么选

场景 推荐模式
现有接口让模型频繁翻车,报错看不懂 LLM 友好 API 设计
多步编排、批量处理,想省 token Code-First / Code Mode
数据处理重,中间结果不需要模型看 Code-Over-API

三个模式是层层递进的关系:LLM 友好 API 是把"单次调用"做对,Code Mode 是把"多步编排"做对,Code-Over-API 是把"批量数据处理"做对。底层接口友好是地基,上面的编排和数据处理才立得住。

还有一个贯穿的判断标准:如果中间结果对模型没有价值,就别让它流经上下文。 这条标准和第二部分的"上下文经济"完全呼应。工具设计不只是"让 Agent 能用",更是"让 Agent 用得起"。

实践清单

  • 接口显式带版本(函数名/路径/schema 里体现),别让模型猜版本
  • 函数名、参数名、schema、文档做到"自描述"
  • 交互扁平化:能一次调用就别拆三步,能扁平就别嵌套
  • 错误信息写给模型看:哪里错、参数该是什么、下一步怎么办
  • 代码库间接层压到两层以内
  • 多步编排场景,考虑让模型写代码而不是逐步调工具
  • 数据处理在沙箱里做,只把摘要和结果回流上下文
  • 先上窄工具面 + 严格参数校验,再慢慢加
  • 给工具调用加可观测性:延迟、失败率、回退路径

本章小结

  • LLM 友好 API:显式版本、自描述、扁平化、可行动的错误信息、两层间接,让模型一看就懂。
  • Code-First 工具接口:模型写代码编排工具,多步流程一次执行,省 10-100 倍 token。
  • Code-Over-API:数据处理在沙箱完成,只有摘要回流,150K → 2K token。
  • 判断标准:中间结果对模型没价值,就别让它进上下文。
  • 底层接口友好是地基,编排和数据处理建立在它之上。

下一章讲工具发现:接口设计好了,模型还得"找得到"工具。渐进发现、懒加载、静态服务清单、统一工具网关。

📑 Agent 模式实战:生产级 AI Agent 的工程模式

1 第 1 章 什么是 Agent 模式 2 第 2 章 规划-执行-观察:先想清楚,再动手 3 第 3 章 反思闭环:让 Agent 学会检查自己的作业 4 第 4 章 委派:让主 Agent 学会把活分出去 5 第 5 章 上下文预算治理:把 token 当成钱来管 6 第 6 章 上下文压缩与精选:装不下怎么办 7 第 7 章 上下文最小化:别让脏东西留在脑子里 8 第 8 章 记忆体系:让 Agent 记得住过去 9 第 9 章 学习沉淀:让 Agent 和团队一起变聪明 10 第 10 章 工具接口哲学:让 Agent 能用、好用、用得起 11 第 11 章 工具发现:让 Agent 在几百个工具里找到对的 12 第 12 章 执行环境:Agent 在哪动手、怎么动手 13 第 13 章 代码执行与沙箱:先写码,再跑码 14 第 14 章 结构化输出与契约:让 Agent 的输出能接住 15 第 15 章 验证循环:Agent 怎么检查自己的作业 16 第 16 章 评测基建:怎么系统地检验 Agent 17 第 17 章 可观测性:看见 Agent 在想什么、在干嘛 18 第 18 章 韧性工程:扛得住部分失效 19 第 19 章 威胁模型:先看风险长什么样,再谈防御 20 第 20 章 控制流隔离:把"谁做决定"和"谁执行"分开 21 第 21 章 权限与审批:谁有权干什么、谁点头 22 第 22 章 凭据与出口:Agent 手里的钥匙和门 23 第 23 章 多智能体信任:多个 Agent 之间怎么互信、怎么审计 24 第 24 章 反馈信号设计:给 Agent 的是信号,不是更大的提示词 25 第 25 章 评测驱动的改进:让 Agent 在真实使用和对抗测试里变强 26 第 26 章 强化学习:把反馈变成训练信号 27 第 27 章 复合式进化:让 Agent 系统越用越值钱 28 第 28 章 多智能体协调:让一群 Agent 一起干活不掉链子 29 第 29 章 模型路由:谁用哪个模型,怎么用得起 30 第 30 章 推理搜索结构:让 Agent 多想想,而不是一条道走到黑 31 第 31 章 控制谱系:从自动补全到完全自主的滑动条 32 第 32 章 团队与产品:把 Agent 变成团队资产,而不是个人玩具
← 返回本书大纲