04—为什么不能让 AI 自己评审自己?AI Skill 四层验证体系完整解析

14 阅读18分钟

为什么不能让 AI 自己评审自己?AI Skill 四层验证体系完整解析

系列:SkillSentry · AI Skill 测评体系从零到一(四)

难度:深入

适合读者:想构建可信测评机制的工程师

📌 一句话摘要:让模型自判卷会产生系统性偏差,本文拆解 AI Skill 四层验证体系如何用独立 Grader Agent 和盲测 Comparator 彻底消除自判卷问题。

🏷️ 推荐标签:AI Skill四层验证 LLM自判卷偏差 独立Grader Agent


问题:为什么不能让模型自己评审自己

让模型自己评审自己的输出,会产生「自判卷偏差」——这是 AI Skill 四层验证体系要解决的核心问题。 最朴素的测评思路:让模型执行任务,然后让模型判断结果是否正确。

这有一个根本缺陷:执行者和评审者是同一个模型

实验中观察到的现象:

  • 同一个用例,with_skill 跑出来通过率 100%,without_skill 也是 100%
  • 展开看 evidence,发现很多断言的 evidence 是空的,或者只写了「根据整体输出判断通过」
  • 这说明评审是「感觉通过」,不是「有证据通过」

这就是「自判卷偏差」。


解决思路:四层验证,各司其职

四层验证体系的核心思路是:把执行、精确校验、语义评审、盲测对比四件事彻底分开,每层只负责自己最擅长的部分。 每层解决不同的问题,互相补充:

Layer 0: 执行模式分发
    → 检测 Skill 类型(mcp_based / code_execution / text_generation)
    → 选择对应执行模式,分发给 Layer 1

Layer 1: Executor(执行层)
    → 真实执行 Skill,记录所有证据(transcript + response + metrics)

Layer 2a: 字段精确校验(Ground Truth)
    → 从 transcript 直接提取字段值,assertEqual / 正则匹配
    → 不经 LLM,完全客观

Layer 2b: 独立 Grader Agent(语义评审)
    → 独立 LLM 读 transcript + response,找原文 evidence
    → 执行者和评审者完全分离
    → 支持 MCP / 代码执行 / 纯文本 三种评审标准

Layer 3: 盲测 Comparator + Analyzer(正常路径 + E2E 必跑)
    → Comparator 不知道哪个是 with_skill,纯质量评分
    → Analyzer 解盲,定位胜负根因

Layer 1:Executor

职责:真实执行 Skill,记录完整证据。没有 Layer 1 的 transcript,后续所有验证层都无法工作。

执行模式自动分发

在 Layer 1 执行前,系统先检测 Skill 类型并选择对应模式:

Skill 类型执行模式transcript 内容
mcp_based真实 MCP 工具调用完整工具调用记录(入参、返回值、报错)
code_execution真实 Bash/脚本执行命令执行记录 + 输出文件内容
text_generation纯文本模式模型推理过程 + 最终输出(无工具调用)

文件系统隔离规则

审计发现:without_skill subagent 会通过读取同一 workspace 目录,「借用」with_skill 已上传的文件 URL,导致 Δ 被系统性低估,掩盖真实能力差距。

强制隔离结构

eval-N/
├── with_skill/
│   ├── workspace/     ← with_skill 专属沙箱,只有它能读写
│   └── outputs/
│       ├── transcript.md
│       └── response.md
└── without_skill/
    ├── workspace/     ← without_skill 专属沙箱,完全隔离
    └── outputs/
        ├── transcript.md
        └── response.md

三条强制规则

  1. without_skill subagent 只能读取自己的 workspace/禁止读取 with_skill 下的任何文件
  2. without_skill 执行失败时,记录真实失败,不允许降级复用 with_skill 的中间产物
  3. 启动 without_skill subagent 的 prompt 必须明确写:「禁止读取 with_skill 目录下的任何内容」

不做隔离的后果:Δ 数字是虚假的。without_skill 表现比「真实无 Skill 情况」更好,Skill 的增益价值被低估,可能错误地认为「Skill 没太大用」。


transcript 双分离格式

transcript 把 MCP 原始返回值和 AI 主观解释混在一起是个信任漏洞。例如:"按任务执行要求,自动选择第一个候选项" — 这是 AI 加的注释,不是 MCP API 的原始数据。Grader 读这份混合日志来判断 evidence,相当于「用 AI 的笔记证明 AI 的行为」。

新格式:强制双区块分离

