# Evaluation Judge 流程说明

本文说明当前 evaluation judge 流程的实际实现，代码入口在 [backend/investibook/core/eval/eval_judge.py](/Users/huyibin/Documents/dev/investibook/backend/investibook/core/eval/eval_judge.py:1)。

本文重点回答 4 个问题：

- 当前 judge 有哪些步骤
- 每一步依赖哪些输入
- 每一步如何判定 pass / fail / partial
- 每一步在工程上的实际意义是什么

## 适用范围

本文描述的是当前代码实现，不是理想化设计。

本文主线覆盖 deterministic judgment pipeline，同时说明 semantic shadow artifact 在当前实现中的位置。

主入口：

- [eval_judge.py](/Users/huyibin/Documents/dev/investibook/backend/investibook/core/eval/eval_judge.py:24)

artifact 解析：

- [eval_artifacts.py](/Users/huyibin/Documents/dev/investibook/backend/investibook/core/eval/eval_artifacts.py:1)

schema 归一化和枚举约束：

- [eval_contracts.py](/Users/huyibin/Documents/dev/investibook/backend/investibook/core/eval/eval_contracts.py:1)
- [eval_registry.py](/Users/huyibin/Documents/dev/investibook/backend/investibook/core/eval/eval_registry.py:382)

semantic shadow artifact 生成：

- [semantic_evaluator.py](/Users/huyibin/Documents/dev/investibook/backend/investibook/core/eval/semantic_evaluator.py:1)

## 总体流程

当前 judge 流程按下面顺序执行：

1. 解析 run bundle
2. 从 trace、artifact、evidence 文件、output_text、analysis patch 中提取 facts
3. 判定 `contract`
4. 判定 `workflow`
5. 判定 `execution_contract`
6. 判定 `evidence_collection`
7. 判定 `critical_evidence`
8. 判定 `evidence_routing`
9. 判定 `diagnosis` 质量
10. 按 verdict precedence 产出最终 `pass` / `fail` / `invalid`

主编排逻辑见：

- [eval_judge.py](/Users/huyibin/Documents/dev/investibook/backend/investibook/core/eval/eval_judge.py:35)

补充说明：

- runner 会先执行 deterministic judgment
- 当 case 定义了受支持的 semantic diagnosis checks 时，runner 还会额外生成 `semantic_context_v1.json` 和 `semantic_packet_v1.json`
- 只有 `semantic_packet_v1.json` 的 `status == ok` 时，当前实现才把 semantic shadow 视为可用
- provider 缺失、调用失败、超时、unsupported provider 等路径只会留下 shadow artifact，不会污染 deterministic 主评分
- 这些 semantic 文件当前属于 shadow artifact，用于补充 diagnosis 评估上下文，不替代本文描述的 deterministic 主流程

## Step 0：Run Bundle Resolution

### 输入

- `audit_dir`
- `workspace_id` 对应的 `workspace_dir`
- case metadata 中的 expected cluster id
- case metadata 中的 allowed time windows

来源：

- [eval_judge.py](/Users/huyibin/Documents/dev/investibook/backend/investibook/core/eval/eval_judge.py:43)
- [eval_artifacts.py](/Users/huyibin/Documents/dev/investibook/backend/investibook/core/eval/eval_artifacts.py:112)

### 做的事情

它负责定位一个可用的 run bundle，里面通常包括：

- manifest
- diagnosis report
- evidence 目录
- 可选的 analysis patch
- 可选的 summary text / json
- 可选的 logs 目录
- 可选的 telemetry 文件

bundle 的结构定义见：

- [ResolvedRunBundle](/Users/huyibin/Documents/dev/investibook/backend/investibook/core/eval/eval_artifacts.py:10)

### 判定方法

- 如果找不到合法 candidate bundle，整个 judgment 直接是 `invalid`
- 如果存在多个合法 candidate，但无法唯一选定，整个 judgment 直接是 `invalid`
- 否则解析成功，后续步骤都基于选中的 bundle 执行

失败路径见：

