site logo

Marico's space

AI 工具需要合同,而非提示词

编程技术 2026-05-20 11:30:01 11

最近折腾了一个叫 entropyx 的代码库分析工具,踩了几个坑才想清楚一件事:AI 时代给开发者工具写提示词是没用的,工具本身得是一份可执行的合同。

这篇文章把这个问题说清楚,顺便聊聊 entropyx 怎么实现这个思路的。

可执行文件就是接口:代理能发现、验证、信任的那种

AI 代理能读 README、扫描测试、检查源码树,然后推断出一个看起来合理的架构。但它还是可能把你的工具搞坏——改个参数名、弱化一个退出条件、或者"简化"了某个下游脚本依赖的 JSON 输出。

问题往往不是模型理解错了代码。更常见的原因是:契约根本没有被可执行化。

在 AI 辅助工程里,CLI(命令行接口)不再是只给人用的界面。它是意图、行为、验证三者能交汇的最窄处。是代理能运行、观察、验证、组合的那个平面。

这就是 entropyx 背后的前提。

entropyx 是一个本地优先的 Rust CLI 工具,用于代码库取证。它扫描一个 git 仓库,然后输出结构化的时间、结构、作者和语义信号摘要。它能告诉你哪些文件吸收了最多的变更、哪些文件承载着耦合压力、哪些公开 API 在没有测试的情况下漂移了,以及仓库里哪些事件能解释这个模式。

但重要的不是这个具体的指标集。重要的是接口的形态。

可执行文件就是契约。

不是提示词。不是仪表盘。不是针对特定模型的集成层。二进制文件暴露了它能做什么、接受什么输入、输出什么、以及如何请求证据。人类能运行它。CI 能运行它。AI 助手能运行它。三者看到的是同一份契约表面。

这就是设计模式:让工具对代理可读——通过让接口更显式,而不是更魔幻。

失败模式

很多开发者工具的契约是隐式的。

README 说一套,--help 说另一套。测试覆盖了内部函数,但没覆盖下游工具依赖的 CLI 行为。JSON 输出看起来稳定,直到某个字段被重命名。一个命令返回的文本人读起来方便,机器解析却脆弱。失败时退出码是 0,因为人会注意到 stderr 上的错误行。

人类靠上下文来弥补。他们记得旧的行为。他们知道哪些字段是承重的。他们知道"漂亮的输出"不是自动化的路径。他们知道哪些参数是公开契约的一部分,即使代码里没说。

代理不会自动获得这些记忆。

它们能推断、能搜索、能跑测试。但如果契约存在于文档、惯例和团队知识里,代理迟早会踩上去。它甚至可能做出本地合理、测试套件通过、但实际破坏了接口的修改。

这就是 AI 优先工具要解决的运营问题。

答案不是更多的提示词文本解释该怎么表现。答案是一份代理能执行的契约表面。

重新定位:CLI 作为协议

命令行常常被当作真正产品的包装层。对 AI 优先工具来说,这搞反了。

CLI 就是协议。

它有命名命令、显式输入、可观察输出、退出码,还有自然放置版本号的地方。它在本地运行。它能与文件、管道、CI 任务、脚本、终端组合。它已经是大多数编码代理能操作的界面。

这让它成为一个好的契约边界。

重点不是每个产品都只能是 CLI。重点是:如果一个工具声称支持 AI 辅助工程,它应该暴露一个代理能发现和验证的表面,而不用靠猜。

对 entropyx 来说,这个表面有意做得很小:

entropyx describe
entropyx schema
entropyx scan /path/to/repo
entropyx explain /path/to/repo file:<blob-prefix>
entropyx calibrate --summary summary.json --labels labels.json

核心代理循环是前四个命令。calibrate 是离线权重拟合路径:拿一个先验摘要,加入标注的文件分数,拟合新权重,然后把这些权重反馈到后续扫描中。

五个命令就够了,因为这些命令不只是动词。它们是契约的结构解剖。

契约的解剖

一个 AI 优先的可执行文件需要的不仅是 --help

--help 是为那些已经知道自己在跑什么东西的人优化的。代理需要的是机器可读的答案,来回答一组不同的问题:

  • 这个可执行文件是什么?
  • 它能做什么?
  • 它接受什么输入?
  • 它输出什么形状的数据?
  • 它承诺哪些不变量?
  • 这个操作大概有多贵?
  • 如何从摘要深入到证据?

entropyx 里,describe 回答第一组问题:

entropyx describe

它输出协议的根节点为 JSON:名字、版本、契约版本、目的、能力、输入类型、输出格式、成本模型和不变量。

schema 回答形状问题:

entropyx schema > tq1-schema.json

它输出 tq1 摘要信封的 JSON Schema。schema 的 $id 绑定到协议契约版本,所以输出格式的破坏性变更不会对消费者隐藏。

scan 回答证据问题:

entropyx scan /path/to/repo > summary.json

它遍历本地 git 历史,输出一个密集的仓库摘要。

explain 回答深入问题:

entropyx explain /path/to/repo file:<blob-prefix>
entropyx explain /path/to/repo commit:<sha>
entropyx explain /path/to/repo range:<base>..<head>