## [tool_calls] Step 5: saveExpenseDoc
<!-- 原始工具调用日志,禁止添加任何 AI 注释,一字不改复制 -->
Tool:   expense-mcp_saveExpenseDoc
Args:   {"docStatus":"10","expenseType":"1","fdApplyMoney":"168.00",...}
Return: {"code":"200","body":"a1b2c3d4"}
Status: success

## [agent_notes] Step 5: 草稿已保存
<!-- AI 主观解释区,清晰标注这是解读而非原始数据 -->
解读:saveExpenseDoc 成功,fdId=a1b2c3d4,草稿状态为 10(未提交审批)

Grader 引用优先级

优先级 1[tool_calls] 区块  → 系统原始数据,可信度最高,PASS 的首选来源
优先级 2:response.md / outputs 文件  → 最终呈现内容,可信度高
优先级 3[agent_notes] 区块  → AI 主观解释,仅辅助参考,不能作为 PASS 的主要证据

规则:如果只有 [agent_notes] 支撑、无 [tool_calls] 佐证 → 强制 FAIL,并在报告中标注「⚠ evidence 来自 agent_notes,可信度较低」。


产出文件

with_skill/
├── workspace/         ← 专属沙箱
└── outputs/
    ├── transcript.md  ← 双分离格式([tool_calls] + [agent_notes] 严格分区)
    ├── response.md    ← 最终向用户呈现的内容
    └── metrics.json   ← 工具调用统计
timing.json            ← 执行耗时 + tokens(来自 task notification,只有这一次机会)

timing.json 格式

{
  "executor_start_ms": 1711234567000,
  "executor_end_ms":   1711234573500,
  "duration_ms":       6500,
  "total_tokens":      2340,
  "input_tokens":      1200,
  "output_tokens":     1140
}

关键设计

  • with_skill 和 without_skill 必须在同一轮同时启动,不能先跑一个再回来跑另一个
  • timing.json 必须在 task notification 到达时立即保存,错过就永久丢失
  • [tool_calls] 区块内容来自真实 MCP/Bash 返回,一字不改,禁止任何 AI 解释语句

Layer 2a:字段精确校验(Ground Truth)

Layer 2a 是四层中最可信的一层,因为它完全不经过任何 LLM,是纯文本匹配——相当于软件测试里的 assertEqual 对可以精确匹配的断言做客观验证,不依赖 LLM 判断。

为什么 Layer 2a 是四层中最可信的:无论模型当天状态如何、temperature 怎么设,同一个 transcript 跑 Layer 2a 永远得到同样的结论。不受模型幻觉、评审偏见、随机性影响。这是 Layer 2b 和 Layer 3 做不到的。

适合验证的断言类型

接口调用次数:saveExpenseDoc 调用了几次(计数)
固定参数值:docStatus == "10"(精确匹配)
链接格式:不含字面占位符 {fdId}(正则)
字段值:fdMonthOfOccurrence 是否等于当前月份计算值(精确匹配)

不适合 Layer 2a 的断言(转交 Layer 2b)

「输出格式是否对用户友好」→ 主观,需要 LLM 评审
「报销主题内容是否合理」→ 语义性,需要 LLM 评审
「错误提示是否清晰」→ 语义性,需要 LLM 评审

产出文件ground_truth.json

{
  "checks": [
    {
      "assertion": "saveExpenseDoc 参数 docStatus=10",
      "method": "transcript_search",
      "passed": true,
      "evidence": "transcript Step5 入参:{\"docStatus\":\"10\",\"expenseType\":\"1\",...}"
    },
    {
      "assertion": "详情链接不含字面占位符 {fdId}",
      "method": "regex",
      "passed": false,
      "evidence": "response.md 第12行链接中仍含 {fdId} 占位符"
    }
  ]
}

这一层的价值:绑定了最可信的 evidence——直接从 transcript 提取的原文,不经过任何 LLM 推断。


Layer 2b:独立 Grader Agent

独立 Grader Agent 通过强制引用原文 evidence,从根本上消除「感觉通过」的评审方式——找不到原文支持,就是 FAIL。 用独立 LLM 评审语义性断言。

与 Layer 2a 的分工

  • Layer 2a 处理:可以精确匹配的断言
  • Layer 2b 处理:需要理解语义的断言

三种评审标准按 Skill 类型切换

Grader 收到 skill_type 参数,自动切换对应评审规范:

skill_typeevidence 来源特殊注意点
mcp_basedtranscript 中的工具调用记录验证入参/返回值/调用次数
code_executionBash 调用记录 + 输出文件内容验证命令正确性和文件内容
text_generationresponse.md 原文段落无工具调用,从输出文本找证据

纯文本 Skill 的 evidence 规范

