# Evaluation 当前实现总览

本文描述的是当前仓库里已经落地的 evaluation 实现，不是目标架构，不引用历史设计文档作为行为依据。

适用范围：

- 当前 `evaluation/` 正式入口
- 当前 `live eval` runner
- 当前 formal registry / judge / score / audit 设计

不在本文范围内：

- `tools/replayer/` 的 legacy replay / postmortem 流程
- `docs/plans/2026-04-20-evaluation-system/` 下的历史设计稿
- 尚未验证并纳入当前 PR 的 admin 前端页面

## 当前系统解决什么问题

当前 evaluation 的目标不是回放旧 workspace，也不是对任意历史对话做泛化打分。

当前实现做的是：

- 从 repo 里的 formal registry 读取 suite 和 case
- 为每个 case 创建一个全新的 no-source workspace
- 用 live Clinic / Jira / ACP 执行真实诊断
- 把本次运行产物镜像到 audit 目录
- 用 deterministic judge 对结果做结构化判分
- 在 case 定义支持时，额外产出 semantic shadow artifact

因此，它是一个以 repo registry 为输入、以 fresh workspace live execution 为执行模型、以 deterministic judgment 为主判链路的评测系统。

## 入口与核心模块

当前建议的 operator 入口：

- CLI：`backend/investibook/scripts/eval.py`

核心模块分工：

- `backend/investibook/core/eval/eval_cli.py`
  - 用户侧 CLI 参数解析
- `backend/investibook/core/eval/eval_runner.py`
  - suite 运行主编排
- `backend/investibook/core/eval/eval_registry.py`
  - registry 读取和校验
- `backend/investibook/core/eval/eval_judge.py`
  - deterministic 判分
- `backend/investibook/core/eval/semantic_evaluator.py`
  - semantic shadow artifact 生成
- `backend/investibook/core/eval/eval_artifacts.py`
  - run bundle / artifact 解析
- `backend/investibook/db/queries.py`
  - `eval_runs` / `eval_case_runs` / `eval_judgment` 等持久化

当前 backend 还暴露了 admin API：

- `GET /api/admin/eval-runs`
- `GET /api/admin/eval-runs/{run_id}`
- `GET /api/admin/eval-cases/{case_id}/history`
- `POST /api/admin/eval-runs`
- `POST /api/admin/eval-runs/{run_id}/rerun-failed`

但当前 PR 不包含已验证的 admin runs 前端页面。当前稳定展示面仍以文本 report 和 API 返回为主。

## Formal Registry 设计

当前 formal registry 位于：

```text
evaluation/regression/
  cases/<case_id>/
    case.yaml
    judge.yaml
    expected.md
  suites/<suite_id>.yaml
```

职责边界：

- `case.yaml`
  - run-visible 输入
  - prompt、环境、时间窗、执行配置
- `judge.yaml`
  - judge-only ground truth 和判分策略
- `expected.md`
  - 仅供人阅读的期望说明
- `suite.yaml`
  - case 集合、执行模式元数据、baseline eligibility

当前系统只把 `evaluation/regression/` 当正式 registry。

以下内容不再是 formal registry：

- `tools/replayer/cases.yaml`
- legacy replay case 目录
- `golden_workspace_id`
- issue id 驱动的回放资产

## 防信息暴露设计

当前实现里，防信息暴露不是附加规范，而是 formal registry 的一部分。

### 1. 匿名 case 标识

case id 和目录名必须使用匿名、现象化命名，例如：

- `component-restart-during-incident-window`
- `service-degradation-during-incident-window`

禁止在路径和 case id 中暴露：

- Jira issue id
- GitHub issue number
- 已知 bug 编号
- 最终根因
- 修复版本

### 2. run-visible 与 judge-only 拆分

`case.yaml` 是 run-visible 输入，因此只能包含执行所需的最小信息：

- `cluster_id`
- time window
- 用户请求
- 执行配置

`judge.yaml` 才能保存答案侧信息：

- ground truth
- evidence checks
- diagnosis checks
- semantic check policy

当前实现的核心原则是：

- agent 运行时只能看到 `case.yaml`
- 判分时才读取 `judge.yaml`

### 3. prompt 直接写 live-like 用户请求

当前 runner 不再为 case 注入一层额外的 cluster/time 模板。

正式 case 应该把 live-like 请求直接写在 `case.yaml.prompt.text` 里，并在文本中显式包含：

- cluster id
- 问题时间窗
- 观察到的现象
- 需要完成的诊断任务

同时禁止把答案提示写进 prompt，例如：

- 已知根因
- bug 编号
- fix version
- issue id

### 4. fresh no-source workspace

每个 case 都创建一个新的 workspace，并显式把 `workspace_sources` 置为空列表。

这意味着当前 eval 运行不依赖：

- 旧 workspace conversation
- 预先挂载好的 Jira source
- frozen replay source snapshot

当前 live eval 的执行上下文来自：

- workspace init 写入的 runtime config
- `case.yaml.prompt.text`
- ACP 运行时对 live data 的真实调用

### 5. output_fact 白名单

