# 13. Skill 评测实验台设计

## 1. 背景

当前快慢分流优化需要完成一条完整链路：

```text
已有评测集
  -> 使用现成 Skill 判断快慢
  -> 支持单条和批量运行
  -> 对比人工标签和模型判断
  -> 查看分歧及判定依据
  -> 更新 Skill 或切换模型
  -> 重新评测并比较变化
```

这里讨论的是离线优化和评估场景。评测过程允许 Agent 使用完整 Agent Loop，重点是判断质量、依据可追溯和标准持续收敛。线上快慢路由的耗时、成本和部署形态属于后续独立问题。

本设计假设快慢分流 Skill 已经存在。实验台负责稳定调用和评估 Skill，不负责定义 Skill 内部的分类标准。

## 2. 目标

实验台需要支持：

- 单条输入调用指定 Skill，返回判断、理由和执行记录。
- 上传 CSV、XLSX 或 JSONL，配置字段映射后批量运行。
- 选择模型和不可变的 Skill 版本。
- 后台并发执行，支持暂停、继续、取消和失败重试。
- 自动计算总体、分类别和分垂域指标。
- 集中查看人工与模型分歧，并进行人工复核。
- 查看模型声明的 Skill 判定依据和实际访问记录。
- 对比不同 Skill 版本或模型版本的修正与退化。
- 导出原始结果、复核结果和版本对比结果。

实验台不承担：

- 在线请求的实时路由。
- 快系统能力或分类原则的编写。
- 将每个普通 Skill 自动转成页面应用。
- 修改现有聊天 Agent 的会话和运行流程。

## 3. 总体架构

```mermaid
flowchart LR
    UI["快慢分流评测页面"] --> EXP["Evaluation Service"]
    API["Skill Run API"] --> EXP

    EXP --> MAP["Dataset Mapping"]
    EXP --> SNAP["Skill Snapshot"]
    EXP --> SCHED["Batch Scheduler"]

    SCHED --> RUNNER["Headless Skill Runner"]
    RUNNER --> AGENT["LocalCodingAgent"]
    RUNNER --> MODEL["指定模型"]
    RUNNER --> SKILL["固定 Skill 快照"]

    EXP --> STORE["Evaluation Store"]
    STORE --> RESULT["指标 / 分歧 / 复核 / 版本对比"]
```

新增能力由五个部分组成：

1. `Evaluation Service`：管理数据集、实验配置、运行和结果。
2. `Dataset Mapping`：把不同格式的数据转换为统一 case。
3. `Skill Snapshot`：固定每次实验使用的 Skill 内容。
4. `Headless Skill Runner`：使用现有 Agent Core 在后台执行指定 Skill。
5. `Batch Scheduler`：拆分、并发和恢复批量任务。

## 4. 与现有 Agent 的隔离

现有聊天链路保持不变：

```text
/api/chat
  -> AgentState
  -> account/session-scoped LocalCodingAgent
  -> 聊天 session 和活动流
```

评测链路新增独立入口：

```text
/api/evaluations
  -> EvaluationRuntime
  -> evaluation-scoped LocalCodingAgent
  -> 评测任务、case 结果和内部执行记录
```

两条链路可以复用：

- `LocalCodingAgent`
- `ModelConfig`
- Skill loader
- Tool handler
- `OutputSchemaConfig`
- 模型兼容层

评测链路必须独立管理：

- Agent 实例。
- Session 命名空间。
- 数据库记录。
- 运行队列。
- 并发和资源配额。
- Skill 快照。

评测产生的内部 Agent session 不进入左侧聊天会话列表，也不读取聊天历史、用户记忆或当前聊天 session 状态。

代码隔离只能保证功能不互相污染。批量任务仍可能争用模型服务资源，因此评测任务需要独立 worker pool、并发上限和低于聊天请求的调度优先级。有条件时可以为评测配置独立模型地址。

## 5. 数据集字段映射

评测文件格式由数据来源决定，不应写进 Skill，也不应要求用户临时组织成提示词。

上传文件后，系统先读取表头和样本，用户把源字段映射到统一字段：

| 统一字段 | 说明 | 必填 |
|---|---|---|
| `case_id` | case 唯一标识 | 否，可自动生成 |
| `query` | 当前用户 query | 是 |
| `gold_route` | 人工快慢标签 | 评测时必填 |
| `history` | 对话历史 | 否 |
| `context` | 设备、位置等上下文 | 否 |
| `domain` | 垂域 | 否 |
| `request_id` | 线上 RID | 否 |
| `metadata` | 其他保留字段 | 否 |
| `source_row` | 原始行 | 自动保留 |