- [eval_judge.py](/Users/huyibin/Documents/dev/investibook/backend/investibook/core/eval/eval_judge.py:339)

### 实际意义

这一步回答的是：

“我们当前拿来 judge 的，是不是正确的那次 eval run，而且最基本的文件是否齐全？”

如果这一步不先挡住，后面所有评分都有可能是在错误 run、残缺 run、或者混淆 run 上做判断。

## Step 1：Facts 提取

judge 并不是每个 bucket 都直接扫原始文件，而是先把输入归一化成几类 facts。

### 1.1 Trace Facts

来源：

- [eval_judge.py](/Users/huyibin/Documents/dev/investibook/backend/investibook/core/eval/eval_judge.py:1145)

输入：

- diagnosis report JSON
- manifest JSON
- telemetry 文本
- case metadata 中的 cluster id 和 allowed time window

产出：

- `window.*`
- `cluster.*`
- `workflow.*`
- `artifacts.*`

例子：

- `window.matched_allowed_window`
- `cluster.matched`
- `workflow.architecture_context_loaded`
- `artifacts.analysis_patch_present`

提取逻辑：

- 时间窗口优先从 diagnosis report metadata 里取，manifest 做 fallback
- cluster id 优先从 diagnosis report metadata 里取，manifest 做 fallback
- workflow milestone 主要通过 telemetry 文本和 bundle 文件存在性推断

实际意义：

- Trace facts 负责表达“这次 run 是否对齐这个 case”以及“agent 是否走到了预期执行阶段”

### 1.2 Evidence Facts

来源：

- [eval_judge.py](/Users/huyibin/Documents/dev/investibook/backend/investibook/core/eval/eval_judge.py:1304)

输入：

- `evidence/collection_errors.json`
- `evidence/gate_checks.json`
- `evidence/log_stats.json`
- `evidence/queries.json`
- diagnosis report JSON
- summary 文本
- `logs_dir` 下的补充日志文件

产出：

- evidence bundle 是否完整
- 渠道覆盖情况
- 渠道尝试动作与 artifact 落盘的一致性
- panic / kill-query 相关信号
- 简单的 infra / hotspot 指示
- 后续 routing 使用的 metric/query evidence

例子：

- `core_evidence_bundle_present`
- `logs_channel_observed`
- `query_or_metric_channel_observed`
- `events_collection_attempted`
- `events_output_file_present`
- `events_attempted_implies_artifact_present`
- `panic_signal_artifact_present`
- `kill_query_signal_present`
- `metric_evidence`
- `query_evidence`

实际意义：

- Evidence facts 回答的是：
  “这次 run 实际收集到了哪些证据，覆盖了哪些证据渠道，是否已经有一些明确的信号类别出现？”

### 1.3 Output Facts

来源：

- [eval_judge.py](/Users/huyibin/Documents/dev/investibook/backend/investibook/core/eval/eval_judge.py:2170)

输入：

- agent 最终输出的 `output_text`

产出：

- 一组通过关键词 / regex 启发式提取的布尔 facts

例子：

- `mentions_v8_5_2`
- `diagnosis_kill_query_chain_asserted`
- `diagnosis_cpu_hotspot_asserted`
- `fake_alarm_primary_asserted`
- `missing_index_primary_root_cause_asserted`

实际意义：

- Output facts 是把自由文本 final answer 转成可判分信号的桥梁

### 1.4 Routing Facts

来源：

- [eval_judge.py](/Users/huyibin/Documents/dev/investibook/backend/investibook/core/eval/eval_judge.py:1985)

输入：

- `analysis_patch.json`
- 提取出的 metric evidence
- 提取出的 query evidence
- logs / events 渠道是否存在

产出：

- patch 是否存在
- patch 是否是合法 JSON
- item shape 是否合法
- 是否包含 key/supporting evidence
- metric refs 是否可解析
- query refs 是否可解析
- log/event 引用是否可解析

例子：