✅ 好的 evidence(纯文本版):
  "response.md 第3段:'本次分析覆盖了以下三个维度:1.性能 2.安全 3.可维护性'
   ——三个维度均已呈现,满足断言"

❌ 不可接受:
  "从整体输出来看,内容比较完整"
  "AI 似乎理解了用户意图"

Grader Agent 的工作方式

Step 1:读取 skill_type,选择评审标准
Step 2:读取 transcript.md 或 response.md(根据类型)
Step 3:对每条断言:找原文 → 判定 PASS/FAIL → 必须 quote 具体文字
Step 4:提取隐含 claims 并验证(幻觉检测)
Step 5:读取 user_notes.md(执行者在运行时记录的疑问)
Step 6:批评断言质量(eval_feedback)
Step 7:读取 timing.json,将耗时/Token 数据写入 grading.json

grading.json 新增字段

{
  "skill_type": "text_generation",
  "timing": {
    "executor_duration_ms": 6500,
    "total_tokens": 2340,
    "input_tokens": 1200,
    "output_tokens": 1140
  }
}

user_notes.md:执行层(Layer 1)在运行过程中记录的疑问,例如「Step 3 接口返回了异常状态码但未中断,不确定是否符合预期」。Grader 读取这些注记后可以有针对性地重点评审对应断言。

evidence 强制非空的意义

强制引用原文,消除了「感觉通过」的评审方式。如果找不到原文支持,就是 FAIL。

幻觉检测(最有价值的额外工作)

Grader 不只评审预定义的断言,还会从输出中提取「隐含声明」并逐一核查:

隐含声明:「saveExpenseDoc 调用了一次」
类型:factual
验证结果:true
证据:transcript 中 saveExpenseDoc 出现 1 次(Step5)

隐含声明:「fdMonthOfOccurrence 取当前月份 120260200」
类型:factual
验证结果:false
证据:checkExpenseItem 入参 fdMonthOfOccurrence=120251100(发票月而非提单月)

这里发现的问题,是预定义断言可能遗漏的。

断言质量建议(eval_feedback)

Grader 还会批评断言本身的质量,例如:

「『输出包含报销金额』只检查了存在性,未验证金额与发票一致,幻觉金额也会通过。建议改为:报销金额等于发票识别金额」


Layer 3:盲测 Comparator + Analyzer(正常路径 + E2E 必跑)

盲测 Comparator 通过让评审者不知道哪个是 with_skill,从机制上消除「对好版本更宽容」的评审偏见。 深度比较 with_skill 和 without_skill 的输出质量。

触发规则:正常路径用例(Happy Path)和 E2E 用例完成后必须执行,不可跳过。其他类型用例不触发。

为什么要盲测:Layer 2b 的 Grader 知道哪个是 with_skill,可能存在「对好版本更宽容」的评审偏见。Comparator 不知道 A/B 哪个是 with_skill,只看内容质量打分,消除了这种偏见。

Comparator 评分维度

内容维度(Content):
  正确性(Correctness)  1-5 分
  完整性(Completeness) 1-5 分
  准确性(Accuracy)     1-5 分

结构维度(Structure):
  组织性(Organization) 1-5 分
  格式规范(Formatting) 1-5 分
  可用性(Usability)    1-5 分

综合得分 = (内容均值 + 结构均值) / 2 × 2(换算 1-10 分)

Analyzer 做什么:Comparator 评完分后,Analyzer 解盲(得知哪个是 with_skill),定位胜负根因,生成可操作的改进建议:

{
  "improvement_suggestions": [
    {
      "priority": "high",
      "category": "instructions",
      "suggestion": "将含糊的「处理发票」描述替换为明确的 3 步操作序列",
      "expected_impact": "消除歧义,防止模型自行发挥"
    }
  ]
}

四层之间的关系

四层验证的关系是:Layer 0 分发,Layer 1 是基础,Layer 2a/2b 互补,Layer 3 是深化——每一层都依赖前一层的产出,缺少任何一层都会留下验证盲区。

Layer 0 是前提
    → 确定 Skill 类型,选对执行模式,并设置文件系统隔离沙箱

Layer 1 是基础
    → 没有 transcript,Layer 2 和 3 都无法工作

Layer 2a 和 2b 互补
    → 精确字段 → Layer 2a,语义内容 → Layer 2b
    → 最终的 grading.json 合并两层结果

Layer 3 是深化
    → 只在重要用例(Happy Path / E2E)上执行
    → 发现 Layer 2 发现不了的质量问题

grading.json 中的字段,标注每条断言来自哪一层、强度如何:

