概述

本文由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

  1. 直接映射API Schema

    • JSON结构可以直接对应API的请求体
    • 无需在Agent内部进行格式转换
    • 减少出错的可能性
  2. LLM生成友好

    • LLM天然擅长生成结构化JSON
    • 不需要学习特定的CLI标志约定
    • 零翻译损失
  3. 灵活性

    • 可以轻松处理嵌套结构
    • 支持数组、对象等复杂类型
    • 便于扩展新参数

实现建议

支持--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.mdCONTEXT.mdAgent学习材料
7暴露MCP接口原生Agent集成

核心结论

"Agent不是受信任的操作员。请基于这个假设来构建。"

这意味着:

  • 零信任:验证所有输入,假设可能是对抗性的
  • 防御性设计:多层安全检查,不依赖单一防线
  • 可预测性:避免模糊行为,结果必须可预期
  • 最小权限:只返回必要数据,只执行必要操作

观点存疑

  1. JSON是否总是优于标志?

    • 对于简单操作,标志可能更直观且错误率更低
    • JSON语法错误(如引号不匹配)对Agent来说也是常见问题
    • 可能需要同时支持两种形式,增加维护负担
  2. 过度防御是否会影响可用性?

    • 严格的路径验证可能阻碍合法用例(如系统级工具)
    • 控制字符过滤可能破坏某些国际化场景
    • 安全与便利的权衡需要仔细考量
  3. MCP协议是否是未来标准?

    • MCP目前还在早期阶段,可能面临竞争标准
    • 投入大量资源实现MCP支持可能存在风险
    • 需要观察行业采纳度
  4. SKILL.md的有效性存疑

    • 不同Agent框架可能有不同的技能定义格式
    • 维护SKILL.md可能增加文档负担
    • Agent是否真的需要显式规则,还是可以通过示例学习?
  5. 对Agent能力的假设是否过于悲观?

    • 文章假设Agent经常产生幻觉和错误
    • 随着模型能力提升,这些防御措施是否仍然必要?
    • 过度防御可能限制Agent的能力发挥
  6. 实施成本是否被低估?

    • 同时支持人类和Agent接口需要额外开发工作
    • 小型团队可能难以承担这种双重设计负担
    • 需要权衡投入产出比

Tags

#CLI设计 #AIAgent #开发者体验 #API设计 #安全 #MCP #工具设计 #自动化 #GoogleWorkspace #开发者工具