- `analysis_patch_present`
- `analysis_items_shape_valid`
- `analysis_metric_refs_required`
- `analysis_metric_refs_resolvable`
- `analysis_query_refs_resolvable`

实际意义：

- Routing facts 负责判断 analysis patch 里的结构化证据引用，是否真的能指向本次 run 中收集到的真实 evidence，而不是空引用、错引用、伪造引用

## Step 2：Contract

来源：

- 评估逻辑：[eval_judge.py](/Users/huyibin/Documents/dev/investibook/backend/investibook/core/eval/eval_judge.py:426)
- schema 归一化：[eval_contracts.py](/Users/huyibin/Documents/dev/investibook/backend/investibook/core/eval/eval_contracts.py:532)

### 输入

- 只使用 trace facts

常见 checks：

- `time_window_match`
- `cluster_scope_match`

### 判定方法

- 每个 check 按 `all_of` 或 `any_of` 评估规则
- 只要存在 `required` check 失败，bucket 状态就是 `fail`
- 否则 bucket 状态是 `pass`

### 实际意义

这是“可比性 gate”。

它回答的是：

“这次 run 到底是不是这个 case 对应的 cluster、对应的 incident window？”

如果不是，就不应该把它当成有效诊断结果继续打分。

## Step 3：Workflow

来源：

- [eval_judge.py](/Users/huyibin/Documents/dev/investibook/backend/investibook/core/eval/eval_judge.py:512)

### 输入

- `trace["workflow"]`
- `judge.workflow` 配置

常见 checks：

- `architecture_context_loaded`
- `misdiagnosis_guardrails_loaded`
- `diagnosis_run_initialized`
- `evidence_collection_completed`
- 以及可选项 `summary_read_completed` 等

### 判定方法

- 每个 check 会变成带 weight 的 scored item
- workflow score 只基于 required checks 的 raw score 归一化
- 状态逻辑：
  - `pass`：没有 required failure
  - `partial`：有 required failure，但 run 明显有推进
  - `fail`：有 required failure，且 run 基本没有推进

见：

- [eval_judge.py](/Users/huyibin/Documents/dev/investibook/backend/investibook/core/eval/eval_judge.py:543)

### 实际意义

这一步回答的是：

“agent 有没有真的把预期诊断流程跑起来？”

它评估的是执行进度，不是根因判断是否正确。

## Step 4：Execution Contract

来源：

- [eval_judge.py](/Users/huyibin/Documents/dev/investibook/backend/investibook/core/eval/eval_judge.py:1868)

### 输入

- workflow check 的 pass/fail facts
- artifact contract facts
- channel contract facts

这些输入来自：

- workflow bucket 输出
- trace artifacts
- evidence facts

辅助构造函数见：

- [eval_judge.py](/Users/huyibin/Documents/dev/investibook/backend/investibook/core/eval/eval_judge.py:1844)

常见 checks：

- `required_workflow_present`
- `required_artifacts_present`
- `preferred_channels_minimum_observed`
- `preferred_channels_all_observed`

### 判定方法

- `fail`：任一 required check 失败
- `partial`：required 都通过，但 optional check 有失败
- `pass`：所有 checks 都通过

### 实际意义

这是“最小可 judge 性 gate”。

它回答的是：

“这次 run 是否至少满足了最基本的 workflow、artifact、evidence channel 要求，从而值得继续评分？”

它和 `workflow` 的区别是：

- `workflow` 更关注过程有没有走
- `execution_contract` 更关注最小可用执行产物是否齐

## Step 5：Evidence Collection

来源：

- [eval_judge.py](/Users/huyibin/Documents/dev/investibook/backend/investibook/core/eval/eval_judge.py:661)

### 输入

- evidence facts
- artifact facts
- channel facts

常见 checks：

- `required_artifacts_present`
- `preferred_channels_minimum_observed`
- `preferred_channels_all_observed`
- `attempted_events_collection_persisted`

### 判定方法

- 使用带权重的 checks
- `fail`：任一 required check 失败
- `partial`：required 通过，但 optional check 失败
- `pass`：全部通过

