# 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` |