摘要当前为 HEAD blob 生成 file:<blob-prefix> 句柄。explain 也接受 commit:<sha>range:<base>..<head> 地址形式,这让代理能在不依赖先验摘要条目的情况下请求提交或发布窗口证据。

这个区别很重要。契约应该精确说明它发出哪些标识符,以及它接受哪些标识符。

契约长什么样

最重要的输出路径不是散文。它是 tq1,一个带类型的 JSON 信封。

一个简化后的摘要像这样:

{ "schema": {"name": "tq1", "version": "0.1.0"}, "dict": { "files": ["src/lib.rs"], "authors": ["a@example.com"], "metrics": [ "change_density", "author_entropy", "temporal_volatility", "coupling_stress", "blame_youth", "semantic_drift", "test_cooevolution", "composite" ] }, "files": [ { "file": 0, "values": [0.4, 0.2, 0.1, 0.7, 0.3, 0.6, 0.5, 0.43], "lineage_confidence": 1.0, "signal_class": "api_drift" } ], "events": [], "handles": { "file:abc123def456": { "kind": "file", "file": 0, "blob_prefix": "abc123def456" } }, "enrichments": {"pull_requests": {}}
}

这不是代理需要逆向工程的漂亮终端输出。这就是协议本身。

字典固定了字符串表。指标列顺序是显式的。文件行是密集的。事件是带类型的。句柄按其规范字符串形式作为键。可选的富化数据放在侧车(sidecar)里。

这个设计给了代理一条稳定的路径:

  1. 读取 schema 和契约版本。
  2. 解码字典。
  3. 对文件行排序或过滤。
  4. 检查带类型的事件。
  5. 用地址请求 explain 获取证据。

最终的答案可以是散文。但仪表应该输出结构。

entropyx 作为案例研究

entropyx 为每个文件测量七个维度:

  • D_n:变更密度
  • H_a:作者分散度
  • V_t:时间波动性
  • C_s:耦合压力
  • B_y:责备年龄(blame youth)
  • S_n:语义漂移
  • T_c:测试协同演化

综合分数有用,但它本身不是契约。每个分数都能分解成产出它的那些维度。

这就是测量和观点的区别。如果一个文件得分高是因为耦合压力大,助手应该说这个。如果分数是由没有测试协同演化的语义漂移驱动的,那是另一种说法。如果作者分散度在一个热门文件上上升,那是所有权问题,不是通用的风险标签。

基于规则的信令分类法也是协议的一部分:

  • incident_aftershock
  • coupled_amplifier
  • refactor_convergence
  • api_drift
  • ownership_fragmentation
  • frozen_neglect

事件流有五个变体:

  • rename
  • hotspot
  • incident_aftershock
  • ownership_split
  • api_drift

这些不是生成的解释。它们是带类型的输出。助手可以用它们、引用它们、过滤它们,并请求它们背后的证据。

这是核心的分工:

  • 可执行文件负责测量。
  • 协议负责保留结构。
  • 助手负责选择检查什么。
  • 助手负责向用户解释证据。

确定性就是代理的 UX

确定性不只是一个测试偏好。它是给代理的用户体验。

如果同一个仓库上运行同一个命令产生不同结果,助手就得去推理测量系统本身,而不是代码库。仓库变了吗?工具变了吗?时钟、随机种子、网络调用、并行归约或模型更新让结果移动了?

entropyx 对同一个仓库状态、entropyx 版本、参数和本地输入,保持核心扫描的确定性。

这意味着:

  • 核心测量层不使用墙上时钟读数
  • 确定性的浮点归约
  • 跨序列化往返的稳定驻留(interning)
  • 本地 git 历史作为真相来源
  • 确定性物理层没有 ML 评分
  • 版本化的协议契约

可选的 GitHub 富化是故意分离的。它可以将 PR 元数据附加到事件提交 SHA 上,但那个远程侧车不是本地测量的基础。

这个分离很重要。确定性核心可以缓存、diff、测试、签名和复现。远程富化可以添加上下文,而不会把核心发现变成依赖网络的论断。

对人类来说,这让输出可审计。对代理来说,这让输出可以安全地在上面构建。

证据先于解释

AI 助手擅长解释。但这不意味着工具应该让助手去凭空发明证据。

entropyx 从仓库开始。提交、diff、作者、rename、blame 快照、公开 API 变更、测试共变更、共变更图谱构成了测量层。助手不需要从原始 git log 输出推断整个历史,然后才能回答一个问题。

结果是更小、更可靠的循环。

而不是:

  1. 读 README。
  2. 猜该运行哪些 git 命令。
  3. 检查一堆 diff。
  4. 推断哪些文件重要。
  5. 希望答案有根据。

助手可以:

  1. 运行 entropyx describe
  2. 运行 entropyx scan
  3. 检查带类型的摘要。
  4. 请求 entropyx explain 获取那些重要的地址的证据。
  5. 写一个绑定到具体证据的答案。

这不是要取代工程判断。是给判断一个更好的输入。