{"text": "docStatus=10", "precision": "exact_match", "passed": true,
 "evidence_source": "tool_calls", "method": "ground_truth", "evidence": "..."}

{"text": "输出包含报销主题", "precision": "semantic", "passed": true,
 "evidence_source": "response", "method": "grader", "evidence": "..."}

{"text": "输出非空", "precision": "existence", "passed": true,
 "evidence_source": "response", "method": "grader", "evidence": "..."}

报告中 ★精确 + [ground_truth] 标签的断言可信度最高,○存在性 断言不计入准入判断。


quick 模式最少运行 2 次

单次运行无法判断结论是否稳定。大模型每次运行结果略有不同,单次测评通过率 80%,可能下次跑是 60%,也可能是 95%。

规则:quick 模式强制运行最少 2 次,取均值。

两次通过率均值 → 最终通过率(不取最优)

两次差距 ≤ 15% → 正常,标注「quick 2次」
两次差距 > 15% → 报告标红,触发警告:
  ⚠️ 结果不稳定(差距 XX%),建议升级 standard 模式
  发布决策自动从 PASS 降为 CONDITIONAL PASS

为什么不取最优次:取最优会掩盖不稳定性。测评的目的是发现问题,不是证明 Skill 能在最好情况下通过。


与 skill-creator 的对应关系

我们的四层验证体系借鉴并扩展了 skill-creator 的设计:

我们的层skill-creator 对应改进点
Layer 1Step 1(spawn runs)+ Step 3(capture timing)增加 transcript 格式规范
Layer 2a无(我们新增)精确字段校验,来源于软件测试 assertEqual 思路
Layer 2bagents/grader.md汉化,增加 evidence 强制非空约束
Layer 3agents/comparator.md + analyzer.md汉化,增加 E2E 必须执行 Layer3 的规则

小结

解决的核心问题产出可信度触发时机
Layer 0不同 Skill 类型执行方式不同;with/without 文件系统污染skill_type 分发 + 独立 workspace 沙箱每次测评启动时
Layer 1没有真实执行记录,评审无原材料transcript(双分离)+ response + timing.json基础每个用例必跑
Layer 2aLLM 评审精确字段不可靠ground_truth.json最高Layer 1 完成后立即执行
Layer 2b执行者自判卷;agent_notes 被当原始数据grading.json(含 precision/evidence_source/claims)Layer 1 完成后立即执行
Layer 3评审者知道哪个是「好版本」存在偏见comparison.json + analysis.json高(盲测)仅正常路径 + E2E 用例
quick 稳定性单次运行无法判断结论稳定性均值通过率 + 稳定性差距quick 模式必须跑 2 次

工程细节:测评进度的可见性设计

测评进度的可见性设计解决的核心问题是:十几个子步骤如果全部暴露给用户,界面会非常嘈杂,5 个里程碑节点恰好对应用户需要参与决策的关键点。 这套四层验证体系有一个配套的工程细节值得单独说一下。

测评工具在启动时写入 5 个 todo,用户在界面上始终可见:

用户层 todo(5 个)——用户在界面上看到的

1/5】📋 准备阶段:分析 Skill + 收集测评所需信息
【2/5】⚙️ 方案确认:选择模式 + 确认用例清单
【3/5】🚀 执行测评:分批运行所有用例(四层验证)
【4/5】📊 评分分析:汇总指标 + 覆盖率检查 + 质量清单
【5/5】📄 生成报告:输出 HTML 报告 + 解读指引

每完成一个阶段立即标记完成,用户始终知道当前在第几步、下一步需要做什么决策。

这个设计解决的问题:测评流程有十几个子步骤(MCP 检测、规则提炼、用例设计、分批执行……),如果每个步骤都暴露给用户,界面会非常嘈杂。5 个里程碑节点恰好对应「用户需要参与决策的关键点」,其余执行细节由 AI 自动完成,不打扰用户。

下一篇,我们来深入拆解 skill-creator 的源码——它的触发率测评和 description 自动优化循环是怎么实现的。


附录:与 ADK 5 大设计模式的对应关系

SkillSentry 的四层验证体系与谷歌 ADK 的 Reviewer 模式直接对应(独立评审、权责分离),Pipeline 模式驱动整个测评流水线。完整分析见第八篇。

ADK 模式SkillSentry 对应核心解决的问题
ReviewerGrader/Comparator/Analyzer 三个独立 Agent执行者自判卷
Pipeline阶段零→六的流水线 + 量化准出标准信息不完整时跳步
Invokereferences/ 文件精确触发条件漏读规则或全量读浪费 Token
Tool Wrapper8 种异常标准话术表MCP 不可用时 AI 自由发挥
Generator报告前置检查 + 章节强制校验AI 跳过关键章节