页面需要支持：

- 列名自动推荐。
- 手动选择源字段。
- JSON path，例如 `data.query`。
- 标签值转换，例如 `快/慢`、`0/1`、`fast/slow`。
- 必要的字段组合和简单转换。
- 转换后样本预览。
- 缺失字段、非法标签和重复 ID 校验。

确认后的映射保存为可复用的 `Dataset Mapping Profile`。Skill Runner 只接收统一 case，不感知原始文件的列名和格式。

统一 case 示例：

```json
{
  "case_id": "001",
  "query": "到目的地电量够不够",
  "gold_route": "fast",
  "history": [],
  "context": {},
  "domain": "地图导航",
  "request_id": "",
  "metadata": {},
  "source_row": {}
}
```

## 6. Skill 版本与快照

实验不能只记录 Skill 名称。Skill 内容会持续更新，同一个名称在不同时间可能代表不同标准。

每次创建实验时生成不可变快照，并记录：

```json
{
  "skill_name": "fast-slow-routing",
  "repository": "zk-data-agent",
  "git_commit": "abc1234",
  "content_hash": "sha256:...",
  "snapshot_id": "skill_snapshot_xxx",
  "status": "committed"
}
```

版本来源可以是：

- 当前部署版本。
- 指定 Git commit。
- 指定 Git tag。
- 尚未提交的草稿快照。

快照建议存放在：

```text
.port_sessions/evaluations/skill_snapshots/
  <skill-name>/
    <commit-or-content-hash>/
```

运行旧版本时不能切换当前项目分支，也不能覆盖线上 Skill。对于 Git 中的历史版本，可以通过 `git archive` 提取单个 Skill 目录到快照缓存。

草稿版本允许参与实验，但页面必须明确标记“未提交”，并使用内容哈希保证同一次实验可复现。

评测 Agent 始终从快照加载 Skill；聊天 Agent 继续从当前部署目录加载 Skill，两者互不影响。

## 7. Headless Skill Runner

`Headless` 表示没有聊天页面，不表示减少 Agent 能力。

Runner 接收：

```json
{
  "skill_snapshot_id": "skill_snapshot_xxx",
  "model_id": "model_xxx",
  "case": {
    "case_id": "001",
    "query": "到目的地电量够不够"
  }
}
```

Runner 的执行过程：

1. 从快照读取指定 Skill。
2. 创建独立的评测 Agent 实例和 session。
3. 直接把指定 Skill 注入当前任务，不等待模型自行召回 Skill。
4. 把统一 case 作为任务输入。
5. 按 Skill 约定执行完整 Agent Loop。
6. 保存最终结果、执行事件、工具调用和用量。
7. 将最终输出归一化为评测结果。

统一评测结果至少包含：

```json
{
  "case_id": "001",
  "prediction": "fast",
  "reason": "当前快系统已有对应能力",
  "confidence": 0.95,
  "evidence_refs": [
    "knowledge/地图能力.md#到达电量预估"
  ],
  "status": "completed",
  "raw_output": "",
  "agent_session_id": ""
}
```

单条判断创建一个 Agent Run。批量判断把每条 case 拆成独立 Agent Run，避免样本间上下文污染。

## 8. 批量调度

批量调度器负责：

- 把数据集拆成独立 case 任务。
- 使用受控并发执行。
- 保存每条 case 的状态和重试次数。
- 单条失败不终止整个实验。
- 支持暂停、继续和取消。
- 服务重启后从数据库恢复未完成任务。
- 只重跑失败项、分歧项或用户选中的 case。

任务状态：

```text
pending
running
completed
failed
cancelled
```

聊天请求和评测请求需要独立并发池。评测批量任务默认低优先级，避免影响当前在线使用者。

## 9. 判定依据与可追溯性

页面需要回答“模型依据 Skill 的哪部分作出判断”，但不能把完整模型思考当作可靠因果证据。

实验台展示两类事实：

1. **实际访问记录**：Agent 读取了哪些 Skill 文件、知识文件和章节。
2. **模型声明的判定依据**：最终结果中的 `evidence_refs`。

生成 Skill 快照时，系统为 Markdown 标题和知识文件建立稳定引用，例如：

```text
SKILL.md#判断流程
knowledge/地图能力.md#到达电量预估
knowledge/边界.md#开放式行程规划
```

评测 Runner 的统一任务约定要求最终结果返回引用。页面将引用解析为可点击的原文片段。

如果模型没有引用明确规则，页面展示“未引用具体依据”，并允许按此条件筛选。这类 case 本身就是标准缺失或模型未正确使用 Skill 的候选问题。

