机器学习笔记 -- 给 Agent 设计 CLI 的原则

给 Agent 设计 CLI 的十个原则 Google 官方发了 7 条 CLI 设计原则,我读完发现我们都忽略了一种新用户 Agent 时代的 CLI 设计哲学探讨

给 Agent 设计 CLI 的十个原则

来源

Agent 怎么想的

LLM 训练数据里有大量 shell 命令和 bash 脚本(GitHub 代码约占 5%、Stack Overflow 约占 2%),Agent 天生对命令行有相当的「直觉」。

但这个直觉有边界。Berkeley 的 BFCL V4 Leaderboard 显示,即使排名第一的模型,在复杂的多步工具调用场景下准确率也达不到 100%。SWE-bench 数据更直观:当前最强模型也只能解决约七成编码任务。

模型能力仍在飞速进步,但使用 CLI 的方式没变:跑 --help 、解析输出、拼命令、看退出码。模型越强,每一步判断越准 —— 但 CLI 本身设计不好,再强的模型也会掉进同样的坑。

Surge AI 有一个案例:让前沿模型处理只需改 2 行代码的问题,它走了 39 轮对话、改了 693 行代码,最终失败。原因是第一步就幻觉出了不存在的类名,然后在错误基础上越陷越深,连续 22 轮坚持「核心逻辑是对的」。而另一个模型遇到同样问题,发现文件被截断后主动要求重新读取,确认完整内容才动手,一次解决。

好的 CLI 设计,就是帮 Agent 做这个区分:减少它需要猜的东西,增加它能验证的东西。

Agent 的弱点

  • 大小写敏感短参数grep -a / -Assh -v / -V 含义完全不同;Agent 基于概率选择,两个 token 概率接近时选错风险高
  • 交互式提示Are you sure? [y/N] 对 Agent 是一堵墙(2019 年 AWS CLI v2 改默认 pager 为 less ,导致全球数千 CI 任务挂起)
  • 幻觉参数 :Agent 会自信地使用不存在的参数;你的 CLI 不能假设只有最强模型才来调用它
  • 非结构化输出 :人类可读的 table 输出对 Agent 来说靠猜

原则一:名词在前(noun-verb)

# 好:docker container ls / gh pr create / lark-cli calendar +agenda
# 不好:create-pr / delete-image / search-user

Agent 发现命令的过程是 树搜索mytool --help → 看到名词 → mytool user --help → 看到动词。每一步有 --help 可查,确定性缩小范围。verb-noun 结构把所有操作平铺一层,无层级引导。

Docker 是典范:container / image / volume 是名词, ls / rm / create 是动词,Agent 学会 docker container ls 就能推断 docker volume ls

原则二:长参数优先

所有参数都应有长格式( --verbose ),短格式( -v )作为可选的人类便利。

  • --dry-run 具备自描述能力; -n 在不同工具中可能是 --line-number--numeric--no-action
  • --verbose--version 不会混淆; -v / -V 一字之差意义迥异
  • LLM 见过海量 --output ,语义绑定强; -o 的语义绑定弱得多

钉钉 CLI 的 --yes 参数描述是『跳过确认提示(AI Agent 模式)』 —— 参数名本身就是自描述的,Agent 一看就知道该在什么时候加它。

原则三:输出是契约

stdout 输出 JSON 数据, stderr 输出状态信息, 严格分流

结构化输出一旦发布即为 API。Kubernetes v1.14 弃用 --export ,v1.18 正式移除,导致数千 Helm chart 和 CI/CD 管道崩溃。 加新的可选字段安全,改变已有字段类型或名称就是破坏性变更。

GitHub CLI 在这方面做得趄庞:检测到输出被管道传输时,自动切换为 tab 分隔格式,去掉颜色转义符,文本不截断。飞书 CLI 支持 JSON、NDJSON、table、CSV、pretty 五种格式。

原则四:感知环境

检测 TTY 还是 pipe,自动调整行为。非 TTY 下默认输出 JSON,不该要求 Agent 额外加 --json flag。钉钉 CLI 的 --yes 参数就专门为此设计:跳过所有确认提示,进入 AI Agent 模式。

gcloud 文档里有一条建议值得所有 CLI 开发者记住:「不要依赖 gcloud 的原始输出格式,永远使用 --format 标志。」因为原始输出格式可能随版本变化。

原则五:干跑优先

每个有副作用的命令支持 --dry-run ,给 Agent 探索-验证的反馈循环。好的输出是结构化 JSON diff,告诉 Agent 什么会被创建、修改或删除。