FAQ:关于 AI Skill 四层验证,用户常问的问题

为什么不能让模型自己评审自己的输出?

让执行者和评审者是同一个模型,会产生「自判卷偏差」。实验中可以观察到:同一个用例,with_skill 和 without_skill 的通过率都是 100%,但展开看 evidence 发现大量断言的 evidence 是空的,说明模型在「感觉通过」而不是「有证据通过」。这种偏差会让测评结论完全失去可信度,无论跑多少用例都没有意义。

四层验证体系各层分别解决什么问题?

四层各有分工:Layer 1(Executor)负责真实执行并记录完整 transcript,是其余三层的原材料来源;Layer 2a(Ground Truth)用精确文本匹配校验可量化的字段,不经过任何 LLM,是最可信的一层;Layer 2b(独立 Grader Agent)用独立 LLM 评审语义性断言,强制引用原文 evidence;Layer 3(盲测 Comparator)让评审者在不知道哪个是 with_skill 的情况下打分,消除评审偏见。四层合并的结论比任何单一验证机制都可信。

什么是 Ground Truth 精确校验,和 LLM 评审有什么区别?

Ground Truth 精确校验(Layer 2a)是纯文本匹配,相当于软件测试里的 assertEqual——直接从 transcript 提取字段值,做计数、精确匹配或正则匹配,完全不经过任何 LLM。LLM 评审(Layer 2b)则是让独立 Grader 理解语义、判断断言是否成立。前者的结论不受模型随机性影响,同一个 transcript 永远得到相同结论;后者更灵活,能处理语义性的断言,但可信度依赖 evidence 是否有原文支撑。两者互补,不可替代。

盲测 Comparator 是如何消除评审偏见的?

Comparator 在评分时不知道 A/B 哪个是 with_skill 输出,只看内容质量本身,从六个维度(正确性、完整性、准确性、组织性、格式规范、可用性)独立打分。评分完成后,Analyzer 才解盲——得知哪个是 with_skill——再定位胜负根因并给出改进建议。这种「先盲评,后解盲」的机制,确保评分过程不受「我知道这个是好版本」的主观预期影响。

evidence 字段为什么必须引用原文?

evidence 强制非空是对抗「感觉通过」的核心机制。如果允许 Grader 写「根据整体输出判断通过」这类无来源的评语,那这条断言的判定结果就完全依赖模型当天的状态,不具备可重复性和可审计性。强制引用原文意味着:找到了原文 → PASS;找不到原文支持 → FAIL。这让每一条断言的判定结果都有据可查,报告中的通过率才是真实可信的数字。

without_skill 执行结果为什么会「虚假地好」?(P0 文件隔离问题)

真实审计发现:without_skill subagent 通过读取 with_skill 已上传的文件 URL,「借用」了 with_skill 的中间产物,最终成功完成了任务——这本应是 without_skill 做不到的事。两个 agent 虽然对话上下文隔离,但它们写入同一个 workspace 目录,without_skill 可以读取 with_skill 的产出文件。这会导致 Δ 被系统性低估,掩盖 Skill 的真实增益价值。解决方案是给两个 subagent 各自分配完全独立的 workspace 沙箱,并在启动 without_skill 时明确禁止读取 with_skill 目录。

为什么 transcript 里的 AI 注释会影响 Grader 的可信度?(P2 双分离)

transcript 里会出现「按任务执行要求,自动选择第一个候选项」这类语句——这是 AI 加的主观解释,不是 MCP API 的原始返回数据。Grader 读这份混合日志来判断 evidence,本质上是「用 AI 的笔记证明 AI 的行为」。解决方案是把 transcript 强制分为 [tool_calls](原始系统数据)和 [agent_notes](AI 主观解释)两个区块,Grader 优先引用 [tool_calls] 作为 evidence,[agent_notes] 降权为辅助参考,只有 agent_notes 支撑而无 tool_calls 佐证的断言直接判 FAIL。

quick 模式为什么要跑 2 次而不是 1 次?

大模型每次运行结果略有不同。单次测评通过率 80%,可能下次跑是 60%,也可能是 95%——这不是 Skill 质量差,而是随机性。quick 模式强制最少跑 2 次,取均值作为最终通过率(不取最优)。两次差距 > 15% 时报告标红「结果不稳定」,并将发布决策从 PASS 降为 CONDITIONAL PASS,提示用户升级 standard 模式(3 次运行)以获得可信结论。