当前 diagnosis supporting checks 不能在 YAML 里自由发明新的 output fact 名称。

`output_fact` 规则必须引用 judge 已支持的 fact id 白名单。这保证了：

- answer-side规则不会无限膨胀
- case 不能随意塞入只服务单一 ticket 的临时 fact 名称
- 判分逻辑仍然由代码维护，而不是被单个 case 任意改写

## 整体运行流程

当前一次 eval suite 运行的主流程如下。

### Step 1：解析命令和加载 suite

CLI 默认行为：

- `command=run`
- `mode=manual`
- `execution-mode=acp`
- `user-email=eval@localhost`
- `timeout-s=900`
- `idle-timeout-s=300`

runner 首先：

1. 校验 execution mode
2. 加载 suite
3. 按 `case_ids` 选择子集
4. 读取当前 `suite_id + suite_version` 的 active baseline
5. 生成 `run_id`
6. 写入 `eval_runs` skeleton

当前 comparison status 规则：

- `manual_suite` / `manual_case` / `rerun_failed`
  - `review_only`
- `scheduled`
  - baseline 存在时为 `pending`
  - baseline 不存在时为 `unbaselined`

### Step 2：为每个 case 创建 fresh workspace

当前 runner 对每个 case：

1. 创建一个新的 workspace 记录
2. 绑定 owner
3. 将 `workspace_sources` 清空
4. 写入 `.opencode/opencode.json`
5. 用服务端环境填充 workspace `.env`

当前 workspace `.env` 会注入：

- `INVESTIBOOK_WORKSPACE_DIR`
- `CLINIC_DIAGNOSTIC_DOWNLOAD_DIR`
- `CLINIC_API_KEY`
- 可选 `GITHUB_TOKEN`
- 可选 `DEVOPS_BASE_URL` / `DEVOPS_API_KEY`

这一步保证 eval workspace 和正常 workspace 使用同一套 runtime 初始化逻辑，但不带用户侧 source state。

### Step 3：执行 case

当前支持两种执行模式：

- `dry-run`
  - 只持久化 run skeleton，不执行 ACP
- `acp`
  - 真正运行 live eval

`acp` 模式下：

1. runner 构造 case prompt
2. 如果 case 配置了 `execution.acp_bash_timeout_s`，会在 prompt 末尾追加 bash timeout hint
3. 调用 `run_chat_and_wait(...)`
4. 等待 Agent 完成或超时
5. 无论成功与否，都尝试释放 ACP session

finish reason 到 case execution status 的当前映射：

- `timeout`
  - `invalid / infra / agent_execution_timeout`
- `error`
  - `invalid / infra / acp_error`
  - 特定 bootstrap timeout 会映射到 `acp_bootstrap_timeout`
- `shutdown`
  - `invalid / infra / acp_shutdown`
- 其余
  - `completed`

### Step 4：镜像 audit 产物

当前 runner 会把 workspace 里的关键产物镜像到：

```text
backend/data/eval_audits/<run_id>/<case_run_id>/
```

当前默认镜像：

- `evidences/`
- `.opencode/telemetry.log`

run 结束后还会写：

- `backend/data/eval_audits/<run_id>/report.txt`
- `backend/data/eval_audits/<run_id>/report_detail.txt`

### Step 5：deterministic judgment

case 完成后，runner 调用 `judge_case_output(...)`。

judge 的输入不是原始 transcript 直接全文比对，而是：

- resolved run bundle
- trace / manifest / telemetry
- evidence 目录下的结构化文件
- output_text
- analysis patch
- case 的 `judge.yaml`

judge 当前输出：

- `judgment_verdict`
- `invalid_subtype`
- `primary_failure_code`
- `score`
- `facts`
- `packet`
- `audit`

同时写入 `eval_judgment`。

### Step 6：semantic shadow artifact

当 case 的 diagnosis checks 包含 `mode=blended` 且配置了 semantic spec 时，runner 还会额外调用 semantic evaluator。

当前行为：

1. 根据 deterministic judgment 生成 `semantic_context_v1.json`
2. 调用 semantic provider 生成 `semantic_packet_v1.json`
3. 只有 `packet.status == "ok"` 时，shadow 才被视为可用
4. provider 缺失、错误、超时、unsupported provider 只会留下 shadow artifact，不会污染 deterministic 主评分

这是当前实现里非常重要的一条边界：

- semantic evaluator 是 shadow 补充路径
- 不是主 gate
- 也不应该在失败时拉低本应通过的 deterministic judgment

### Step 7：更新 case run / run summary

runner 会更新：

- `eval_case_runs`
  - execution status
  - output
  - summary
- `eval_runs`
  - pass / fail / invalid 计数
  - comparison status
  - run summary

最终 run detail 还会附带：

- case 列表
- judgment
- token usage summary
- 文本报告路径

## Judge 设计

当前 judge 是分 bucket 的 deterministic pipeline。

bucket 顺序：

1. `contract`
2. `workflow`
3. `execution_contract`
4. `evidence_collection`
5. `critical_evidence`
6. `evidence_routing`
7. `diagnosis`

每个 bucket 的职责不同。

### 1. `contract`

