能不能放手让 AI 自动跑,不取决于它多聪明,取决于有没有一个独立于它的裁判。
你手里的 AI 工作流里,每一个「自动化环节」背后都藏着一个问题:谁来判断这一步做对了? 软件测试里这叫 test oracle problem——判断「观察到的行为是否正确」的那个裁判。当裁判是编译器、类型检查、测试、property check 时,它独立于生成代码的模型,说对就是对;当裁判是「让 AI 再看一遍」「多个 agent 辩论」「self-consistency 投票」时,它和生成者共享失败模式——是同一个大脑批改自己的卷子。这一期不讲「怎么写更好的 eval」,讲一件更底层的事:给自己的整条 AI 流水线做一次 oracle 盘点,标出哪些环节有独立裁判、哪些是 AI 看 AI,然后按可验证性、而不是按任务难度重新画自动化边界。这决定了你哪些地方能真正放手,哪些地方一放手就是在放大一个没有裁判的系统。
Barr 等人 2015 年那篇被反复引用的 oracle 综述把「验证」拆成了一个很干净的问题:test oracle 就是能回答「这个输出对不对」的信息源。它按独立性和可自动化程度分四类,这个分类直接决定你能不能放手:
关键洞察在这:编译器、类型检查、单元测试、property-based test 是强独立 oracle;而「让 LLM 再检查一遍」「LLM-as-judge」「self-consistency」是弱 oracle 甚至伪 oracle——因为验证者和生成者是同一个(或同族)模型,它们的盲区高度重叠。你以为加了一道验证,其实只是让同一个大脑把答案又抄了一遍。盘点的第一步,就是对流水线里每个环节问:这里的裁判,是哪一类?
给你的工作流列一张 oracle 盘点表——每个自动化环节一行,标注裁判类型与独立性:
# 环节 裁判 类型 独立于模型?
生成函数实现 typecheck+pytest implicit/spec 是 ✅ → 可全自动
重构等价改写 回归测试快照 derived 是 ✅ → 可全自动
SQL 生成 在只读库上 EXPLAIN implicit 是 ✅ → 可全自动
文档摘要质量 LLM-as-judge 伪 oracle 否 ❌ → 需人抽检
「这段代码安全吗」 另一个 LLM 说安全 伪 oracle 否 ❌ → 需独立扫描/人
需求是否理解对 —(无) no oracle 否 ❌ → 必须人
贴在项目根目录。规则很简单:「独立于模型」那一列是 ✅ 的行,才有资格全自动放手;❌ 的行要么找到一个真 oracle,要么保留人在环。
为什么「独立」是核心词?做一点粗糙的概率直觉:如果 generator 漏检率是 p,再叠一道完全独立的 verifier(漏检率 q),双漏概率降到 p·q——平方级下降,这才是「双重检查」的价值来源。但如果 verifier 和 generator 的错误高度相关(相关系数 ρ→1),双漏概率退回到 ≈ p——你什么都没多买到。同一个模型自查、同族模型互查,正是 ρ 很高的情形:它们在同样的地方自信地错。
更糟的是 self-preference bias。Panickssery 等人 2024 年(NeurIPS)证明:LLM 评判者能认出自己的生成并系统性打更高分——人类看来质量相当时也如此。加上 Day 5 讲过的 backward rationalization、confidence inflation,「让 AI 检查 AI」不仅相关,还带正向偏袒。所以 multi-agent debate、self-reflection 这些看起来像「独立验证」的东西,本质仍是同族大脑的内部争论,共享同一套盲区。
破解办法只有一条主线:把裁判换成非 LLM,或换成与问题数学性质绑定的 oracle。metamorphic testing 是最典型的一招——不判单个输出对错,判多次执行之间的关系,关系来自问题本身而非模型:
# LLM 生成了一个 sort 实现,你没有「正确答案」当 oracle
# → 用 property + metamorphic 关系当独立裁判(hypothesis)
from hypothesis import given, strategies as st
@given(st.lists(st.integers()))
def test_idempotent(xs):
assert my_sort(my_sort(xs)) == my_sort(xs) # MR: 排序幂等
@given(st.lists(st.integers()))
def test_permutation(xs):
assert sorted(my_sort(xs)) == sorted(xs) # MR: 输出是输入的排列
@given(st.lists(st.integers()), st.integers())
def test_shift_invariant(xs, k):
assert my_sort([x+k for x in xs]) == [x+k for x in my_sort(xs)]
这三条 property 里没有一个「标准答案」,但它们联合起来是一张独立于 LLM 的过滤网——模型编不出既满足全部关系又是错的实现。这就是把「无 oracle 任务」硬转成「有 oracle 任务」的手艺。退一步的弱化版:至少让 verifier 用不同族模型(Claude 生成 → 换一族模型 + 显式扫描器交叉),能降低 ρ,但仍不如一个真正的非 LLM oracle。
大多数人画自动化边界的直觉是「简单的交给 AI,难的自己来」。这是错的坐标轴。正确的坐标轴是可验证性——一个环节有没有便宜、独立的 oracle。把它排成一道阶梯:
规则:越靠上,越该无脑放手让 AI 高速迭代——因为 generate-and-check 循环便宜又可信,AI 跑错了 oracle 会立刻拦下。越靠下,越该保留人在环。最危险的是 generate-verify gap 大的任务:生成一秒钟、验证要半小时。METR 2025 那项随机对照实验里,资深开发者用 AI 后完成时间反而多了 19%——一个主要来源就是验证成本被系统性低估:AI 生成飞快,但把「看起来对」验证成「真的对」的活全压回了人身上,而这段没有 oracle。
重排你的 agentic loop:只在有 oracle 的 sub-task 上开自动 retry,无 oracle 的 sub-task 一律 checkpoint 交人。
def step(task):
out = llm_generate(task)
oracle = ORACLE_FOR[task.kind] # 盘点表查出来的裁判
if oracle.independent: # ①② 阶梯上层
for _ in range(MAX_RETRY):
if oracle.check(out): return out # 通过才放行 (fail-closed)
out = llm_fix(task, oracle.feedback) # 独立反馈驱动自修复
raise NeedsHuman(task) # 试满了仍不过 → 交人
else: # ③④ 阶梯下层:不许自动放行
return checkpoint_to_human(task, out)
这段的灵魂是那个 if oracle.independent:它把「AI 能不能自转」这个决策,从「任务看起来简不简单」硬移到了「有没有独立裁判」。Day 3 讲过升级成 agent 的三条件之一就是「能验证最终输出对错」——这里给了它可操作的判据。
MAX_RETRY 自动重试的环节,先确认它上面挂着的是①②,不是③④。
规模化放大的从来不只是产出,还有没被 oracle 拦住的错误。手动跑十次,你眼睛就是那个(很贵但独立的)oracle;一旦自动化跑一万次,你的眼睛退场了,如果没有别的独立裁判补位,错误就以一万倍的速度流进下游。所以「个人版护栏」的本质是:在你自己的 harness 层,把独立 oracle 固化成自动 gate,在放手之前。一张升级前必答的检查清单:
四个都 ✅ 才有资格把这条流水线接上自动触发。任何一个 ❌,就是你规模化时会被反噬的地方。
在 Claude Code 里,护栏就是 hooks——把真 oracle 挂成 fail-closed 的门,不过就物理拦截,不靠模型自觉:
{
"hooks": {
"PostToolUse": [{
"matcher": "Edit|Write",
"hooks": [{"type":"command",
"command":"tsc --noEmit && pytest -q || exit 2"}] # 真 oracle,非 0 即拦
}],
"Stop": [{"hooks":[{"type":"command",
"command":"pytest tests/property -q || exit 2"}]}] # 派生 oracle
}
}
exit 2 是关键:Claude Code 的 hook 用非零退出把结果作为错误反馈回喂给模型并阻断本次动作——这就是把独立 oracle 变成 fail-closed gate。注意别把「格式类」hook(prettier / lint --fix)误当验证正确性:它们让代码能看,不保证跑对。护栏要分清「能跑」和「跑对」。
挑一条你已经在用的 AI 工作流(比如「Claude Code 改 bug → 提 PR」或「RAG 问答」),花半小时走一遍:
做完你会发现:真正卡住你规模化的,从来不是「模型不够强」,而是那几个没有独立裁判的环节。把它们找出来、要么造 oracle 要么留人——这就是个人 AI 工作流从「能用」到「敢放手」的分水岭。
sort(shift(x))==shift(sort(x)))。这个关系独立于模型:它来自排序的定义,不来自任何一次生成。所以「不需要 oracle」是指不需要点对点的参考答案,不是说不需要裁判——裁判换成了「关系必须成立」。这恰恰是最实用的一招:当你造不出标准答案时,去找问题里天然成立的关系。