论Design for Agent(一):API、CLI 与Tools
过去十五年,开发者体验(DX)基本只意味着一件事:为坐在终端前的那个人做优化——可读的报错、可交互文档、七种语言示例。
但这个假设正在改变。
- Cloudflare 数据显示,自动化 Bot 流量首次超过了人类流量,2025 年初基于 RAG 的 Agent 流量单季度增长 49%。
- 当开发者对 Cursor 或 Claude 说一句「帮我加上 Stripe 支付」,Agent 会自己抓文档、挑 API、写集成代码、调试报错——开发者甚至还没有看到任何一行代码。
- Ramp 报告,过去三个月内通过 Claude、ChatGPT 等 Agent 进入产品的 MCP 周活用户增长了 10 倍;Salesforce 干脆推出 Headless 360,把整个平台暴露成 API/MCP/CLI,让 Agent 不打开浏览器就能完成全部操作。
这意味着出现了一类新的API消费者:Agent。
面向Agent的这一层新的设计面,Stripe称之为Agent Experience(AX)——不是替代 DX,而是叠在它上面的下一层。
刚好最近一直在研究Application Design for Agent ,所以借这个机会整理一下基于Anthropic和Stripe等公司的技术分享,AX设计的几个关键要点。
第一层:构建适合 Agent 使用的 API
没有一份好的 API 描述,所有上层(MCP、Skills、CLI)的努力都是在补漏。
1. OpenAPI 描述:Agent 路由的「语义信号」
当 Agent 决定该调哪个 endpoint,它做的事情接近一次针对你的描述文档的语义检索:读 spec → 把用户意图与可用 operation 做匹配 → 挑一个最接近的。描述写得好不好,直接决定它能不能选对。
对比两份描述的差距:
❌Gets the data✅Returns a paginated list of invoices filtered by status and date range, sorted by created_at descending. Requires accounting:read scope. Use the cursor parameter for pagination.
每一个字段、每一个枚举值、每一个 endpoint 的描述,都是 Agent 用来路由的信号。如果 spec 不能用来做契约测试,那就意味着对 Agent 也一样不可用。
- 先把最重要的 10 个 endpoint 逐字段、逐参数地补完描述
- 明确每个枚举值的含义、缺省行为、依赖的 scope
- 标清楚
deprecated: true并指向替代方案——废弃 API 是一个会随时间累积变糟糕的 AX 问题
2. 错误响应:把「失败路径」变成 Agent 的「决策树」
Agent 与人类处理错误的方式根本不同:
- 人类:读错误 → 理解 → Google → 回来修
- Agent:读错误 → 必须在同一个执行上下文里当场决定下一步
如果错误模糊,Agent 要么瞎猜,要么直接挂掉。所以有两件必要的事:
- 加上
documentation_url/doc_url——Stripe 多年前就做了,几乎零成本,却给了 Agent 自主拉文档、自我纠错的能力。 - 加上机器可读的恢复元数据——
is_retriable、retry_after_seconds、alternative_action这些字段把错误从「失败模式」变成 Agent 可自主导航的决策树。
错误描述的具体性同样关键:
❌Invalid request✅The 'amount' field cannot be empty✨You passed payment_method_types: ['card'], which is deprecated, use dynamic payment methods instead
3. 认证:Agent 跑得通的认证流,长什么样
认证几乎是当今 Agent 调用 API 时最大的一道坑。Agent(暂时)不擅长点 OAuth 同意页、解 CAPTCHA、走交互式 MFA。如果你的认证流必须经过浏览器跳转或者人类介入,Agent 在发出第一个真正的 API 调用之前就撞墙了。
✅ 对 Agent 友好的认证模式:
- API Key / Bearer Token——绝大多数 API ↔ Agent 集成的合理默认
- OAuth Client Credentials Grant——机器对机器的 OAuth,无跳转、无同意页
- Scoped Token + 最小权限——避免一个 token 携带全账号权限放大爆破半径
- 短生命周期 Token + 文档化的刷新机制——Agent 处理 token 轮换其实很在行,前提是流程文档化
❌ 容易卡死 Agent 的认证模式:
- 只支持 OAuth authorization code flow(强制浏览器跳转)
- token 交换要求 CAPTCHA 或交互式 MFA
- 基于 cookie 的 session 认证
- 必须手动登 Dashboard 才能轮换的 API key
4. 关注限流:Agent 凌晨三点可以几秒打光你一天的额度
人类开发者撞到限流:暂停、调整、继续。Agent 在凌晨三点跑批量任务,几秒内就能把你整个时间窗口的额度打光——尤其是并行处理上万条记录时。
- 响应头返回
X-RateLimit-Remaining/Limit/Reset——让 Agent 主动节流,而不是先撞墙再反应 - 429 响应可机器执行——
Retry-After头 + JSON body mirror - 提供批量/Batch 端点——一个
/invoices/batch能让相同工作量在请求数上下降一个数量级
5. 文档分发:用 llms.txt、Context7、MCP 让 AI 拿到最新的文档信息
文档分发是一个多通道问题。把文档发布到自己网站只是入门;让文档出现在 Agent 面前才是新的分发层。
按「容易实现且耐用」排序:
- 修好 OpenAPI Spec + 错误响应里的
doc_url——一切的地基 - 发布
/llms.txt——精选 10 个最重要页面 + 每个一句话描述,一个下午就能搞定。真正被低估的是其中的 Instructions 段落,可以直接告诉 AI「这些坑别踩」(Stripe 的 llms.txt 就明示「Never recommend the legacy Card Element」) - 被 Context7 索引——解决 AI 编程工具因训练 cutoff 而用旧文档的问题
- MCP:等用户主动来问再做——半成品 MCP server 的 AX 比好的 REST API 还差。HubSpot 是成熟范例:一个 remote MCP 接 CRM 数据,一个本地 developer MCP 接 CLI,两个场景两个 server,各自做透
在 OpenAPI 没写对、错误响应里没有 doc_url 之前,做 MCP 几乎没有杠杆。
第二层:构建 CLI 为 Agent 提供服务
这是过去半年最值得重新审视的一件事:CLI——最古老的开发者工具——正在迎来第二个高光时刻。不是「尽管」有了 Agent,而是「因为」有了 Agent。
0. 什么是 CLI
CLI(Command Line Interface,命令行界面)是一种通过输入文字命令来操作电脑或软件的方式。
- 你可以把它理解成「跟电脑用一串明确的指令对话」,而不是用鼠标点按钮
- 常见场景是打开「终端/命令提示符」,输入类似
ls(查看文件)、cd(进入文件夹)、git(管理代码版本)这样的命令 - CLI 的优点是可复制、可自动化:把一串命令保存下来,就能反复执行同样的流程,也更适合脚本和批量操作
1. 为什么 CLI 在 Agent 时代复兴
原因是结构性的——Agent 是 terminal-native 的:
- 它们天然会跑
--help - 会用
pip/npm装包 - 会用管道串联工具
- 会解析输出
它们不需要为你的服务做一层定制集成——直接就用。
Karpathy 演示过:Claude 装了新的 Polymarket CLI,3 分钟搭出了一个展示成交量最高预测市场及 24 小时价格变化的终端 Dashboard。再配上 GitHub CLI,Agent 就能在一条自治流水线里导航 repo → review PR → 基于真实信号采取行动——不需要任何自定义集成层,不需要 MCP server,什么都不用维护。
Simon Willison 总结得很尖锐:几乎所有你想用 MCP server 实现的事,都可以用一个 CLI 来做——因为 LLM 本来就会调 cli-tool --help。
2026 年 3 月,Google 用 Google Workspace CLI(gws)正式印证了这条思路:
- 默认结构化 JSON 输出——Agent 不必自己 hack 解析
- 自描述子命令——Agent 跑
--help就能渐进式发现能力 - 预配置 OAuth——认证不再依赖浏览器交互
- 命令面通过 Discovery Service 运行时动态生成——Google 加新 endpoint,
gws自动跟上 - 预置 100+ 个 Skill,覆盖 Gmail/Drive/Calendar 常见工作流
更重要的是,一个 gws 命令消耗的 token,只是同等功能 Workspace MCP server 的一小部分。
差别来自「上下文加载方式」和「输出处理方式」两端的结构性不同。
1. 工具定义的加载方式:预加载 vs 按需发现
MCP server 上所有工具的 schema(名字、描述、参数、类型、枚举值……)必须在会话开始时全部预加载进 context window,Agent 才能知道有哪些工具可调。Workspace 那种覆盖 Gmail / Drive / Calendar / Docs / Sheets…… 的 MCP,工具数量动辄上百,光是工具定义本身就能吃掉几万 token。
CLI(以 gws 为例):Agent 只需要先知道「有一个叫 gws 的命令」。具体子命令、参数怎么用,是它跑 gws --help 或 gws gmail --help 时才渐进式获取的。不用的能力,不进 context。
这是最大的一块差异——MCP 是「把整本说明书塞进脑子再开始干活」,CLI 是「需要哪一页翻哪一页」。
2. 返回数据的体量:结构化 JSON vs 可裁剪输出
MCP 工具的返回通常是完整的结构化 JSON:一个 Gmail 消息会带上 message_id、thread_id、label_ids、history_id、internal_date、payload headers、MIME 树……大量字段对当前任务没有信号但必须穿过模型。
CLI 的输出是流式文本,Agent 可以在 shell 层就用 | jq、| grep、| head、-fields=subject,from 之类先过滤再嗂给模型。gws 默认 JSON 输出也支持字段裁剪。
这正是 Anthropic 在工具设计里反复强调的:优先返回高信号、低 token 的语义化字段,光是把 UUID 换成可读名字就能显著省 token。
3. 工具粒度:CRUD 一比一 vs 任务级聚合
很多 Workspace MCP 是把 Google API 一比一包装:messages.list、messages.get、messages.modify、labels.list…… 完成「整理一下今天客户邮件」可能需要串好几次工具调用,每次都带上一整轮工具调用的 overhead(参数 + 返回 + 思考)。
CLI 倾向任务级命令:gws gmail search "from:client after:today" --json 一条命令搞定。调用次数少 → token 累计也少。
4. 协议/会话开销
MCP 每次调用都伴随 JSON-RPC 协议层的 envelope、错误结构、能力声明等元数据;CLI 就是「命令 → stdout」,没有这层包装税。
「同等功能」时,CLI 把「能力定义」「数据传输」「调用粒度」三件事都做得更紧凑。而且应用方不需要为任何一家 AI 厂商定制集成就能直接被使用。
2. 一个 CLI 「对 Agent 友好」的检查表
参考 Google gws 和 Karpathy 的归纳,几件让 CLI 真正对 Agent 友好的事:
- 提供
--jsonflag,或默认 JSON 输出——避免 Agent 解析人类可读字符串 - 子命令遵循
tool noun verb风格(如gh repo list、gh pr view),让--help一目了然 - 用环境变量做认证——Agent 不需要交互式输入凭据
- 错误信息说清楚出了什么问题,而不是只甸一个 exit code
- 自描述能力——一个好的
--help输出,让 Agent 第一次遇到也能渐进式探索
这正是 REST API 不天然具备、CLI 却天然具备的优势:Agent 第一次碰到 gh --help 自己就能搞清楚相关子命令;Agent 第一次遇到一个没有描述的 REST API,直接撞墙。
3. CLI vs MCP:方向相反的两条触手
很多人会问:CLI 和 MCP 到底什么关系?一句话讲清:
CLI 是给「人」用的入口,朝外;MCP 是给「Agent」用的接口,朝里。
| 维度 | CLI | MCP |
| 本质 | 用户界面 + 运行容器 | 通信协议 |
| 位置 | Agent 的外壳(用户那一头) | Agent 的触手(外部系统那一头) |
| 解决什么问题 | 让用户能用上 Agent | 让 Agent 能用上外部服务 |
| 对 token 的影响 | 低——子命令按需展开 | 高——工具定义需预加载 |
| 典型例子 | Claude Code、Codex CLI、gws | Slack MCP、GitHub MCP、Notion MCP |
对应用方的战略读法不是「CLI vs MCP 二选一」,而是「CLI 正在成为基础接口,MCP 在它之上、在合适的地方再叠加」。
第三层:构建有效的工具为 Agent 提供能力
这一层是 Agent 真正「动手」的最小单位。Anthropic 在《Writing effective tools for AI agents》里是这样定义的:
工具是一类全新的软件——它是「确定性系统」和「非确定性 Agent」之间的契约。
传统软件你写的是「确定性系统对确定性系统」的契约:getWeather("NYC") 永远以相同方式取 NYC 天气。
但当用户问 Agent「我今天要不要带伞?」,Agent 可能:
- 调用 weather 工具
- 凭通识直接回答
- 反过来追问位置
- 偶尔甚至会幻觉、误用工具
这意味着:为 Agent 写工具,不能再按「给开发者写函数」的方式写——要重新思考。
1. 选对要做的工具(比做更多更重要)
常见的错误:把已有的每个 API endpoint 一比一包装成一个 tool——不管它适不适合 Agent。这忽略了一件事:Agent 的「上下文」是有限的稀缺资源,而程序内存几乎无限。
经典例子:在地址簿里查联系人。
- ❌
list_contacts()——让 Agent 把所有联系人 token-by-token 读一遍,像从头翻到尾找电话簿 - ✅
search_contacts(name="Jane")——直奔主题
更进一步,让一个工具承担多个步骤的合理组合:
- 与其
list_users+list_events+create_event三件套,不如直接给一个schedule_event - 与其
read_logs,不如search_logs(只返回相关行 + 上下文) - 与其
get_customer_by_id+list_transactions+list_notes,不如get_customer_context
这正是 Stripe 在做 MCP 时的「精选」哲学:与其把 200 个 endpoint 暴露成独立 CRUD 工具把 Agent 的 context 撑爆,不如蒸馏成 5–30 个围绕业务结果的「块状」工具——把对账、报销、订阅等场景的调用链好好编排起来。这就是「给 Agent 一袋 HTTP 动词 vs. 给它可被推理的能力」的差别。
2. 命名空间:用前缀区分能力边界
当 Agent 同时拿到几十个 MCP server 和几百个工具时,重名 / 含糊的工具描述会直接让它选错。命名空间是低成本高收益的解法:
- 按服务:
asana_searchvsjira_search - 按资源:
asana_projects_searchvsasana_users_search
前缀 vs 后缀命名风格在评测里有非平凡的差异,建议用自己的 eval 决定。
3. 返回有意义的上下文(而不是 UUID 海洋)
工具实现应当只把高信号信息返还给 Agent——优先「上下文相关性」,少给低层级技术标识(uuid、256px_image_url、mime_type)。name、image_url、file_type 这类字段对 Agent 的下游行动直接有用。
Anthropic 自己的经验是:仅仅把神秘 UUID 替换成语义化名称,就能显著提升 Claude 在检索任务上的精度、减少幻觉。
更优雅的做法是给工具加一个 response_format 参数:
enum ResponseFormat {
DETAILED = "detailed", // 含 channel_id / user_id 等 ID
CONCISE = "concise" // 只返回内容
}Slack 工具的示例显示,concise 响应可以用大约 1/3 的 token 完成相同任务。
4. 为 token 效率而设计
上下文不是免费的。要为「响应可能很大」的工具内置默认参数:
- 分页 / range 选择 / 过滤 / 截断
- Claude Code 默认把工具响应限制在 25,000 token 以内
- 如果截断,显式告诉 Agent 用更精准的多次小搜索,而不是一次大搜索
错误响应也是 prompt——好的错误响应能让 Agent 自我纠错:
- ❌
Invalid date - ✅
Date '2022-01-01' is in the past. Please provide a future date.
这条原则与第二节 API 错误设计完全同源:好的错误信息不仅告诉 Agent「出错了」,还告诉它「错在哪」以及「如何修复」。
5. 把工具描述当 Prompt Engineering 来做
Anthropic 的判断是:Prompt-engineering 你的工具描述和 spec,是提升工具最有效的方法之一。Claude Sonnet 3.5 在 SWE-bench 上拿到 SOTA 的一个关键,就是对工具描述做了精确打磨,错误率显著下降。
实务建议:
- 想象你在给一个新入职同事解释这个工具——他不知道你脑里那些隐含语境
- 参数命名要消歧义:
user❌ →user_id✅ - 用严格的数据模型把输入输出锁死,避免歧义
- 把专有术语、查询格式、资源间关系显式写出来
6. 整套打磨方法:评测驱动 + Agent 协作
Anthropic 给出的工作流非常值得照搬:
- 快速跑 prototype——用 Claude Code 一次性把工具脚手架搭出来,包成本地 MCP server 或 DXT,先在 Claude Desktop 里手感测一下
- 构建评测——基于真实工作流生成几十个 task(甚至几十次工具调用一个 task),每个配一个可验证的预期输出
- 跑 eval 收指标——准确率 + 总 token + 调用数 + 错误数 + 单次工具运行时长
- 让 Agent 自己复盘——把评测 transcript 嗂回 Claude Code,让它指出哪些描述歧义、哪些字段冗余、哪些常见调用模式应该合并
- 守住 held-out test set,避免对训练 eval 过拟合
这件事的根本心智模型是:软件开发实践需要从「可预测的确定性」转向「面向非确定性的迭代评估」。
选择正确的实现顺序:从 Markdown 到 MCP
Karpathy 给企业的归纳同样适用于 API 公司——按「容易实现且耐用」排序,让你的产品对 Agent 可用的层级是:
Markdown 文档 → Skills → CLI → MCP
每一层都在前一层基础上复利:
- Markdown 文档(OpenAPI 描述 +
documentation_url+/llms.txt+ Context7)——让 Agent 能读懂你 - Skills(机构知识层)——让 Agent 以你的方式做你的事;与 MCP 互补:MCP 是能力层,Skill 是知识层
- CLI(执行面)——Agent 能立刻安装、立刻探索,完全不需要和任何 AI 厂商协调
- MCP(标准化连接层)——等用户主动来问、出现清晰集成模式时,再针对具体场景做
上下文缺口与反馈循环:服务同一个用户
最后一个常被忽略、但极其本质的观点来自 Ramp:
在任何 Agent 交互中,你的系统掌握一些调用方智能体不知道的上下文;调用方智能体也掌握一些你的系统不知道的上下文。设计这些交互时,你应该清楚地判断:哪一方在哪些信息上更有优势。
比如一个差旅报销场景:
- 用户的 AI 幕僚知道:日历、邮箱里的酒店/航班、Slack 里的客户邀约、收据照片
- 费用管理系统知道:原始交易、公司报销政策、GL 科目、历史归类习惯
传统 API 会把问题丢回给用户:「这里有一笔交易需要填写 GL code,请从 150 个里挑一个。」
设计良好的 Agent ↔ Agent 交互会反过来:不索要 GL code,而索要上下文——「这是客户餐、团队餐还是个人?」用户的 Agent 从日历或 Slack 里查到答案,费用系统再据此自动套用科目。Diego 和他的 Agent 都不需要知道 GL code 是什么。
承认你的 Agent 在某些事情上不擅长是完全可以的——因为你们其实在服务同一个用户。
这件事的最后一层落地是反馈循环。Ramp 的做法很值得拄:
- 每次工具调用要求 Agent 填一个
rationale——你看不到聊天内容,但能从理由日志重建用户意图 - 提供一个独立的 feedback 工具——Agent 卡住时可以用它告诉你它原本想做什么、试了什么、卡在哪
- 在特定工具里加上下文种子参数——把以后会有用的上下文一并捕获
当你反复在理由日志里看到「正在生成事故报告」「正在起草事故摘要」时——这就是一个新功能的信号。Agent 比真人用户更具体、更一致地告诉你下一步应该构建什么。
写在最后
DX 是「为人类移除摩擦」。AX 是把同样的纪律,应用到「无法主动求助、也无法适应你 API 把它带去意外地点」的自治消费者身上。
如果把这篇文章浓缩成三句话:
- API 层:先把 OpenAPI 描述、错误响应(带
doc_url+ 恢复元数据)、机器友好的认证和限流做对——这是地基,没有这一层,上面所有努力都是补漏。 - CLI 层:CLI 在 Agent 时代正在复兴。一个默认 JSON、子命令可组合、
--help写得好、用环境变量认证的 CLI,杠杆往往比同等规模的 MCP server 更高——因为 Agent 本来就会用 CLI。 - 工具层:工具不是 API 的 1:1 包装,而是为非确定性消费者重新设计的契约。选对要做的工具 > 做更多工具;让工具响应回传高信号、低 token、语义化的上下文;用评测驱动迭代,把工具描述当 prompt engineering 来做。
那些过去让 API 对开发者「好用」的特性——具体、语义一致、错误友好——在没有人类在回路中为模糊性兜底时,只会更加重要。
好消息是:让你的 API 对机器好懂的方法,和让它对一个不熟悉你系统的新人开发者好懂的方法,本质上是同一件事——从这里开始就好。