判断 run bundle 是否和 case 基本对齐，例如：

- cluster id 是否匹配
- allowed window 是否匹配
- 核心 bundle 是否可解析

### 2. `workflow`

判断 agent 是否走到了预期执行阶段，例如：

- architecture context 是否加载
- diagnosis 初始化是否完成
- evidence collection 是否完成

### 3. `execution_contract`

判断这次运行是否可比较，例如：

- required workflow 是否满足
- required artifacts 是否齐全
- required / preferred channels 是否达到最小要求

### 4. `evidence_collection`

判断是否收集到了 case 需要的渠道和基础证据。

### 5. `critical_evidence`

判断 case 级关键证据是否命中。这一层主要消费：

- logs
- structured artifact JSON
- numeric evidence

### 6. `evidence_routing`

判断 `analysis_patch.json` 里的证据引用是否真实可解析，而不是空引用或伪引用。

### 7. `diagnosis`

判断最终答案质量。它会综合：

- output facts
- hard checks
- supporting checks
- 可用时的 semantic shadow

但当前主判链仍然是 deterministic-first。

## Verdict 设计

当前 overall verdict precedence 是固定的，自上而下首个命中即生效：

1. `contract=fail` -> `invalid`
2. `execution_contract=fail` -> `invalid`
3. `workflow in {partial, fail}` -> `invalid`
4. `evidence_collection=fail` -> `invalid`
5. `evidence_collection=partial` -> `fail`
6. `critical_evidence=fail` -> `fail`
7. `critical_evidence=partial` -> `fail`
8. `evidence_routing=fail` -> `fail`
9. `evidence_routing=partial` -> `fail`
10. `diagnosis=fail` -> `fail`
11. 所有 bucket `pass` -> `pass`

这里有两个重要约束：

- `invalid` 保留给不可比较 run
- `partial` 永远不能被提升成 overall `pass`

## 判分系统设计

当前 score 不是单一黑盒分，而是 bucket 分数汇总后再经过 hard gate banding。

bucket 分值上限：

- `workflow`: 10
- `evidence_collection`: 5
- `critical_evidence`: 30
- `evidence_routing`: 5
- `diagnosis`: 50

总分上限：100

当前 `contract` 和 `execution_contract` 不直接给分，它们承担 hard gate 职责。

### 归一化

每个 bucket 先按规则求 raw score / raw max，再归一化到该 bucket 的固定上限。

### hard gate score band

当前代码还有一层 score banding：

- `PASSING_HARD_GATE_SCORE_FLOOR = 70`
- `FAILING_HARD_GATE_SCORE_CAP = 69`

含义：

- 如果 overall verdict 是 `pass`，最终分数至少是 70
- 如果 overall verdict 是 `fail`，最终分数最多是 69
- `invalid` 不使用 pass/fail band 强制修正，只返回 bounded raw total

这保证了：

- 分数和 verdict 不会互相冲突
- 不会出现 `pass` 但分数低于 70
- 也不会出现 `fail` 但分数高于 69

## Semantic 设计

当前 semantic 不是全局统一 LLM judge，而是 case opt-in 的 shadow diagnosis check。

触发条件：

- `judge.diagnosis.checks[]`
- `mode=blended`
- 存在 semantic spec

当前支持的 provider：

- `opencode`
- `heuristic`

artifact：

- `semantic_context_v1.json`
- `semantic_packet_v1.json`

当前语义边界：

- semantic 只补 diagnosis 层
- 不推翻 execution / evidence / workflow hard gate
- provider 错误时优雅降级，不污染 deterministic 主结果

## 当前持久化模型

当前 evaluation domain 至少包含：

- `eval_runs`
- `eval_case_runs`
- `eval_judgment`

这些表承载的是 formal eval run 数据，不是旧的 workspace analytics。

当前 run 结果会记录：

- suite id / version
- trigger mode
- execution status
- comparison status
- promote eligibility
- case 计数
- case judgment
- token / cache / cost 汇总

## 当前对外可见的结果

当前最稳定的 operator 可见产物有三类：

1. CLI 输出
2. `eval_runs` / `eval_case_runs` / `eval_judgment` API / DB 记录
3. `backend/data/eval_audits/` 下的 audit 和文本 report

其中 run 级文本报告会提供：

- suite / run 基本信息
- comparison / trigger 状态
- pass / fail / invalid 计数
- total / avg score
- token / cache / cost
- case failure 摘要

## 当前没有做的事情

为了避免把历史设计和当前实现混在一起，下面这些能力不应被误认为“当前已完成”：

- frozen replay 作为正式执行模型
- `golden_workspace_id` 作为 formal case intake contract
- 已验证的 `/admin/eval-runs` 前端页面
- scheduled CronJob lane 的正式交付
- baseline promote 的完整运营闭环

当前已经落地的主线应理解为：

- formal repo registry
- fresh workspace live eval
- deterministic-first judge
- semantic shadow artifact
- backend run API + CLI + audit report

## 相关阅读

- `evaluation/README.md`
- `evaluation/regression/README.md`
- `evaluation/judge-pipeline-explainer.zh.md`