飞书的实现更细致:干跑时会输出完整的请求 URL、方法、参数,Agent 可以在执行前确认请求的正确性。Lightning Labs 的设计更进一步:--dry-run 使用专门的退出码(exit code 10),这样 Agent 可以通过退出码区分「干跑成功」和「真正执行成功」。

原则六:退出码控制

退出码 含义 Agent 应对
0 成功 继续执行
1 一般错误 读 stderr 诊断
2 参数错误 修正参数重试
3 资源不存在 跳过或创建
4 权限不足 提示用户授权
5 冲突 / 已存在 跳过或更新

退出码一旦发布即为契约,不可随意修改。

原则七:防住幻觉

  • 严格输入验证 :验证 URL(拒绝 javascript:file: 协议)、域名、输出路径(拒绝写入 .ssh/.gnupg/
  • 枚举约束--format json|table|csv--format <string> 安全得多
  • schema 自省mytool schema --all 输出完整命令树 JSON;飞书 CLI 已实现 lark-cli schema calendar.events.list
  • schema 应按需查询 —— GitHub MCP 服务器一次注入 55,000 tokens,CLI 让 Agent 按需跑 --help

原则八:幂等设计

# 命令式:资源已存在会报错
mytool user create --name "john"
# 声明式:无论调用多少次,结果一致
mytool user ensure --name "john"
# 或
 mytool user create --name "john" --if-not-exists

Agent 会重试(网络超时、结果不确定、任务中断恢复……)。 kubectl apply 是声明式设计的教科书。飞书 CLI 的 +messages-send 支持 --idempotency-key 参数:Agent 传入一个唯一标识符,即使命令被重复执行,服务端也只会处理一次。

原则九:错误即指南

{
  "error": "permission_denied",
  "message": "缺少 calendar:read 权限",
  "suggestion": "运行 lark-cli auth login --domain calendar 授权",
  "retryable": false
}

错误信息四要素: 错误类型 (机器可读)、 描述 (发生了什么)、 修复建议 (怎么解决)、 是否可重试

原则十:帮助即大脑

Anthropic 发现「描述是影响工具使用准确率的最关键因素」 —— 仅通过优化工具描述就在 SWE-bench 上大幅降低错误率。

好的 --help :以 2-3 个示例开头;明确标注必需 / 可选参数;参数描述含值域( --format json|table|csv );50 行以内。

Checklist

[ ] noun-verb 层级命令结构,支持树搜索
[ ] 所有参数有长格式,短格式可选
[ ] stdout 输出 JSON,stderr 输出状态信息
[ ] 非 TTY 自动调整输出(默认 JSON)
[ ] 副作用操作支持 --dry-run,输出结构化 diff
[ ] 退出码有细粒度语义文档
[ ] 参数枚举约束,输入严格验证
[ ] 支持 schema 自省命令
[ ] 关键操作幂等或支持 --if-not-exists
[ ] 错误信息含类型、描述、修复建议、可重试标志
[ ] --help 示例开头,标注必需 / 可选,50 行内
[ ] --yes / --no-interactive 跳过所有交互提示

开源参考:agent-cli-guide (GUIDE.md + CHECKLIST.md,可直接丢给 Coding Agent 使用)

给飞书和钉钉打个分

用这十条原则对照钉钉和飞书 CLI:

飞书做得好的: noun-verb 结构清晰,三层架构让 Agent 按需选择抽象层级,schema 命令提供完整 API 自省,五种输出格式,按域申请权限(最小权限原则),权限不足时自动提示修复方案。

飞书可以改进的: 非 TTY 下没有自动切 JSON,退出码没有文档化的细粒度语义, --idempotency-key 应该更广泛使用。

钉钉做得好的: --yes (AI Agent 模式)语义自描述, --mock 参数方便调试,帮助文本全中文(对中文 LLM 更友好),批量熔断防 Agent 失控。

钉钉可以改进的: 命令只有两层,缺少 shortcut 快捷层;没有 schema 自省命令;输出格式只有三种;帮助文本缺少使用示例。


Google 官方发了 7 条 CLI 设计原则,我读完发现我们都忽略了一种新用户

来源

Google Cloud 发了一篇技术文章:"10 分钟构建一个同时服务 AI Agent 和人类的 CLI"。它在讲一件比 "CLI 怎么写 "重要得多的事:

我们正在进入一个软件需要同时服务两种用户的时代 —— 人类和机器。

今年写的每一个 CLI,某个时刻都会被 Agent 调用。大多数 CLI 还没准备好。

交互式确认弹窗、彩色终端输出、进度条动画、" 你确定吗?(Y/n)" —— 对人类是友好体验,对 Agent 是灾难。

核心思路:把数据和呈现解耦

把数据和呈现分离(Decouple the data from the presentation)。

CLI 内部只做一件事 —— 生成数据。呈现层是可插拔的客户端。

# 人类得到交互式 TUI
a2acli watch --task abc123

# Agent 得到结构化 NDJSON
a2acli watch --task abc123 --no-tui

不需要维护两套代码库。状态由后端服务管理,CLI 只是轻量传输层。Agent 通过读取 skills/SKILL.md 学习如何使用这个 CLI。

原则一:可发现性 —— 帮 Agent 找到入口

人类会随机探索、看错误信息、Google、问同事。Agent 只能从你提供的信息里推断该怎么做。

  • 按功能分组,不按字母排序。 Agent 扫描的是结构,不是字母表
  • 显式标注入口点。 help 描述里加 "(start here)" 或 "(typical first step)"
  • 每个命令三个字段: 短描述、长描述(是什么 / 为什么 / 与类似命令的区别)、示例(3-5 个可直接复制粘贴的)

示例比描述更重要。Agent 会解析示例来推断 flag 的用法。

建议在仓库根目录放 AGENTS.md ,显式定义 AI 开发者的交互规则、默认工作流和架构标准。

原则二:Agent 优先的互操作性

  • 每个产生数据的命令支持 --json--no-tui flag
  • 支持 NO_COLOR[APP]_NO_TUI 环境变量
  • stdout 被 pipe 时自动跳过所有交互元素
  • 输出必须是合法 JSON 或 NDJSON,不能混入日志
  • 非 TTY 模式下不能有交互式提示

保护 Agent 的上下文窗口: 默认截断大文本块、脱敏敏感信息;需要完整数据时要求显式加 --full--verbose

CLI 必须是无状态的: 用引用 ID(如 --task <ID> )来操作,让后端服务管理长时运行的上下文。Agent 调用 CLI 不是在开启一段对话,而是在执行一个操作。

原则三:配置优先级

CLI Flag > 环境变量 > 配置文件 > 默认值

Agent 不需要知道 URL 和 token,只需传 --env prod ,CLI 自己去找对应配置。

原则四:错误要给出解决路径

Error: database not initialized
Hint: run 'a2acli init' to create the local database

每条错误信息后加 Hint: ,Agent 可以解析来自我纠正。 尽早失败(Fail fast) :在执行重逻辑之前先验证配置和连接,不要让 Agent 等 30 秒之后才发现 token 不对。

原则五:Flag 一致性

同一个缩写在所有命令里必须代表同一个意思。不一致的 Flag 会破坏 Agent 的推理 —— 它在一个命令里学到的规律不能用在其他命令上。

默认行为永远应该是最安全、最常见的路径。 破坏性操作必须要求显式 Flag 才能触发。

原则六:终端的视觉设计

用 "语义色彩 token" 而非直接用颜色名称:定义 Pass token 对应绿色(成功)、 Warn 对应黄色(警告 / 进行中)、 Error 对应红色(失败)、 Meta 对应灰色(元数据)。

不要给描述文字和帮助文本上色。 过度上色等于什么都没强调,用空白和对齐表达层级更有效。

原则七:版本管理与生命周期

  • --version 要能用,重要更新时主动提示
  • 优雅处理 SIGINT(Ctrl+C) :不能出现数据损坏,Agent 可能在任何时刻被上层系统打断
  • 写操作记录操作者 :从 git config user.name$USER 获取身份并记录,在多 Agent 协作场景下审计追溯越来越重要

设计范式转变

在软件工程的历史上,我们设计过面向人类的界面(GUI、CLI、Web UI),也设计过面向机器的接口(API、消息队列、数据库连接)。但我们 从来没有认真设计过 "同时面向人类和机器 "的接口

这篇文章真正在做的事,是给这个新问题提供一套工程解法 —— 同一个入口,根据调用者的身份自动切换呈现方式,同时保证数据层的一致性。

2026 年最好的 CLI,不是终端界面最漂亮的那些,而是当人类输入命令和当 Agent 编程调用时,同样运行良好的那些。

这不只是 CLI 的问题 —— 随着 Agent 介入各种工具链,SDK、API、内部平台工具迟早都会被 Agent 调用。"对人类友好 "和 "对 Agent 可用 "将成为每个工具都必须同时满足的要求。

原文:Build a CLI for AI agents & humans in less than 10 mins(Google Cloud 官方)

Agent 时代的 CLI 设计哲学探讨

来源

CLI-Anything 最近很火 —— 它用 AI Agent 自动为 GIMP、Blender、Audacity 等 GUI 软件生成命令行工具,号称要构建 "Agent-Native Software Ecosystem"。

但我们花了一个晚上拆解它的代码后,得出了一个不太客气的结论: CLI-Anything 生成的 CLI 是给人用的,不是给 Agent 用的。

更重要的是,在拆解过程中,我们推导出了一套 Agent 时代的 CLI 设计哲学 —— 五条原则,每条都有代码级的实证支撑。

传统 Unix 哲学回顾

  • Do one thing wellgrep 只搜索, sort 只排序
  • Pipe 组合grep | sort | uniq ,简单工具组合出复杂能力
  • Text is the universal interface — 一切皆文本流
  • Silent on success — 成功不废话,exit code 0
  • No captive user interface — 不要把用户锁在交互式界面里
  • 行为可预测 — 相同输入,相同输出
  • 幂等性 — 重复执行应安全

精髓:一个好的 CLI 工具不是一个应用程序,而是一个 积木块

哪些没变,哪些变了

依然成立: Do one thing well、行为可预测(对 Agent 甚至比对人类更重要)、幂等性(Agent 会重试)。

根本变化: CLI 的消费者从人类变成了 LLM Agent。两者的差异不在于 "模型够不够聪明" —— 当前旗舰模型的 CLI 使用能力已和资深程序员相当。差异在于 Agent 消费 CLI 输出的架构方式

  • 人类有很高的视觉带宽;Agent 的上下文窗口是有限的共享资源
  • 人类通过管道组合工具,数据不经过大脑;Agent 通过推理循环组合 —— 每条命令输出都要进入上下文、被理解、被推理
  • 人类多敲一条命令几乎零成本;Agent 多一轮调用就是一次完整的推理循环

原则一:上下文经济性

每轮 CLI 调用消耗的 token 要尽可能少,信息密度要尽可能高。

# 人类友好的输出(~45 tokens)
╭──────────────────────────────────╮
│  ✓ 3 layers in project           │
│  0: Background (solid)           │
│  1: Photo (image, 80%)           │
│  2: Title (text)                 │
╰──────────────────────────────────╯

# Agent 友好的输出(~25 tokens,信息量相同)
[{"i":0,"name":"Background","type":"solid"},
 {"i":1,"name":"Photo","type":"image","opacity":0.8},
 {"i":2,"name":"Title","type":"text"}]

token 消耗减少约 45%。单次差异不大,但复杂任务可能调用数十次 CLI,差异累积显著。

设计含义:

  • 默认输出 JSON,而非格式化的人类可读文本
  • 不附带装饰性内容(emoji、边框、banner、空行)
  • 对可能返回大量数据的命令(list、log),默认限制输出量

原则二:调用经济性

完成一个任务所需的 CLI 调用轮次要尽可能少。

# 需要两轮调用(多一轮查询确认)
$ cli-anything-gimp layer move 2 --to 0
{"status": "ok"}
$ cli-anything-gimp layer list   # Agent 还得再调一次确认结果

# 只需一轮调用(操作 + 结果状态一起返回)
$ cli-anything-gimp layer move 2 --to 0
{"moved":2,"to":0,"current_order":["Title","Background","Photo"]}

设计含义:

  • 每个状态变更命令,返回中包含操作后的关键状态
  • 避免 "先执行、再查询 "两步才能完成的操作模式
  • 不需要返回全部状态,只需返回与本次操作相关的状态变化

原则三:Cache 友好性

CLI 输出在状态未变时,应保持字面完全一致,以利于 LLM 推理的 prompt cache 命中。

很多 Agent 框架在组装每轮上下文时会将工具状态重新注入到上下文中间。如果这部分输出带时间戳,每轮都不同,从该插入点开始后面全部 cache miss。

// 带时间戳  每轮都不同  从此点开始 cache miss
{"name":"my_project","layers":3,"last_modified":"2026-03-15T21:35:11Z"}

// 纯结构状态  项目没变就字面一致  cache 命中
{"name":"my_project","layers":3}

设计含义:

  • 避免在输出中包含不必要的易变数据(时间戳、随机 ID、进程号)
  • 同一状态的查询命令,返回字节序列应完全一致(字段顺序固定、无随机性)
  • 只在用户显式请求时才包含易变元数据

原则四:模型友好性

CLI 的设计应尽量贴合训练数据中大量存在的 CLI 范式,降低模型幻觉概率。

# 非标准 → 模型会幻觉出 --json(训练数据中更常见)
$ mytool export --machine-readable
# 标准 → 贴合高频模式,模型不会猜错
$ mytool export --json

# 非标准子命令 → 模型可能猜测为 "mytool layer list"
$ mytool do --action=list --type=layer
# 标准子命令 → 和 git/docker/kubectl 一致,模型一次调对
$ mytool layer list

Convention over creativity — 在 Agent 时代,遵循约定比追求独特更重要。

设计含义:

  • Flag 命名遵循业界惯例( --json--output--verbose--quiet
  • 子命令结构贴合主流范式( tool <noun> <verb>tool <verb> <noun>
  • 使用主流 CLI 框架(Python Click、Node Commander)生成标准的 --help 格式

原则五:Skill-CLI 责任边界

确定性的可重复模式封装到 CLI 中;创意性的、依赖上下文的编排交给 Skill 引导 Agent 完成。

Agent 时代的工具链是 Agent → Skill → CLI 三层结构:

  • 逻辑放在 CLI 里 → 执行确定性强、鲁棒,但不灵活
  • 逻辑放在 Skill 里由 Agent 编排 → 灵活性高,但概率性的、可能出错
# 适合封装到 CLI:确定性的批量操作
cli-anything-gimp batch convert --input "*.png" --format webp --quality 80
# 如果让 Agent 编排:每张图 3 轮调用 × 100 张 = 300 轮推理循环

# 适合 Skill + Agent 编排:创意性的、依赖上下文的任务
# Skill 指导 Agent:先建项目 → 根据图片内容选择构图 → 加文字 → 调色 → 导出
cli-anything-gimp project new --width 1080 --height 1080
cli-anything-gimp layer add-from-file photo.jpg
cli-anything-gimp filter add brightness -l 0 -p factor=1.3  # Agent 根据图片亮度决定参数
cli-anything-gimp draw text --text "Hello" --size 48         # Agent 根据内容决定文案
cli-anything-gimp export render post.png

经验法则:如果一个流程每次执行步骤都一样 → CLI;如果步骤取决于上下文 → Skill + Agent。

五条原则总览

原则 优化目标 类别 根因
上下文经济性 每轮 token 量 效率 上下文窗口是有限共享资源
调用经济性 任务总轮次 效率 每轮推理循环有成本
Cache 友好性 单 token 推理成本 效率 Prompt cache 按前缀匹配
模型友好性 调用正确率 可靠性 模型基于训练数据做概率预测
Skill-CLI 责任边界 鲁棒性与灵活性平衡 架构 Agent 编排是概率性的,CLI 执行是确定性的

前三条优化 效率 ,第四条优化 可靠性 ,第五条优化 架构合理性

CLI-Anything 对照评估

用五条原则对照 CLI-Anything 生成的 GIMP CLI:

  • 上下文经济性 ❌ — 默认输出人类格式,JSON 只是 opt-in;项目有 499 行的 repl_skin.py ,全是边框、emoji、彩色字符 —— Agent 不需要任何一行
  • 调用经济性 ⚠️layer move 只返回 {"moved":2, "to":0} ,不返回新排列顺序;Agent 想确认结果还得再调一轮 layer list
  • Cache 友好性 ❌session.py 里随意嵌入 datetime.now().isoformat() ,每次快照都不同
  • 模型友好性 ✅ — Click 框架 + 标准子命令结构 + --json flag;但这不是刻意为 Agent 设计的,只是 Python CLI 的通用最佳实践
  • Skill-CLI 责任边界 ❌ — 没有任何 batch 命令;REPL 交互模式却是默认入口 —— 一个 Agent 完全不需要的 Captive UI

结论: CLI-Anything 是工程完成度不错的项目,但其设计哲学停留在 "给人用的 CLI 加个 --json "的阶段。它提出了正确的问题,但给出了一个没想透的答案。 最具说明性的信号是:无子命令时默认进入 REPL 模式 —— Unix 哲学就反对它,在 Agent 时代更不合适。

结语

这套框架的适用范围不限于 CLI —— 任何 Agent 调用的工具接口,都面临相同的约束:

  • 你的输出占用 Agent 多少上下文?
  • Agent 需要几轮调用才能完成任务?
  • 你的输出在状态不变时能不能保持字面一致?
  • 你的接口设计贴合模型的先验知识吗?
  • 哪些逻辑该你做,哪些该让 Agent 做?

参考资料快照
参考资料快照

本文短链接:
If you have any questions or feedback, please reach out .