docs(harness): add LLM judge design note (interview Q&A)

This commit is contained in:
zhuyongxin
2026-08-10 11:34:37 +08:00
parent da18fdf4e1
commit de56551fea
2 changed files with 144 additions and 2 deletions
@@ -0,0 +1,140 @@
# Harness LLM Judge 设计笔记:从不可信判定到可信裁决
**更新日期**:2026-08-04
**主题**:SemanticGuard + EvidenceRepair + GuardModelCall = LLM-as-a-judge 模式在证据安全链的完整落地(面试问答版)
**代码位置**:`src/main/java/com/superbiz/agent/harness/guard/semantic/` + `src/main/java/com/superbiz/agent/harness/release/EvidenceRepair.java`
## 1. 定位:三个角色
```text
SemanticGuard → 典型 LLM judge:判「结论是否被已验证证据支持」,输出 verdict + reason
EvidenceRepair → judge 的修复延伸(rewriter):验真失败后「只修引用、不修结论」
GuardModelCall → 受控 LLM 调用底座:judge 类调用的基础设施(共用)
```
## 2. 面试五段式回答稿(完整叙事)
### ① 动机(先讲问题,不报组件名)
> 我们的证据安全链里有一道「机械验真」——检查模型引用的每条证据是不是真实来自工具结果,这个用规则就能做。但光验真不够:模型可能引用真实的证据,结论却是「站不住」的——比如证据只支持 A 场景,它却拿去支撑 B 结论。这个「结论被没被证据支持」是**语义判断**,规则引擎做不了,必须靠模型。所以我们需要一个「裁判模型」来判——但裁判模型本身是不可信的,它可能乱判、可能输出奇怪的形状、可能跑很久。所以核心问题是:**怎么让一个不可信的模型做可信的判定**。
### ② 决策(方案 + 放弃了什么)
> 我的方案是:用**隔离的轻量判定模型**——单轮、无工具、输出被强约束,跟主 Agent 的循环完全分离。这里放弃了两条路:第一,让主 Agent 自己判——不行,它已经写了自己的结论,有偏向;第二,纯规则判——语义判断规则做不到。同时有个关键决策:**判读的输入是「视图」不是原始内容**——裁判只看到用户将看到的内容和已验证证据,看不到内部 id 这些实现细节,防止信息污染影响裁判的客观性。
### ③ 实现(关键机制)
> 三个关键机制:
> **输入视图化**:把 draft 投影成「用户可见视图」再交给裁判,剥离内部引用 id;
> **输出硬校验**:裁判的输出必须是恰好两个字段——verdict 和 reason,verdict 必须是合法枚举,reason 不能为空。多一个字段都不接受——我们不信任模型输出的形状,只信它在一个极小的空间里做选择;
> **受控调用**:裁判跑在独立线程、有硬超时、Run 取消能强杀它、它的输入输出都计入预算和 Token 账本——裁判的花费不是无底洞,它也是 Run 的一部分。
### ④ 边界(诚实说不做什么)
> 裁判不判「内容对不对」——那是事实问题,由证据链负责;裁判不自己调工具,单轮无工具;裁判有硬截止线,超时就放弃判定;裁判失败走降级,**不阻塞主结论的发布路径**——我们宁可没有裁决,也不让裁决失败卡死整个流程。
### ⑤ 30 秒话术
> "LLM judge 的完整设计:**动机**是结论的支持度是语义判断、规则做不了,但裁判模型不可信,所以核心是让不可信的模型做可信的判定。**方案**是隔离的轻量判定模型——单轮、无工具、强约束输出。三个关键机制:输入视图化(裁判只看用户可见内容,防信息污染)、输出硬校验(恰好 {verdict, reason} 两字段,多一个都不接受)、受控调用(独立线程、硬超时、取消强杀、计预算记账)。**边界**:裁判不判事实、不调工具、超时即放弃、失败走降级不阻塞主路径。总结一句话——judge 不是追加一个模型调用,而是把『不可信判定』关进笼子里:限定输入、锁死输出、受控运行、失败降级。"
## 3. 追问应对大全
### Q1:为什么 judge 不判事实?(最容易混的边界)
```text
分工:事实由证据链保证,judge 只判「支持关系」
事实真伪 → 证据来自真实工具结果 + EvidenceGuard 验引用真实(根在数据源)
支持关系 → SemanticGuard 判结论与证据的逻辑/相关性
judge 判不了事实的三个原因:
① 没有事实源——它只看「视图 + 已验证证据」,不能查库,判事实只能猜
② 事实真伪需要权威源复核(真实值在哪),judge 拿不到
③ 如果 judge 判事实,它成了第二个事实来源——两个来源可能打架
例子 1(judge 能判的——判支持不是判真伪):
证据:mysql 返回 count(*)=1000;结论:「user 表有 2000 条」
EvidenceGuard 验引用真实 → 通过;SemanticGuard → UNSUPPORTED(数字不一致)
注意:judge 不知道真实值是多少,它只发现「结论与证据不一致」
例子 2(支持关系成立,但事实未必对):
证据:慢查询日志显示 DB 全表扫描;结论:「延迟由 DB 全表扫描导致」
SemanticGuard → SUPPORTED(逻辑上站得住)
但真实原因可能是网络抖动——judge 判不了(没有网络数据源)
→ judge 只能保证「在现有证据下结论站得住」,不能保证「事实就是如此」
一句话:EvidenceGuard 保证「引用的证据是真的」,SemanticGuard 保证
「基于这些证据结论说得通」——事实的真伪从来不是 judge 的职责。
```
### Q2:为什么重试 2 次(semanticGuard 策略)?
```text
可重试性分析:语义审查单轮、无副作用(幂等)——多试几次不会造成破坏
但也不能无限重试:判定有硬截止线(总超时耗尽即放弃)
→ 2 次 = 一次失败的成本 × 收益的平衡点;judge 失败走 Fallback,不影响主路径
```
### Q3:语义不变性怎么保证(EvidenceRepair)?
```text
双重锁死:prompt(只能改 analysis_id / tool_call_ids / based_on_analysis_ids 三字段)
+ SemanticDraftView.hasSameUserVisibleSemantics(修复前后逐字段比对)
关键:比的是「用户可读的内容」不是内部引用 id——
Conclusion 只比 text,不比 basedOnAnalysisIds
Action 只比 action + requiresHumanConfirmation,不比 basedOnAnalysisIds
→ 引用 id 允许变(这正是修复目标),用户看到的文字不许动(碰了判 SCHEMA_INVALID 重试)
```
### Q4:judge 判错了怎么办?
```text
judge 不是最终真相源,是「安全链的一道闸」:
① judge 判 UNSUPPORTED → 不发布,走 Fallback(宁可保守)
② judge 判 SUPPORTED 但事实错 → 那是事实问题,不在 judge 职责(见 Q1)
③ judge 自身失败 → 降级(不阻塞主路径)
→ 设计哲学:judge 的角色是「挡住明显不成立的结论」,不是「证明结论正确」
```
### Q5:为什么独立线程 + 单独超时?
```text
judge 调用不能阻塞主流程(主 Agent 循环)
独立线程 + future.get(timeout) = 硬超时截断
Run 取消 → future.cancel(true) 强杀在途判定(judge 也是 Run 的一部分)
```
### Q6:输入为什么视图化?
```text
judge 只看该看的:用户可见内容 + 已验证证据
剥离内部 id(tool_call_id 等)——防止 judge 用内部信息做「看起来合理」的裁决
(信息污染:judge 看到内部 id 可能产生不当关联,或泄露内部结构到裁决)
```
## 4. 通用 LLM Judge 设计要素(可迁移)
| 通用要素 | 本项目实现 |
|---|---|
| 判什么(judgment task) | 结论是否被已验证证据支持 |
| 输入视图(该看什么) | SemanticDraftView(剥离内部 id)+ 已验证证据 |
| 输出约束(schema) | 恰好 {verdict, reason} + 枚举合法 + reason 非空 |
| 硬校验 | 字段集 equals({verdict, reason})——不多不少 |
| 隔离 | 单轮、无工具、独立线程——judge 不能自己调工具 |
| 硬超时 | 每次 attempt 剩余超时递减,总超时耗尽即放弃 |
| 重试策略 | 可重试性分析:单轮无副作用 → 2 次 |
| 失败降级 | judge 失败 → Fallback(不卡死主路径) |
| 可审计 | ModelCallLedger 记账 + semanticAttempt/evidenceRepairAttempt trace |
| 成本控制 | 输入/输出字节限制 + reserveRunBytes 计入 Run 预算 |
## 5. 代码位置索引
| 类 | 文件 |
|---|---|
| `GuardModelCall` | `src/main/java/com/superbiz/agent/harness/guard/semantic/GuardModelCall.java` |
| `SemanticGuard` | `src/main/java/com/superbiz/agent/harness/guard/semantic/SemanticGuard.java` |
| `SemanticDraftView` | `src/main/java/com/superbiz/agent/harness/guard/semantic/SemanticDraftView.java` |
| `SemanticGuardInput` / `SemanticGuardLimits` | `src/main/java/com/superbiz/agent/harness/guard/semantic/` |
| `EvidenceRepair` | `src/main/java/com/superbiz/agent/harness/release/EvidenceRepair.java` |
| `EvidenceRepairLimits` / `EvidenceRepairPrompt` | `src/main/java/com/superbiz/agent/harness/release/` |
| 重试策略(semanticGuard/evidenceRepair) | `src/main/java/com/superbiz/agent/harness/retry/HarnessRetryPolicies.java` |
@@ -111,6 +111,7 @@
| [Harness contract 状态流学习笔记-11个状态枚举的正交全景](Harness%20contract%20状态流学习笔记-11个状态枚举的正交全景.md) | 五层状态/四正交轴/纵向映射链/真实数据案例/面试叙事模板与追问应对 | ✅ 已沉淀 |
| [Harness 面试复习笔记-五步复习与白板图沉淀](Harness%20面试复习笔记-五步复习与白板图沉淀.md) | **详细版**:30 秒陈述展开/三张白板图/九域五段式讲法(动机→决策→实现→边界→话术)/六易错点带原因/追问应对大全/支付超时案例/纠正认知清单/面试 Checklist | ✅ 已沉淀 |
| [Harness agent 域学习笔记-从框架 ReAct 接入到受控停止](Harness%20agent%20域学习笔记-从框架%20ReAct%20接入到受控停止.md) | agent 域:装配(Factory 粘合点)/双拦截器(Model 预算+Token 审计、Tool 五道门)/UseCase 循环外壳/受控停止/双视图投影/串行工具 | ✅ 已沉淀 |
| [Harness LLM Judge 设计笔记-从不可信判定到可信裁决](Harness%20LLM%20Judge%20设计笔记-从不可信判定到可信裁决.md) | LLM-as-a-judge 模式:SemanticGuard(判支持度)+ EvidenceRepair(修引用)+ GuardModelCall(受控底座);面试五段式回答稿 + 六追问应对 + 通用要素 | ✅ 已沉淀 |
## 3. 一次请求的完整学习主线
@@ -138,8 +139,9 @@ flowchart LR
5. 2 分钟支付超时案例(复习笔记 §7)
可选深化(不阻塞面试):
1. audit 域深化:RagLookupAuditEnricher 检索审计明细
2. 按需深入:模型步 hook 细节 / DiagnosisChatExecutor 恢复路径 / SSE 序列化
1. audit 域深化:RagLookupAuditEnricher 检索审计明细(已覆盖大半)
2. LLM Judge 设计(已沉淀:面试问答 + 追问应对)
3. 按需深入:模型步 hook 细节 / DiagnosisChatExecutor 恢复路径 / SSE 序列化
```
## 5. 建议每次学完一个域后更新