### 实际意义

这一层评估的是证据覆盖质量，不是根因正确性。

它回答的是：

“这次 run 是否收集到了足够证据，而且是否覆盖到了希望的证据渠道？”

典型用途：

- 硬门槛：至少命中一个 preferred channel
- 加分项：所有 preferred channels 都命中
- 软扣分项：尝试过查询 events，但没有落下 `events.json` 或 report 里的 `evidence.events`

特别说明：

- `attempted_events_collection_persisted` 是一个故意设计成 soft 的一致性检查。
- 如果 telemetry 显示 run 尝试查询过 Activity Hub events，那么即使结果为空，也应该落出 events artifact。
- 这个 check 失败时，只会扣 `evidence_collection` bucket 的分数；它本身不会把 bucket 状态打成 `fail`，也不会单独把整体 run 判成 `invalid`。

## Step 6：Critical Evidence

来源：

- [eval_judge.py](/Users/huyibin/Documents/dev/investibook/backend/investibook/core/eval/eval_judge.py:1347)

### 输入

- `logs_dir` 下的原始日志文档
- 选定的 JSON artifacts，例如：
  - `component_health.json`
  - `gate_checks.json`
  - `metrics.json`
  - `queries.json`
  - restart metric 文件
  - summary JSON

artifact 加载逻辑见：

- [eval_judge.py](/Users/huyibin/Documents/dev/investibook/backend/investibook/core/eval/eval_judge.py:1520)

### 匹配方式

- `keyword_any`
- `json_list_nonempty`
- `json_text_contains_any`
- `numeric_series_any_gt`

### 判定方法

- 每个 check 有独立 weight
- 每个 check 在自身 rules 上按 `all_of` 或 `any_of` 求值
- bucket 状态：
  - `pass`：所有 checks 都通过
  - `partial`：部分 checks 通过
  - `fail`：没有任何 check 通过

### 实际意义

这是“客观证据层”。

它回答的是：

“本次收集到的 artifacts 里，是否真的包含一个好诊断应该抓到的关键症状/机制证据？”

这一层有意独立于 final answer 的措辞，用来把 case 锚定在真实可观察证据上。

## Step 7：Evidence Routing

来源：

- [eval_judge.py](/Users/huyibin/Documents/dev/investibook/backend/investibook/core/eval/eval_judge.py:872)

### 输入

- 从 `analysis_patch.json` 提取的 routing facts
- metric/query evidence
- logs/events 渠道可用性

### 检查内容

- patch 是否存在
- patch JSON 是否合法
- item shape 是否合法
- 是否存在 key evidence
- metric ref 是否可解析
- query ref 是否可解析
- log/event ref 是否可解析

### 判定方法

- `fail`：任一 required check 失败
- `partial`：required 通过，但 optional 失败
- `pass`：全部通过

当前 bucket score 采用 required checks 的 raw score 归一化到 10 分。

见：

- [eval_judge.py](/Users/huyibin/Documents/dev/investibook/backend/investibook/core/eval/eval_judge.py:154)

### 实际意义

这一层回答的是：

“analysis patch 里的结构化证据地图，是否内部一致，而且真的能落到本次 run 收集到的 evidence 上？”

它主要防止：

- metric/query/log/event 引用不存在
- analysis patch 结构错误
- patch 里写了无法解析的 evidence link

## Step 8：Diagnosis Quality

diagnosis bucket 分成两层。

### 8.1 Hard Checks

来源：

- [eval_judge.py](/Users/huyibin/Documents/dev/investibook/backend/investibook/core/eval/eval_judge.py:467)

输入：

- output facts
- 原始 output text

判定方法：

- 每个 check 都是 boolean
- check 在自身 rules 上按 `all_of` 或 `any_of` 求值
- 只有所有 hard checks 全通过，diagnosis quality status 才是 `pass`

计分：

- hard checks 会被归一化到 18 分

实际意义：

- Hard checks 编码的是必须给出的结论，以及必须避免的误诊
- 它就是 diagnosis 质量里的 critical rubric

