你向你的AI副驾驶要求捕获一个窗口。副驾驶写下peek app "Xcode"。工具尝试寻找一个精确名称为Xcode的窗口,但没有找到,因为实际的进程名是Xcode-16.3。工具打印出Error: application not found。这位情感记忆如金鱼般的副驾驶尝试peek app "Xcode-16.3",成功了。但浪费了一次对话轮次、输入和输出的令牌,还有支付账单的用户的耐心。
现在想象另一种场景:副驾驶写下peek app xcode。工具会自动标准化名称,进行模糊匹配,找到Xcode-16.3,捕获窗口,并返回/tmp/peek/Xcode-16.3-1712524800.png。一个输出令牌。零次无效尝试。
这两种情况的差别不是一个bug,而是一个设计决策。
那些不会看你--help的用户
2025年初,Netlify的CEO Mathias Biilmann创造了术语“智能代理体验”(Agent Experience,即AX),用来描述AI代理在与一款产品互动时所拥有的体验。正如用户体验(UX)关注的是人类用户,开发者体验(DX)面向的是开发者,智能代理体验(AX)则专注于LLM。
这个概念听起来很抽象,直到你把它应用到某些具体的东西上。例如,命令行工具(CLI)。CLI已有40年的历史,其设计一直是为了与人类用户交互:描述性的信息、颜色、高度可视化的进度条以及--help页面。这些对于LLM来说全是“噪音”。一个LLM不会查看帮助信息——它会根据命令名称推断出标志。它不会欣赏绿色的“成功”提示——它只处理纯文本。它也不会观察进度条——它只等待过程完成。
传统CLI的设计旨在让人类用户理解发生了什么,而AX则优化CLI工具,以便智能代理能用最少的令牌和轮次来完成操作。
五项原则,三款工具
在过去的几个月里,我创建了三款秉持同一理念的CLI工具:peek(用于macOS窗口捕获)、lql(用于Linear问题管理)和driftkit(用于智能代理的行为审核工具)。它们都设计成即使没有说明文档,LLM也可以使用。从这些经验中总结出了五项原则。
1. 输出是契约,不是对话
传统的CLI用于窗口捕获时可能会输出类似以下的信息:
✅ Screenshot saved successfully!
File: /tmp/peek/Xcode-1712524800.png
Size: 1920x1080
Format: PNG
这看起来很漂亮,也很有信息量。但对于需要将路径传递到另一工具的LLM来说完全没用。它不得不解析这个输出,忽略掉表情符号,找到以File:开头的那行,并提取路径。浪费了令牌。
peek只输出一个内容:
/tmp/peek/Xcode-1712524800.png
一个路径,没有其他多余信息。LLM直接读取、使用并继续。stdout的输出是一种契约:始终是一个容易解析的路径,始终保持稳定。如果更改格式,就破坏了契约,也会让所有依赖该工具的代理出错。
也就是说:你的stdout是API,而不是用来“装饰”的。
2. 容忍幻觉——不要惩罚它们
LLM会产生幻觉命名。这是一种自然现象,就像地心引力或wifi总是在你赶时间时信号变差。如果你的工具需要准确名称,那就是在向一种基于概率的机器要求精确。这是行不通的。
peek有三种搜索模式以找到应用程序:
- 精确匹配(不区分大小写):
xcode→Xcode - 标准化匹配(去除空格和短横线):
thinklocal→ThinkLocal - 部分匹配:
xcode→Xcode-16.3
LLM并不需要知道进程的确切名称。它只需给出一个大致的名字,工具会处理剩下的事情。lql也采取类似的方法来匹配项目和团队的名字——它会根据部分内容将tokamak解析为Tokamak,而无需使用UUID。
原则很简单:如果监督代理的那个人类能够推测出它的意思,那么工具也应该能够做到。
3. 错误信息应该告诉用户怎么做,而不是发生了什么
对比一下以下两个错误消息:
Error: application not found in window list
Error: "Xcode" is not running. Start it with: open -a "Xcode"
第一个描述了问题所在。第二个则提供了解决问题的方法。人类读了第一个会想“哦,没打开啊。”LLM读到第一个后会……用其他名字试试,或者去Google搜索,又或者发明一个--force标志。而对于第二个,LLM会直接执行open -a "Xcode",等待,然后再试一次。问题在一个轮次内得以解决。
再看一个例子。当peek无法确定你想要指代的应用程序,因为名字匹配了多个:
Error: "code" matches multiple apps:
Visual Studio Code
Xcode
Be more specific.
LLM收到候选列表,选择正确的应用程序,再次尝试。无需猜测,也无需搜索。工具提供了所有需要的操作信息。
规则是:每条错误信息都应该包含可执行命令或有效选项列表。如果代理在读取错误信息后还需要思考,你的错误信息就不够好。
4. 零强制性标志
传统的窗口捕获CLI可能要求:
capture --app "Xcode" --format png --output /tmp/screenshot.png --window-id 12345
四个强制性标志。LLM必须记住(或猜测)所有四个标志。每缺少一个标志,就可能需要额外的错误修正轮次。
peek只需要:
peek app Xcode
格式始终是PNG。输出路径有合理的默认值(/tmp/peek/<app>-<timestamp>.png)。窗口ID通过选择最大窗口自动解析。如果需要自定义,可以添加选项如--output或--panel,但并不是必须的。
lql采用了相同的理念:lql create "登录失败"会根据工作目录自动分类类型、优先级、团队和项目。完全零标志。
原则是:合理的默认值不仅仅是为了方便——它们是对幻觉的稳健性保障。代理所需的参数越少,它就越不会凭空捏造。
5. 静默捕获——不要打断代理
这一点特别针对peek,但却说明了一个普遍原则:工具不应干扰代理的工作流。
在macOS中,用screencapture捕获窗口时需要将其置于前台。这会导致终端窗口失去焦点,而代理将无法继续操作。这就好比在电工修理时从他手中拿走螺丝刀。
peek使用了Apple的ScreenCaptureKit框架,通过过滤器按窗口ID选择单个窗口(SCContentFilter(desktopIndependentWindow:))。窗口可以被直接捕获,而无需将其激活、移动或更改焦点。终端仍然保持为活动窗口。
通用原则是:为代理设计的工具不应产生可见的副作用。不应弹出窗口、显示确认对话框,或者提示用户按Enter继续。代理在后台工作,你的工具也应该如此。
设计对比
从整体上看,我们将传统设计与AX设计应用于相同的操作进行比较:
| 操作 | 传统CLI | 面向AX的CLI |
|---|---|---|
| 捕获输出 | ✅ Saved to /tmp/... + metadata | /tmp/peek/App-123.png |
| 应用未找到 | Error: not found | Error: "X" not running. Start: open -a "X" |
| 名称不准确 | 错误 | 自动模糊匹配 |
| 所需标志 | 3-4个强制性标志 | 0(仅需位置参数) |
| 创建问题 | --team T --type bug --priority 2 ... | "登录失败"(自动分类) |
| 焦点丢失 | 是(screencapture) | 否(ScreenCaptureKit) |
右侧列不仅对LLM更友好,对所有用户都更方便。这就是有趣的地方:为代理设计的通常会提升人类的体验。
如何立即应用
你无需从零开始重写工具。以下是可以本周就实现的三个变化:
检查你的
stdout输出。 如果工具输出的内容不能直接作为其他工具的输入使用,那说明只是装饰而非输出。添加--quiet或--json选项以仅输出数据。更好的是,将这设为默认值。检查你的错误消息。 阅读代码中的每个
Error:并问自己:“代理是否能在没有额外上下文的情况下解决这个问题?”如果答案是否定的,增加一个建议命令或有效选项列表。统计你的强制性标志数量。 每一个强制性标志都是代理错误的潜在风险点。这是否可以有合理的默认值?是否可以从上下文推断?例如工作目录、配置文件或命名约定——任何可以避免代理猜测的方式。
目前的局限
这些原则并非万能。目前仍存在一些限制。peek的模糊匹配在处理常见情况时效果很好,但在两个应用程序名称几乎相同时(例如Code和Xcode),会失效。在这种情况下,工具会返回一个候选列表,代理需要选择,这依然需要一个额外的操作轮次。
此外,lql的自动推断功能(根据问题文本自动分类类型、优先级和团队)目前的准确率约为70%。在其余的30%情况下,代理需要进行修正。这虽比强制填写四个标志要好,但不是完美的解决方案。
而且,以“默认最小输出”为设计的代价是显而易见的:使用peek直接从终端操作的用户可能会更愿意看到一种视觉上的确认。仅输出路径不如包含元数据的Saved to...提示那样令人放心。这就是--verbose标志存在的理由。
尝试一下
peek可以通过Homebrew安装:
brew tap frr149/tap
brew install peek
peek app Safari
代码可在github.com/frr149/peek上找到。如果你正在为代理构建CLI工具(或想要调整现有工具),欢迎创建一个issue——我很想看看你如何将这些原则应用到实际案例中。
未来是双语的
我并不是说你该放弃人类用户。我的意思是,你的工具将会有两种类型的用户:想要可读输出的人类和想要可解析输出的代理。同时,先为代理设计,然后再为人类添加界面,比反过来要容易得多。
在stdout中输出路径对代理来说完美无瑕,对人类来说也能接受。而包含表情符号和颜色的消息虽然对人类用户来说很完美,但对于代理却是地狱。
发展的方向是明确的:未来的CLI工具将会说两种语言。从那种不需要装饰的语言开始。
本文原文为西班牙语,借助AI翻译。