## 10. 页面信息架构

页面采用“实验配置、结果页签、单条详情”三层结构。

### 10.1 实验配置

顶部配置区包含：

- 数据集。
- 字段映射。
- Skill 和版本。
- 模型。
- case 数量。
- 并发配置。
- 开始、暂停、继续、取消、克隆实验。

运行开始后配置区折叠，避免占用结果空间。

### 10.2 结果页签

| 页签 | 主要内容 |
|---|---|
| 概览 | 运行进度、总体指标、混淆矩阵、快慢分布、分垂域指标 |
| 分歧 | 人工标签与模型判断不一致、低置信、无明确依据的 case |
| 全部 Case | 全量结果、状态、筛选、排序和导出 |
| 版本对比 | 不同 Skill 或模型实验之间的修正、退化和指标变化 |

### 10.3 单条详情抽屉

点击一条 case 后展示：

1. 原始数据和统一后的输入。
2. 人工标签、模型判断、置信度和理由。
3. Skill 判定依据及原文片段。
4. 实际读取文件和工具调用。
5. 默认折叠的完整 Agent 执行记录。
6. 人工复核结果和备注。

人工复核至少支持：

- 人工原标签正确。
- 模型判断正确。
- 分类标准存在歧义。
- 快系统能力信息缺失。
- 暂不确定。

## 11. 实验与版本对比

一个实验由以下对象共同确定：

```text
数据集版本
+ 字段映射版本
+ Skill 快照
+ 模型版本
+ 执行参数
```

同一数据集可以克隆实验并替换 Skill 或模型：

```text
实验 A：Skill abc1234 + 模型 A
实验 B：Skill def5678 + 模型 A
实验 C：Skill def5678 + 模型 B
```

版本对比需要展示：

- 总体指标变化。
- 各垂域指标变化。
- 从错误变正确的 case。
- 从正确变错误的 case。
- 判断未变化但理由变化的 case。
- 新增或消失的无依据结果。

这样才能形成“评测、发现分歧、更新 Skill、重新评测”的优化闭环。

## 12. 数据存储

评测运行状态适合使用独立 SQLite 数据库，例如：

```text
.port_sessions/evaluations/evaluations.db
```

核心实体：

```text
evaluation_datasets
dataset_mapping_profiles
skill_snapshots
evaluation_experiments
evaluation_cases
evaluation_case_runs
evaluation_results
evaluation_reviews
```

数据库保存可查询状态、关系和人工复核结果。上传原文件、Skill 快照、导出文件和较大的 Agent transcript 保存在实验目录，数据库记录路径和摘要。

## 13. API 轮廓

```text
POST /api/evaluations/datasets
POST /api/evaluations/datasets/{id}/mapping

POST /api/evaluations/skill-snapshots
GET  /api/evaluations/skill-snapshots

POST /api/evaluations
GET  /api/evaluations/{id}
POST /api/evaluations/{id}/start
POST /api/evaluations/{id}/pause
POST /api/evaluations/{id}/resume
POST /api/evaluations/{id}/cancel

GET  /api/evaluations/{id}/results
POST /api/evaluations/{id}/retry
POST /api/evaluations/{id}/reviews
GET  /api/evaluations/{id}/export

POST /api/evaluations/compare
```

API 同时服务 Web 页面和外部批量调用。单条调用可以创建只包含一个 case 的轻量实验，复用相同运行与记录机制。

## 14. 实施顺序

### P1：后台单条运行

- Skill 快照。
- Headless Skill Runner。
- 单条输入和统一结果。
- 独立于聊天 session 的运行记录。

### P2：数据集与批量调度

- 文件上传和字段映射。
- 批量拆分、并发、暂停、恢复和重试。
- 基础指标和导出。

### P3：评测页面

- 实验配置。
- 概览、分歧、全部 Case。
- 单条详情和人工复核。
- Skill 依据引用。

### P4：版本对比

- 克隆实验。
- Skill 和模型版本矩阵。
- 修正、退化和理由变化分析。

## 15. 验收标准

- 开启评测任务后，现有聊天 Agent 可以正常创建、继续和停止会话。
- 600 条批量任务不会出现在聊天会话列表。
- 不同输入格式可以通过字段映射转换为统一 case。
- 每次实验可以确认具体 Skill commit 或内容哈希。
- Skill 更新后，历史实验仍能读取原快照并复现配置。
- 单条失败不会中断全量任务，服务重启后可以恢复。
- 页面可以定位人工与模型分歧，并查看简短理由和 Skill 引用。
- 可以比较两个实验的修正项和退化项。
- 批量评测的并发不会明显拖慢聊天 Agent。
