先贴命令行。
mole research "为什么单线程就够用" --usd 0.50
Mole,lajosdeme 写的一个深度研究 agent,Apache-2.0,Go 编译出两个静态二进制,CGO_ENABLED=0,零运行时依赖。深度研究 agent 一抓一大把,我本来没打算细看。让我停下的是那个 --usd:它不是可选 flag。mole research 必须带预算,--usd 和 --tokens 二选一,互斥,没有默认值。
我第一反应是这交互挺烦的 😅——跑个研究还要先报价。后来想明白了:烦就是设计。没有默认预算,等于不存在“没定价就开跑”的路径。别的工具大多留着这个口子,它直接焊死。
然后看它怎么管这笔钱。三步:调用前,从余额里预留一笔;跑完,按实际花费结算;差额回补。而真正的那一行在数据库 schema 里——余额这列不许为负。
-- 大意
CHECK (balance >= 0)
预留不够,这次调用根本发不出去。注意顺序:不是花超了再报警,是不够就不让花。监控是事后的,告警到达时 token 已经烧完,你能做的只剩道歉;预留是事前的,扣不动就是扣不动。check-then-call 还有竞态,两个并发调用都瞄一眼余额、都觉得自己够,就都发出去了。预留放进一个事务里扣,这个竞态不存在。
跟酒店预授权一个道理。前台不知道你会不会喝冰箱里的可乐,但先冻住一笔上界的钱。
项目自己报的预算超支率是 0%。我第一反应是宣传话术,评测数字哪有正好是零的。看完机制我收回这句:0% 不是跑出来的,是定义出来的。schema 不收负余额,“超支”这个状态在数据结构里存不进去。唯一的口子是把某次调用的上界估低了,那是定价错,不是架构漏。
常见的做法长什么样,大家都熟。一个预算中间件,阈值到了发告警,超了走回调,回调里决定重试还是降级,再配个面板看曲线。
// 那套东西的形状
err := budget.Monitor(ctx, call,
WithAlertAt(0.8),
WithOnExceed(func(ctx context.Context) { /* ... */ }))
功能上都说得通。问题是这套东西每个钩子都是扩展点,每个扩展点都是将来要有人读的代码。为了管住一个数字,引入一整个中间件层。一行 CHECK 能表达的不变量,不该用一套回调系统来表达。
同一个手法在 Mole 里出现了第二遍:引用。每条主张必须带一段在来源页面里逐字出现的引文。找不到逐字引文,这条主张在提取阶段就被丢掉,不是降权,不是标“待定”,是丢。事后复核发现撑不住的,在报告里明着标出来。数据结构的形状我猜是这样:
// 我猜的
type claim struct {
text string
quote string // 必须在来源页面逐字出现,否则整条不成立
}
为什么非要逐字。因为模型给引用的时候,给的是“看起来对”的东西,它见过太多看起来对的引用。意译的结论、转述的观点、链接指向一个确实存在但说的不是这回事的页面,这些过得了人眼,过不了字符串比对。把校验降到字节这一层,模型骗不了它。
第三遍:本地数据。CSV 或者整个文件夹丢给它分析,原始数据不出机器。模型在这中间只干两件事:选一个假设模板,挑列名。SQL 由 Mole 渲染、Mole 执行,模型拿回来的只有计数、均值这类聚合值,和覆盖至少五条记录的桶。
模型决定:哪个模板、哪一列
Mole 执行:渲染 SQL、跑查询
模型拿到:聚合值、≥5 条记录的桶
原始行:从头到尾没出过门
那个“五条”不是随手写的。一条记录一个桶,等于把原始行换个马甲递出去;桶太小本身就是漏数据的口子。门槛焊在查询层,模型没有“给我看这一行”这个选项。对比常见的“AI 数据分析”——CSV 传上去,页面写一句“我们承诺保护您的隐私”。承诺是 prompt,桶的下限是 schema。它还有个 Mole Crossings,明着列出哪些数据离开过本地:不是承诺没送,是记账送了什么。
小地方也是同一个指纹。API key 放 ~/.config/mole/config.json,权限 0600,不进环境变量,不进 .mcp.json。mole serve 监听 Unix socket,权限同样 0600,别的用户连不上。这些不是功能列表里的条目,是同一条原则落在不同位置:不变量放在能强制它的那一层,而不是放在希望它成立的那一层。
它的 eval 记分卡值得单独看一眼:
预算超支 0%
引用准确率 100%
主张完整性 100%
重读确认率 80%
矛盾检测精度 70% 有确认 / 51% 无确认
读这张表有个办法:哪几个数字是满的,作者就把哪几件事焊死在了结构里。预算和引用归结构管,所以是 0 和 100;矛盾检测还得靠模型判断,停在 70 和 51。那个引用 100% 也别当奇迹看,撑不住的主张都扔了,剩下当然全对,你付的代价是召回:有些其实成立的主张也被扔了,而且不知道是哪些。代价没藏在数字后面,我觉得这比 100% 本身体面。
说句题外话。我手头那个玩具存储引擎,块大小 64KB,块内偏移用 uint16 存。当初纯粹想省两个字节,后来才反应过来顺手干了件大事:16 位的偏移在算术上就指不到自己的块外面。数据可以坏成 garbage,坏不成“越界去隔壁块”。不是我防住了越界,是越界这个状态存不进那个字段。
扯回来。所以规则就一条:凡是写在提示词里的约束,先问一句——能不能挪进类型、schema、文件权限、查询门槛。挪得动的,挪。提示词是说给概率体听的话,schema 是跟确定性体签的合同,后者不会忘,也不会被下一段上下文冲掉。
反正我没见过哪段提示词,比一行 CHECK 活得久。