同样的模式可以超越代码库取证应用。测试选择、依赖分析、迁移规划、安全审查、发布风险、文档漂移和合规证据都能从这个契约形状中受益:总结领域、暴露带类型的发现、让代理获取证明。

句柄让协议可导航

句柄可寻址的证据是这个模式最重要的部分。

摘要应该足够紧凑,可以作为地图阅读。它不应该把整个仓库倾倒到代理的上下文中。但没有深入能力的摘要只是另一个报告。

句柄桥接了这个鸿沟。

句柄是一个稳定的、面向用户的指针,从发现指向可以按需获取的证据。在 entropyx 中,文件句柄通过 blob 前缀内容寻址。提交和范围地址形式让 explain 直接解析 git 对象和发布窗口。

这给了助手一个清晰的工作流:

  • 读地图
  • 选区域
  • 获取证据
  • 引用地址
  • 只在需要的地方重复

这很重要,因为上下文是昂贵的,即使上下文窗口很大。一个一次吐出所有东西的工具,强迫助手在知道什么重要之前就为所有东西付费。一个句柄驱动的协议让助手把注意力花在证据指向的地方。

这也对人类有帮助。审查者可以用同样的句柄,运行同样的命令,检查同样的证据。句柄变成了共享的引用,而不是聊天记录里生成的不透明解释。

诚实的空缺

AI 系统容易被自信的谎言欺骗。工具不应该让这变得更糟。

entropyx 设计为在无法测量某事时返回显式的空缺。如果语言后端未知,语义漂移对该文件贡献为零。那个零的意思是"这个维度未测量",而不是"稳定"。如果可选的 GitHub 富化缺失,PR 侧车是空的。如果一个句柄无法解析,命令会干净地失败。

这不如一个总有答案的工具酷炫。但更有用。

一个 AI 优先的仪表不应该试图听起来很智能。它应该精确地说明它知道什么、测量从哪里来、以及证据在哪里停止。

助手就能说"扫描对这个文件类型没有测量语义漂移",而不是把沉默当作安全。

权衡

可执行的契约比非正式接口更不灵活。

这是特性,不是缺陷。

如果一个 JSON 字段是协议的一部分,改它应该感觉像改一个 API。如果退出码传达失败,弱化它应该破坏一个测试。如果一个 schema 版本绑定到契约版本,破坏性变更应该对消费者可见。如果一个命令发出句柄,接受的句柄形式应该被文档化并测试。

这产生了摩擦。应该的。

AI 代理需要稳定的边缘。CI 需要稳定的边缘。人类操作员在故障期间需要稳定的边缘。一个随意改变形态的工具,强迫每个消费者重新发现边界。

教训不是 CLI 永远不能演进。教训是演进应该是显式的。契约要版本化。尽量保持兼容。必要时就果断破坏。

蓝图

entropyx 模式泛化到其他 AI 优先的开发者工具。

从一个小可执行文件表面开始:

tool describe
tool schema
tool scan <target>
tool explain <target> <address>

只有当有明确角色时才添加领域特定命令,就像 calibrate 对 entropyx 做的那样。

然后让契约显式:

  • describe 暴露能力、输入、输出、成本和不变量。
  • schema 暴露机器可读的输出形状。
  • scan 产生目标领域的密集带类型地图。
  • explain 解析稳定地址为证据。
  • 退出码是承诺,不是装饰。
  • 结构化输出是自动化的路径。
  • 散文是给人类和最终答案的。
  • 可选网络富化是侧车数据,不是测量核心。
  • 空缺是显式的。
  • 破坏性变更要 bump 契约。

这就是提示词形状的工具和契约形状的工具之间的区别。

提示词形状的工具依赖工具周围的指令。契约形状的工具暴露代理能运行和验证的行为。

接下来会改变什么

下一代开发者工具不会被仅根据人类读文档的体验来评判。它们也会根据代理能多可靠地发现、运行、验证和组合它们的行为来评判。

这改变了 CLI 是什么。

它不是产品的包装层。

它是契约边界。

如果契约只存在于散文里,代理可能误解它。如果它只存在于内部测试里,代理可能永远看不到它。如果它存在于可执行文件表面里,代理可以运行它。

这是 AI 优先开发者工具应该达到的标准:不是文档化的意图,而是可执行的承诺。

entropyx 是这个想法的一个具体实现。它测量代码库历史、输出带类型的协议、保留确定性的本地证据,并让助手从摘要深入到证明。

更广泛的模式才是值得延续的部分。

构建代理能调用的工具。让它们描述自己。给它们 schema。给它们稳定的地址。让核心保持确定性。能本地化的证据就本地化。无法测量时就说实话。

代码知道的往往比故障室里通常记得的多。

工具的任务是以人类和代理都能信任的形式把它读回来。

归属与披露

由 Don / copyleftdev 来自 entropyx 项目撰写。

本文在 AI 辅助下起草和编辑,然后在提交前对照 entropyx 源码和 DEV 当前发布指南进行了审查。

原文链接:https://dev.to/copyleftdev/ai-tools-need-contracts-not-prompts-4b2j