### 8.2 Supporting Checks

来源：

- [eval_judge.py](/Users/huyibin/Documents/dev/investibook/backend/investibook/core/eval/eval_judge.py:399)

输入：

- output facts
- 原始 output text

判定方法：

- 带权重的 checks
- 在各自 rules 上按 `all_of` / `any_of` 求值
- 分数归一化到配置的 supporting weight

实际意义：

- Supporting checks 负责奖励更好的机制解释、对照分析、缓解建议、follow-up 建议
- 它会增加质量分，但不会覆盖 hard checks 失败这一事实

## Step 9：Verdict Precedence

来源：

- [eval_judge.py](/Users/huyibin/Documents/dev/investibook/backend/investibook/core/eval/eval_judge.py:173)

当前 verdict precedence 是显式写死的：

1. `contract fail` -> `invalid`
2. `execution_contract fail` -> `invalid`
3. `workflow fail/partial` -> `invalid`
4. `evidence_collection fail` -> `invalid`
5. `evidence_collection partial` -> `fail`
6. `critical_evidence fail/partial` -> `fail`
7. `evidence_routing fail/partial` -> `fail`
8. 上面都通过，且 diagnosis hard checks 通过 -> `pass`
9. 其余情况 -> `fail`

### 实际意义

这个顺序把结果分成了 3 类：

- `invalid`
  当前 run 不可比，或者不完整到不应该公平打分

- `fail`
  当前 run 可比，但证据质量或诊断质量不够

- `pass`
  当前 run 可比，证据充分，而且诊断质量过关

## 各 Bucket 输入来源总表

| Bucket | 主要输入 | 来源 |
|---|---|---|
| artifact resolution | audit/workspace 目录、manifest、report、evidence dir | 文件系统布局 |
| contract | trace facts | diagnosis report、manifest、case metadata |
| workflow | workflow facts | telemetry 文本、bundle 文件存在性 |
| execution_contract | workflow/artifact/channel facts | workflow 输出、trace artifacts、evidence facts |
| evidence_collection | evidence/artifact/channel facts | evidence JSON、日志、report |
| critical_evidence | logs 和 artifact JSON 文档 | logs dir、evidence JSON、metrics 文件、summary JSON |
| evidence_routing | routing facts | analysis patch + collected evidence refs |
| diagnosis | output facts + output text | final answer 文本 |

## 每一层分别在防什么问题

- artifact resolution
  防止 judge 到错误 run，或者 judge 到残缺 run

- contract
  防止 cluster/window 不匹配

- workflow
  防止 agent 根本没走完预期诊断流程

- execution_contract
  防止在 workflow/artifact/channel 最低要求都不满足时继续打分

- evidence_collection
  防止证据收集过浅、覆盖不完整

- critical_evidence
  防止诊断忽略关键客观证据

- evidence_routing
  防止 analysis patch 结构错误或引用无法落地

- diagnosis hard checks
  防止根因判断错误、遗漏必须结论、或踩入经典误诊

- diagnosis supporting checks
  奖励更好的解释和行动建议

## 当前实现的几个 Caveat

- `critical_evidence` 里的 `json_text_contains_any` 当前是在整份 JSON 序列化文本上搜索，不是 path-scoped 搜索
- `ticket_context_channel_observed` 当前在 evidence extraction 里还是硬编码 `False`
- output facts 本质上是关键词 / regex 启发式，不是严格语义解析
- evidence routing 的质量高度依赖 `analysis_patch.json` 的结构和引用风格

相关实现位置：

- evidence extraction: [eval_judge.py](/Users/huyibin/Documents/dev/investibook/backend/investibook/core/eval/eval_judge.py:1304)
- output extraction: [eval_judge.py](/Users/huyibin/Documents/dev/investibook/backend/investibook/core/eval/eval_judge.py:2170)
- routing extraction: [eval_judge.py](/Users/huyibin/Documents/dev/investibook/backend/investibook/core/eval/eval_judge.py:1985)
