概述
本文由Google高级开发者关系工程师Justin Poehnelt撰写,提出了一个核心观点:为人类设计的CLI无法通过简单改造就适用于AI Agent,必须从设计之初就将Agent作为主要用户。作者区分了"人类开发者体验"(Human DX)和"Agent开发者体验"(Agent DX)的本质差异:前者优化可发现性和容错性,后者则需要优化可预测性和深度防御。文章系统性地阐述了如何重新设计CLI工具,使其成为AI Agent的可靠接口。
详细内容
核心理念:两种截然不同的设计哲学
Human DX vs Agent DX
| 维度 | Human DX(人类开发者体验) | Agent DX(Agent开发者体验) |
|---|---|---|
| 优化目标 | 可发现性、容错性 | 可预测性、深度防御 |
| 错误处理 | 模糊匹配、智能提示 | 严格验证、明确失败 |
| 输入方式 | 交互式、渐进式 | 声明式、一次性 |
| 输出格式 | 人类可读、格式化 | 机器可读、结构化 |
| 学习成本 | 低门槛、渐进学习 | 精确规范、零歧义 |
关键洞察
"为人类优化的CLI进行改造是行不通的;工具必须从第一天起就将Agent作为主要消费者来构建。"
这意味着:
- 不能简单地在现有CLI上加一层Agent适配器
- 需要从根本上重新思考CLI的设计原则
- 一个二进制文件可以同时服务人类和Agent,但接口设计必须区分
设计原则一:原始JSON负载优于定制标志
人类的偏好 vs Agent的偏好
人类喜欢扁平的标志:
gws create drive.file \
--name "Report" \
--mimeType "application/vnd.google-apps.document" \
--parents "folderId123"
Agent更喜欢嵌套JSON:
gws create drive.file \
--json '{
"name": "Report",
"mimeType": "application/vnd.google-apps.document",
"parents": ["folderId123"]
}'
为什么JSON更适合Agent
-
直接映射API Schema
- JSON结构可以直接对应API的请求体
- 无需在Agent内部进行格式转换
- 减少出错的可能性
-
LLM生成友好
- LLM天然擅长生成结构化JSON
- 不需要学习特定的CLI标志约定
- 零翻译损失
-
灵活性
- 可以轻松处理嵌套结构
- 支持数组、对象等复杂类型
- 便于扩展新参数
实现建议
支持--json或--params参数,接受完整的API负载:
# 人类友好的标志
gws drive files list --pageSize 10
# Agent友好的JSON
gws drive files list --params '{"pageSize": 10, "fields": "files(id,name)"}'
设计原则二:Schema内省取代文档
静态文档的问题
- 消耗Token:Agent需要将文档内容注入上下文
- 容易过时:文档与代码实现可能不同步
- 解析困难:自然语言文档难以被机器精确理解
CLI作为运行时文档
实现方式:
# 获取命令的Schema信息
gws schema drive.files.list
输出示例:
{
"method": "drive.files.list",
"parameters": {
"pageSize": {
"type": "integer",
"default": 100,
"maximum": 1000,
"description": "The maximum number of files to return per page"
},
"fields": {
"type": "string",
"description": "The fields to include in the response"
}
},
"response": {
"type": "object",
"properties": {
"files": {
"type": "array",
"items": {
"$ref": "#/definitions/File"
}
}
}
}
}
优势
"CLI成为API当前接受内容的规范真实来源,而不是六个月前文档所说的内容。"
- 实时性:始终反映当前API状态
- 机器可读:JSON Schema格式,易于解析
- 自描述:包含类型、约束、默认值等元数据
- 节省Token:Agent可以按需查询,无需加载完整文档
设计原则三:上下文窗口纪律
问题:API响应过大
Workspace API通常返回庞大的JSON对象:
- 包含大量不必要的字段
- 数组可能包含数千个元素
- 直接消耗Agent的上下文窗口
解决方案
1. 字段掩码(Field Masks)
始终支持fields参数来限制返回数据:
gws drive files list \
--params '{"fields": "files(id,name,mimeType,modifiedTime)"}'
最佳实践:
- 默认只返回最小必要字段
- 提供
--minimal标志快速获取精简响应 - 文档中明确标注每个字段的Token成本
2. NDJSON分页(Newline Delimited JSON)
传统JSON数组的问题:
// 需要缓冲整个数组
{"files": [{...}, {...}, {...}]}
NDJSON流式输出:
{"id": "1", "name": "File 1"}
{"id": "2", "name": "File 2"}
{"id": "3", "name": "File 3"}
优势:
- 逐行处理,无需缓冲
- Agent可以流式消费结果
- 支持大结果集而不溢出上下文
设计原则四:针对幻觉的输入加固
Agent幻觉 vs 人类拼写错误
人类错误:
- 拼写错误:
--pageSzie(可以模糊匹配纠正) - 格式错误:日期格式不对(可以智能解析)
Agent幻觉:
- 生成不存在的文件路径
- 注入控制字符
- 构造恶意资源ID
防御策略
"这个CLI经常被AI/LLM Agent调用。始终假设输入可能是对抗性的。"
1. 文件路径验证
# 沙箱限制:只允许当前工作目录
def validate_path(path):
resolved = Path(path).resolve()
if not str(resolved).startswith(str(Path.cwd().resolve())):
raise ValueError("Path traversal detected")
return resolved
防御目标:
- 防止
../../.ssh/id_rsa类型的路径遍历 - 限制写入位置在预期范围内
- 拒绝绝对路径(除非明确允许)
2. 控制字符过滤
# 拒绝ASCII控制字符(0x00-0x1F)
def sanitize_input(text):
if any(ord(c) < 0x20 for c in text):
raise ValueError("Control characters not allowed")
return text
为什么重要:
- Agent可能在字符串中注入换行符、制表符
- 控制字符可能破坏命令解析
- 可能导致日志注入攻击
3. 资源ID验证
# 拒绝可能破坏URL的字符
def validate_resource_id(resource_id):
forbidden = ['?', '#', '%', '&', '=']
if any(c in resource_id for c in forbidden):
raise ValueError(f"Resource ID contains forbidden characters: {forbidden}")
return resource_id
防御目标:
- 防止嵌入查询参数(
?key=value) - 防止片段标识符(
#section) - 防止双重编码攻击
设计原则五:交付Agent技能,而非仅交付命令
传统文档的局限
--help菜单对人类有用,但对Agent来说:
- 需要解析自然语言
- 缺乏结构化约束
- 不包含最佳实践和约束条件
SKILL.md:Agent的学习材料
文件位置: 与CLI一起分发SKILL.md
格式示例:
---
name: google-workspace-cli
description: CLI for Google Workspace APIs
commands:
drive.files.list:
invariants:
- "Always use --params with fields to limit response size"
- "Default pageSize should be 10, max 100"
examples:
- cmd: 'gws drive files list --params \'{"fields": "files(id,name)", "pageSize": 10}\''
description: "List files with minimal fields"
drive.files.create:
invariants:
- "Always use --dry-run first when creating files"
- "Validate parent folder ID exists before creating"
---
# Google Workspace CLI
## 安全准则
1. **Never** use absolute paths for output
2. **Always** validate resource IDs before use
3. **Always** use field masks to limit API responses
## 常见模式
### 列出文件
...
为什么需要显式规则
"这些规则存在是因为Agent没有直觉——它们需要不变量被明确说明。"
Agent需要知道:
- 哪些操作是危险的(需要
--dry-run) - 哪些参数组合是推荐的
- 什么情况下应该请求人类确认
设计原则六:多接口支持
一个二进制,多种接口
同一个CLI二进制应该支持多种Agent接口:
1. 传统CLI(人类和Agent)
gws drive files list --output json
2. MCP(Model Context Protocol)
# 通过stdio暴露JSON-RPC接口
gws mcp
MCP协议允许Agent直接调用工具:
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "drive_files_list",
"arguments": {"pageSize": 10}
}
}
3. 原生扩展
- Gemini CLI Extension
- VS Code插件
- 其他IDE集成
4. 环境变量(无头认证)
export GOOGLE_WORKSPACE_CLI_TOKEN="..."
gws drive files list # 无需交互式登录
核心原则
"一个真实来源,两种接口。"
- 业务逻辑只实现一次
- 不同接口只是不同的呈现层
- 确保人类和Agent使用相同的底层功能
设计原则七:安全护栏
--dry-run:本地验证
在执行破坏性操作前进行验证:
# 先验证请求
gws drive files delete --id "xxx" --dry-run
# 输出: Would delete file "xxx" (size: 1.2MB)
# 确认后执行
gws drive files delete --id "xxx"
作用:
- 捕获幻觉导致的错误参数
- 显示将要执行的操作详情
- 防止数据丢失
--sanitize:响应清洗
API返回的数据可能包含恶意内容:
gws drive files get --id "xxx" --sanitize
清洗内容:
- 提示注入攻击(Prompt Injection)
- 恶意JavaScript代码
- 社会工程学内容
- 通过Model Armor等安全服务过滤
"响应清洗是最后一道防线。"
实施路线图
不需要一次性重写所有代码,可以渐进式改造:
| 优先级 | 任务 | 影响 |
|---|---|---|
| 1 | 添加--output json | 让输出机器可读 |
| 2 | 验证所有输入 | 防止路径遍历、控制字符 |
| 3 | 添加Schema内省(--describe) | 自描述能力 |
| 4 | 支持字段掩码 | 节省上下文窗口 |
| 5 | 添加--dry-run | 防止意外操作 |
| 6 | 创建SKILL.md或CONTEXT.md | Agent学习材料 |
| 7 | 暴露MCP接口 | 原生Agent集成 |
核心结论
"Agent不是受信任的操作员。请基于这个假设来构建。"
这意味着:
- 零信任:验证所有输入,假设可能是对抗性的
- 防御性设计:多层安全检查,不依赖单一防线
- 可预测性:避免模糊行为,结果必须可预期
- 最小权限:只返回必要数据,只执行必要操作
观点存疑
-
JSON是否总是优于标志?
- 对于简单操作,标志可能更直观且错误率更低
- JSON语法错误(如引号不匹配)对Agent来说也是常见问题
- 可能需要同时支持两种形式,增加维护负担
-
过度防御是否会影响可用性?
- 严格的路径验证可能阻碍合法用例(如系统级工具)
- 控制字符过滤可能破坏某些国际化场景
- 安全与便利的权衡需要仔细考量
-
MCP协议是否是未来标准?
- MCP目前还在早期阶段,可能面临竞争标准
- 投入大量资源实现MCP支持可能存在风险
- 需要观察行业采纳度
-
SKILL.md的有效性存疑
- 不同Agent框架可能有不同的技能定义格式
- 维护SKILL.md可能增加文档负担
- Agent是否真的需要显式规则,还是可以通过示例学习?
-
对Agent能力的假设是否过于悲观?
- 文章假设Agent经常产生幻觉和错误
- 随着模型能力提升,这些防御措施是否仍然必要?
- 过度防御可能限制Agent的能力发挥
-
实施成本是否被低估?
- 同时支持人类和Agent接口需要额外开发工作
- 小型团队可能难以承担这种双重设计负担
- 需要权衡投入产出比
Tags
#CLI设计 #AIAgent #开发者体验 #API设计 #安全 #MCP #工具设计 #自动化 #GoogleWorkspace #开发者工具