first commit

This commit is contained in:
2026-06-12 14:02:15 +08:00
commit 9cbdc1d95d
69 changed files with 9486 additions and 0 deletions

168
docs/models.json Normal file
View File

@@ -0,0 +1,168 @@
{
"data": [
{
"id": "text-embedding-v3",
"object": "model",
"created": 1626777600,
"owned_by": "custom",
"supported_endpoint_types": [
"openai"
]
},
{
"id": "qwen3-vl-embedding",
"object": "model",
"created": 1626777600,
"owned_by": "custom",
"supported_endpoint_types": [
"openai"
]
},
{
"id": "qwen3-vl-plus",
"object": "model",
"created": 1626777600,
"owned_by": "custom",
"supported_endpoint_types": [
"openai"
]
},
{
"id": "qwen3-omni-flash",
"object": "model",
"created": 1626777600,
"owned_by": "custom",
"supported_endpoint_types": [
"openai"
]
},
{
"id": "deepseek-v3.2",
"object": "model",
"created": 1626777600,
"owned_by": "custom",
"supported_endpoint_types": [
"openai"
]
},
{
"id": "glm-5",
"object": "model",
"created": 1626777600,
"owned_by": "zhipu_4v",
"supported_endpoint_types": [
"openai"
]
},
{
"id": "qwen3.5-plus",
"object": "model",
"created": 1626777600,
"owned_by": "custom",
"supported_endpoint_types": [
"openai"
]
},
{
"id": "glm-5.1",
"object": "model",
"created": 1626777600,
"owned_by": "custom",
"supported_endpoint_types": [
"openai"
]
},
{
"id": "kimi-k2.6",
"object": "model",
"created": 1626777600,
"owned_by": "custom",
"supported_endpoint_types": [
"openai"
]
},
{
"id": "deepseek-v4-pro",
"object": "model",
"created": 1626777600,
"owned_by": "deepseek",
"supported_endpoint_types": [
"openai"
]
},
{
"id": "qwen3.6-flash",
"object": "model",
"created": 1626777600,
"owned_by": "custom",
"supported_endpoint_types": [
"openai"
]
},
{
"id": "qwen3.5-flash",
"object": "model",
"created": 1626777600,
"owned_by": "custom",
"supported_endpoint_types": [
"openai"
]
},
{
"id": "qwen3-vl-flash",
"object": "model",
"created": 1626777600,
"owned_by": "custom",
"supported_endpoint_types": [
"openai"
]
},
{
"id": "qwen3.6-max-preview",
"object": "model",
"created": 1626777600,
"owned_by": "custom",
"supported_endpoint_types": [
"openai"
]
},
{
"id": "text-embedding-v4",
"object": "model",
"created": 1626777600,
"owned_by": "custom",
"supported_endpoint_types": [
"openai"
]
},
{
"id": "kimi-k2.5",
"object": "model",
"created": 1626777600,
"owned_by": "moonshot",
"supported_endpoint_types": [
"openai"
]
},
{
"id": "qwen3.6-plus",
"object": "model",
"created": 1626777600,
"owned_by": "custom",
"supported_endpoint_types": [
"openai"
]
},
{
"id": "deepseek-v4-flash",
"object": "model",
"created": 1626777600,
"owned_by": "deepseek",
"supported_endpoint_types": [
"openai"
]
}
],
"object": "list",
"success": true
}

View File

@@ -0,0 +1,621 @@
# PDF 文档转在线评测题库需求设计
## 1. 背景与问题
当前仓库已经具备一条完整的评测主链路:
- `main.py --scenario ...`
- 加载 dataset
- 标准化样本
- 调用 adapter
- 使用 `ragas` 评分
- 写出本地运行资产
但这条链路默认前提是“评测数据已经存在”。现实里RAG 项目的评测样本往往首先来自大量原始文档,例如 PDF 规范、制度、手册、法规或技术说明。没有一条稳定的“文档 -> 题库 dataset”生成链路平台就仍然停留在手工准备数据的阶段。
你给出的外部示例项目已经验证了两件关键能力:
- 可以用阿里云文档解析服务对 PDF 做异步解析
- 可以把解析结果归一成结构节点、语义块和可追溯切片
因此,本轮需求设计的目标不是建设一个完整知识库系统,而是在当前评测平台里补上一条最小可用的数据生产链路,让原始 PDF 可以生成可复核的在线评测题库。
## 2. 目标
本需求设计的 V1 目标如下:
- 支持从单个 PDF 或一个 PDF 目录批量生成在线评测题库
- 解析能力基于阿里云文档解析服务
- 题目生成以单文档为边界,不跨文档混合
- 输出结果是可直接接入当前 `online` 评测模式的 dataset 草稿
- 输出结果保留页码、章节、来源 chunk 等证据链,便于人工复核
- 复用当前仓库的本地文件优先原则,不引入数据库依赖
## 3. 非目标
V1 明确不覆盖以下范围:
- 不建设向量库入库能力
- 不建设文档上传 Web UI
- 不建设多租户文档中心
- 不支持跨文档综合题
- 不支持 Office 文档、图片包、网页抓取等多格式输入
- 不自动生成最终 gold dataset
- 不在 V1 中产出离线评测必需的 `answer / contexts`
- 不复用外部项目中与向量化落库、知识库写入直接耦合的域模型
换句话说V1 的目标是 **先把 PDF 文档转成“可人工复核的在线评测题库草稿”**,而不是一步演进成完整 RAG 数据工厂。
## 4. 用户价值
引入这条链路后,平台可以覆盖以下工作方式:
1. 用户准备一批 PDF 文档
2. 系统调用阿里云文档解析服务获取结构化版面结果
3. 系统把版面结果归一成可追溯的 chunk
4. 系统用 LLM 基于 chunk 生成问题与参考答案草稿
5. 用户在导出的 dataset 上做人工复核
6. 用户把复核后的 dataset 直接接入现有 `online` 评测场景
这样平台就同时具备:
- 数据生产能力
- 数据复核能力
- 在线评测接入能力
- 结果资产沉淀能力
## 5. V1 核心决策
本轮设计固定以下产品决策:
- 输入文档范围:仅 `PDF`
- 题目生成范围:仅 `单文档题`
- dataset 目标类型:`在线题库`
- 发布方式:`先生成草稿,再人工复核`
- 解析服务:阿里云文档解析
- 解析失败默认策略:`fail`
这些决策不再留给后续实现阶段临时判断。
## 6. 目标架构总览
在现有评测架构之外新增一条“dataset build”链路
```text
PDF files
-> dataset build config
-> aliyun parser gateway
-> layout normalization
-> source chunks
-> LLM question generation
-> draft online dataset
-> review
-> existing online evaluation flow
```
该链路与现有评测链路的关系如下:
- `dataset build` 负责“评测输入怎么生产”
- 现有 `rag_eval/execution/` 负责“生产好的评测输入怎么跑分”
二者职责分离,不相互污染。
## 7. 模块边界设计
### 7.1 CLI 入口
保留现有评测入口不变:
```powershell
python main.py --scenario scenarios/offline/sample-offline.yaml
```
新增一个用于构建 dataset 的入口:
```powershell
python main.py --dataset-build-config scenarios/dataset_build/sample-pdf-build.yaml
```
两个入口互斥,避免一次命令同时承担“建题库”和“跑评测”两个职责。
### 7.2 新增模块目录
新增主包:
```text
rag_eval/
dataset_builder/
__init__.py
models.py
schema.py
runner.py
writers.py
sources.py
parser/
__init__.py
aliyun_docmind_gateway.py
aliyun_document_parser.py
aliyun_layout_normalizer.py
generator/
__init__.py
question_generator.py
validators.py
```
职责划分如下:
- `schema.py`:校验 dataset build YAML
- `models.py`:定义 job、解析结果、source chunk、生成样本等内部模型
- `runner.py`:串联一次完整 build job
- `writers.py`:写出 dataset 和本地资产
- `sources.py`:发现输入 PDF 文件
- `parser/`:适配阿里云文档解析能力
- `generator/`:调用 LLM 生成题目草稿并做输出校验
### 7.3 外部示例代码复用策略
允许从外部项目参考并迁移以下能力:
- `aliyun_docmind_gateway.py`
- `aliyun_document_parser.py`
- `aliyun_layout_normalizer.py`
但复用范围只限于:
- 异步解析任务提交与轮询
- layout 拉取
- 结构节点提取
- 语义块合并
- 可追溯切片构建
不迁移以下职责:
- 向量库入库
- embedding 持久化
- 外部知识库 chunk 域模型
- 知识库索引流程
## 8. 配置设计
### 8.1 新增 YAML 类型
新增一类配置文件,例如:
```text
scenarios/
dataset_build/
sample-pdf-build.yaml
```
### 8.2 配置字段
V1 的 dataset build YAML 结构固定如下:
```yaml
job_name: legal-pdf-question-bank
input:
path: ../../datasets/raw/pdfs
glob: "*.pdf"
parser:
provider: aliyun_docmind
failure_mode: fail
generation:
model: qwen-long
output_type: online_question_bank
review_mode: draft_with_manual_review
max_questions_per_document: 10
max_source_chunks_per_question: 3
output:
dataset_path: ../../datasets/raw/generated/legal_question_bank.csv
artifact_dir: ../../outputs/dataset-builds/legal-pdf-question-bank
runtime:
max_documents: 20
```
### 8.3 字段约束
- `job_name`:必填,作为本次构建任务名称
- `input.path`:必填,支持单文件或目录
- `input.glob`:可选,默认 `*.pdf`
- `parser.provider`V1 固定为 `aliyun_docmind`
- `parser.failure_mode``fail | skip`,默认 `fail`
- `generation.model`:可选,允许覆盖默认生成模型
- `generation.output_type`V1 固定为 `online_question_bank`
- `generation.review_mode`V1 固定为 `draft_with_manual_review`
- `generation.max_questions_per_document`:正整数,默认 `10`
- `generation.max_source_chunks_per_question`:正整数,默认 `3`
- `output.dataset_path`:必填,最终 dataset 输出路径
- `output.artifact_dir`:必填,运行资产根目录
- `runtime.max_documents`:可选,用于限制一次处理文档数
## 9. 环境变量设计
### 9.1 阿里云解析配置
`rag_eval/settings.py` 中新增以下环境变量读取:
- `ALIBABA_ACCESS_KEY_ID`
- `ALIBABA_ACCESS_KEY_SECRET`
- `ALIBABA_ENDPOINT`
- `ALIYUN_PARSE_POLL_INTERVAL_SECONDS`
- `ALIYUN_PARSE_TIMEOUT_SECONDS`
- `ALIYUN_PARSE_LAYOUT_STEP_SIZE`
- `ALIYUN_LLM_ENHANCEMENT`
- `ALIYUN_ENHANCEMENT_MODE`
- `DOCUMENT_PARSE_ARTIFACT_PREFIX`
- `PARSER_FAILURE_MODE`
### 9.2 题库生成模型配置
新增环境变量:
- `DATASET_GENERATOR_MODEL`
默认优先级如下:
1. dataset build YAML 中的 `generation.model`
2. `.env` 中的 `DATASET_GENERATOR_MODEL`
3. 代码默认值
### 9.3 密钥管理要求
设计文档只引用环境变量名,不在仓库文档中记录任何明文 AK/SK。当前已经暴露在会话里的密钥需要单独轮换这属于实现前置的安全动作。
## 10. 核心数据模型设计
### 10.1 `DatasetBuildJob`
表示一次 PDF -> dataset 生成任务。
核心字段:
- `job_name`
- `input_path`
- `input_glob`
- `parser_provider`
- `failure_mode`
- `generation_model`
- `output_type`
- `review_mode`
- `dataset_path`
- `artifact_dir`
- `runtime`
### 10.2 `ParsedDocument`
表示一个 PDF 经解析和归一化后的文档。
核心字段:
- `doc_id`
- `doc_name`
- `raw_text`
- `structure_nodes`
- `semantic_blocks`
- `source_chunks`
- `metadata`
### 10.3 `SourceChunk`
`SourceChunk` 是 V1 最关键的证据单元,用于生成题目和支持人工复核。
字段固定为:
- `chunk_id`
- `doc_id`
- `doc_name`
- `text`
- `page_start`
- `page_end`
- `section_path`
- `section_title`
- `source_layout_ids`
设计原则:
- 每个 chunk 必须能反查来源页码
- 每个 chunk 必须能反查章节路径
- 每个 chunk 必须能反查原始 layout id
- 每个 chunk 只服务题库生成和证据追溯,不承担向量化职责
### 10.4 `DraftQuestionSample`
表示一条待复核的在线评测样本草稿。
字段固定为:
- `sample_id`
- `question`
- `ground_truth`
- `scenario`
- `language`
- `doc_id`
- `doc_name`
- `section_path`
- `page_start`
- `page_end`
- `source_chunk_ids`
- `question_type`
- `difficulty`
- `review_status`
- `review_notes`
### 10.5 枚举约束
- `review_status`: `draft | approved | rejected | needs_edit`
- `question_type`: `fact | summary | procedure | comparison`
- `difficulty`: `easy | medium | hard`
## 11. 文档解析设计
### 11.1 解析输入范围
V1 仅接受:
- 单个 `.pdf` 文件
- 或一个包含多个 `.pdf` 的目录
目录模式下默认按 `input.glob` 扫描,默认值为 `*.pdf`
### 11.2 解析流程
每个 PDF 的处理过程固定为:
1. 发现文件
2. 创建阿里云 Docmind client
3. 提交异步解析任务
4. 轮询直到成功、失败或超时
5. 分页拉取全量 layout
6. 归一成结构节点、语义块和 source chunk
7. 写出中间资产
### 11.3 版面归一化规则
从外部示例代码中沿用以下核心规则:
- 识别标题层级
- 跳过目录页内容
- 合并连续段落文本
- 抽取表格为可检索纯文本
- 保留图注类文本
- 按固定窗口做长文本切块
- 为每个 chunk 注入章节头信息和页码追溯信息
### 11.4 错误处理
支持两种失败模式:
- `fail`:任一文档解析失败则整个 job 失败
- `skip`:记录失败文档,继续处理其余文档
V1 默认策略为 `fail`
## 12. 题库生成设计
### 12.1 生成单元
题目生成单元固定为“单文档内的一组 section-aware source chunks”。
约束如下:
- 一条题目只能引用同一个 `doc_id`
- 一条题目最多引用 `3` 个 chunk
- 不允许跨文档混合证据
### 12.2 生成输出
每个候选题必须产出:
- `question`
- `ground_truth`
- `source_chunk_ids`
- `question_type`
- `difficulty`
### 12.3 数量控制
V1 默认:
- 每个文档最多生成 `10` 条题
- 每组 chunk 最多生成 `1` 条题
实现时必须做覆盖率与多样性平衡,避免所有问题只集中在文档开头章节。
### 12.4 复核模式
V1 不自动发布最终 dataset只输出草稿。
草稿规则:
- `review_status` 初始一律写为 `draft`
- `review_notes` 初始为空
- 人工可在 CSV 中修订问题、答案与审核状态
### 12.5 自动校验
候选题进入最终 dataset 前必须通过以下校验:
- `question` 非空
- `ground_truth` 非空
- `source_chunk_ids` 非空
- 引用的 chunk 必须真实存在
- 所有引用 chunk 必须来自同一文档
- `question_type``difficulty` 必须落在允许枚举内
自动校验失败的候选题不进入最终 draft CSV。
### 12.6 去重规则
同一文档内执行如下去重:
- 问题文本归一化后完全相同则去重
- 引用 chunk 完全相同且参考答案语义近似的候选题只保留一条
V1 去重目标是控制明显重复,不追求复杂聚类算法。
## 13. 输出资产设计
### 13.1 Dataset 输出
最终 dataset 默认输出到:
```text
datasets/raw/generated/<job_name>.csv
```
允许由 YAML 的 `output.dataset_path` 覆盖。
### 13.2 运行资产目录
每次构建任务的运行资产输出到:
```text
outputs/dataset-builds/<job_name>/<run_id>/
```
### 13.3 必须输出的资产
每次运行至少写出以下文件:
- `documents.jsonl`
- `semantic_blocks.jsonl`
- `source_chunks.jsonl`
- `dataset_draft.csv`
- `parse_failures.csv`
- `metadata.json`
含义如下:
- `documents.jsonl`:逐文档解析摘要
- `semantic_blocks.jsonl`:逐语义块中间结果
- `source_chunks.jsonl`:逐切片证据结果
- `dataset_draft.csv`:生成后的题库草稿
- `parse_failures.csv`:失败文档清单
- `metadata.json`:运行元数据、配置快照、统计结果
## 14. 与现有评测链路的兼容性修正
### 14.1 当前问题
当前仓库的文档设计已经说明 `online` 模式往往只需要:
- `question`
- `ground_truth`
然后由 adapter 在评测时补齐:
- `answer`
- `contexts`
但当前 `rag_eval/datasets/normalizers.py` 仍然把 `contexts / answer / ground_truth` 统一视作必填。这与文档目标架构不一致,也会直接阻塞本需求设计生成的在线题库接入。
### 14.2 修正原则
后续实现必须把 dataset 校验改成按 mode 分流:
- `offline` 模式必须具备 `question / contexts / answer / ground_truth`
- `online` 模式必须具备 `question / ground_truth`
- `online` 模式允许 `contexts / answer` 在初始数据集中为空
### 14.3 设计影响
这个修正不是附属优化,而是本需求能够成立的前置条件。否则生成出来的在线题库无法进入当前评测主流程。
## 15. 流程设计
一次完整的 dataset build job 执行流程如下:
1. 读取 `--dataset-build-config`
2. 校验 YAML 并生成 `DatasetBuildJob`
3. 扫描输入 PDF
4. 按顺序或受控并发处理每个文档
5. 调用阿里云文档解析
6. 归一化 layout生成 `ParsedDocument`
7. 萃取 `SourceChunk`
8. 基于 `SourceChunk` 调用 LLM 生成题库草稿
9. 对候选题做结构校验与去重
10. 写出 `dataset_draft.csv`
11. 写出中间 artifacts 和失败清单
12. 人工复核后,将复核版本作为 `online` 评测输入
## 16. 测试设计
### 16.1 配置测试
需要覆盖:
- `--scenario``--dataset-build-config` 互斥
- 缺失必填字段
- 非法枚举值
- 输入路径不存在
- 输入目录中没有 PDF
### 16.2 解析测试
使用 mocked 阿里云响应覆盖:
- 提交成功
- 状态轮询成功
- 状态轮询超时
- 任务失败
- 返回空 layouts
同时要覆盖版面归一化规则:
- 目录页跳过
- 标题层级继承
- 表格扁平化
- 图注抽取
- 长文本切块
### 16.3 题库生成测试
使用 mocked LLM 输出覆盖:
- 正常结构化生成
- 空题目
- 缺失 ground truth
- 引用不存在 chunk
- 跨文档引用
- 重复问题去重
### 16.4 端到端测试
需要至少有一组 mocked parser + mocked generator 的端到端流程测试,验证:
- 单 PDF 输入
- 多 PDF 输入
- `fail` 模式
- `skip` 模式
- 所有 artifact 均成功写出
### 16.5 评测回归测试
需要新增测试确保:
- 只包含 `question / ground_truth / metadata` 的在线题库能被加载
- adapter 补齐 `answer / contexts` 后,现有 evaluator 能继续跑完指标
## 17. 实施顺序建议
为了降低风险,后续实现建议按以下顺序推进:
1. 先扩展 `main.py` 和配置层,增加 dataset build 命令入口
2. 再扩展 `settings.py` 与依赖,接入阿里云解析配置
3. 迁移 parser gateway 与 layout normalizer
4. 落地 `dataset_builder` 的 models、runner、writers
5. 实现 LLM 题库生成与输出校验
6. 修正现有 online dataset 校验逻辑
7. 补测试、样例 YAML 和文档
## 18. 最终结论
本需求设计固定了一个清晰、可落地的 V1 范围:
- 用阿里云解析 PDF
- 把解析结果转成可追溯 source chunks
- 用 LLM 基于单文档内容生成在线评测题库草稿
- 用人工复核保证最终质量
- 复核后的题库直接接入现有 online 评测流程
这个设计刻意收窄了输入格式、题型边界和自动化深度,目的不是保守,而是先确保整条链路能够在当前仓库架构中闭环,并且不给后续实现留下需要临场决策的空白。

View File

@@ -0,0 +1,623 @@
# RAG 评测平台架构设计
## 1. 背景与问题
当前仓库已经有一个可运行的离线评测原型,其能力已经收敛到统一的 `main.py --scenario ...` 入口与 `rag_eval/` 分层模块中。这个原型适合验证以下问题:
- 离线导出的 RAG 样本能否被标准化
- `ragas` 指标能否稳定跑通
- 基础评分结果能否写出为 CSV
但当评测对象从“一个数据文件、一次评测”演进为“多个 RAG 应用、多个数据集、多轮实验、多种配置对比”时,单脚本结构会迅速暴露问题:
- **职责耦合过高**:参数解析、数据加载、样本标准化、模型创建、指标执行、结果持久化全部混在一个脚本里
- **难以扩展在线模式**:当前实现默认输入已经包含 `answer``contexts`,不适合直接接入运行中的 RAG 应用
- **难以复现实验**:输出主要是单个 CSV缺少运行快照、元数据和标准汇总
- **难以对比不同方案**:没有统一场景配置,不便系统化比较应用版本、模型和指标组合
- **难以承载多应用接入**:缺少稳定的应用适配层,无法把 HTTP 服务型应用和本地 Python 应用纳入统一流程
因此本仓库需要从“离线评测脚本”演进为“RAG 评测平台骨架”。
## 2. 设计目标
目标架构需要满足以下设计目标:
- **平台化**:从一次性脚本升级为长期可演进的工程结构
- **可扩展**:新增应用接入方式、数据集格式和指标时,不需要重写主流程
- **可复现**:每次运行都能保留完整配置快照和结果资产
- **可对比**:不同应用、模型、检索策略和提示词方案可以稳定横向比较
- **统一双模式**:离线评测与在线评测共享同一条核心数据流
- **多对象接入**:支持多应用、多数据集、多模型和多场景组合
- **本地优先**:第一阶段以本地文件资产为中心,不引入数据库依赖
## 3. 非目标
本轮架构设计明确不覆盖以下方向:
- 不建设数据库中心化评测平台
- 不建设前端控制台或 Web UI
- 不建设远程任务调度与分布式执行系统
- 不引入服务端依赖作为评测执行前提
- 不在本轮实现完整目录骨架和模块代码,只固定设计边界
换句话说,当前阶段的目标是 **先把本地文件驱动的平台骨架设计定案**,而不是一步做成完整产品。
## 4. 目标架构总览
目标平台按照职责分为六层:
### 4.1 配置层
负责读取、校验和标准化 YAML 场景配置,产出统一的 `Scenario` 对象。
职责包括:
- 解析场景配置文件
- 应用默认值
- 校验模式、模型、指标和运行参数
- 生成运行快照
### 4.2 应用接入层
负责连接外部 RAG 应用或本地 Python RAG 函数,把调用结果统一转换为标准输出结构。
职责包括:
- 定义统一请求输入
- 屏蔽不同应用协议差异
- 返回可映射为 `answer``contexts` 的标准响应
### 4.3 数据集层
负责加载原始样本、标准化样本和在线问题集,并统一转换为平台的标准评测样本结构。
职责包括:
- 读取 CSV、Excel、JSONL 等数据源
- 规范化字段
- 校验必填字段
- 输出可进入评测核心层的标准样本集合
### 4.4 指标层
负责按场景配置装配评测指标,形成可执行的指标流水线。
职责包括:
- 构建 judge model 与 embedding model
- 根据场景启用指定指标
- 屏蔽底层评测库差异
### 4.5 执行层
负责把配置、数据、应用和指标串成一次完整运行。
职责包括:
- 数据准备
- 在线应用调用或离线结果加载
- 样本标准化
- 并发执行评分
- 错误捕获与降级
- 结果合并
### 4.6 结果层
负责把一次运行的所有结果沉淀为本地文件资产。
职责包括:
- 写出评分明细
- 写出无效样本
- 写出场景快照
- 写出汇总报告
- 写出元数据
## 5. 领域模型
为了让后续实现边界清晰,平台核心对象固定为以下六类。
### 5.1 `AppAdapter`
表示一个可被评测框架调用的 RAG 应用适配器。
职责:
- 接收标准问题输入
- 调用实际 RAG 应用
- 返回标准化响应结果
最小抽象能力:
- 输入:`question` 与可选上下文参数
- 输出:必须能映射为 `answer``contexts`
### 5.2 `Dataset`
表示一次评测所使用的数据集定义。
职责:
- 描述数据来源
- 加载原始样本
- 输出标准评测样本
需要同时兼容:
- 离线导入样本
- 在线问题样本
- 后续可能的多文件数据集或分片数据集
### 5.3 `Scenario`
表示一次完整实验的配置定义,是平台的统一实验入口。
职责:
- 声明评测模式
- 绑定应用、数据集、模型和指标
- 定义输出目录与运行参数
`Scenario` 必须由 YAML 场景配置生成,而不是在代码里零散拼装。
### 5.4 `Evaluator`
表示一次评测执行器。
职责:
- 根据场景驱动一次完整运行
- 调用数据集、应用适配器和指标流水线
- 产出运行结果与错误信息
### 5.5 `MetricPipeline`
表示一组按场景组合好的指标执行单元。
职责:
- 初始化 judge model 与 embedding model
- 按统一接口执行多个指标
- 输出结构化评分结果
### 5.6 `RunArtifact`
表示一次运行沉淀的结果资产集合。
职责:
- 固定结果文件布局
- 保存配置快照、评分结果、异常样本、汇总报告和元数据
- 作为后续复现、审计和对比的最小单元
## 6. 应用接入层设计
应用接入层把“如何调用 RAG 应用”从“如何做评测”中解耦。后续只保留两类一等公民适配器:`HTTP Adapter``Python Function Adapter`
### 6.1 HTTP Adapter
适用于外部部署的 RAG 服务。
输入约束:
- 标准问题 `question`
- 可选上下文参数,例如租户、知识库、会话配置、检索参数
输出约束:
- 必须能够映射出 `answer`
- 必须能够映射出 `contexts`
推荐统一响应语义:
```json
{
"answer": "string",
"contexts": ["context 1", "context 2"],
"raw_response": {}
}
```
说明:
- 真实 HTTP 响应可以与此不同
- 但 Adapter 层必须把实际响应转换成上述平台内部语义
- `raw_response` 可选保留,用于调试和审计,但不作为核心评测字段依赖
### 6.2 Python Function Adapter
适用于本地 Python 形式的 RAG 应用例如本地函数、SDK 包装器或 Notebook 中已封装的检索问答逻辑。
输入输出约束与 HTTP Adapter 保持同构:
- 输入:`question` 与可选上下文参数
- 输出:必须能映射为 `answer``contexts`
推荐函数语义:
```python
def run(question: str, **kwargs) -> dict:
return {
"answer": "...",
"contexts": ["...", "..."],
"raw_response": {...},
}
```
设计原则:
- 上层评测执行器不应该关心底层是 HTTP 调用还是 Python 函数调用
- 只要符合统一输入输出契约,就可以接入同一条评测管线
## 7. 数据集层设计
数据集层的目标是把不同来源的样本统一为同一标准格式。
### 7.1 原始样本
原始样本是未经平台标准化的输入,可能来自:
- 业务系统导出的 CSV 或 Excel
- 在线评测时的问题集
- 后续数据清洗脚本生成的中间文件
原始样本允许存在额外字段,但不应该直接进入评测核心层。
### 7.2 标准化评测样本
无论数据来源如何,进入评测核心层前都必须变成同一标准样本结构。最小核心字段固定为:
- `question`
- `contexts`
- `answer`
- `ground_truth`
可选元数据字段可包括:
- `sample_id`
- `scenario`
- `language`
- `retrieval_config`
约束如下:
- `question`:字符串
- `contexts`:有序文本列表
- `answer`:字符串
- `ground_truth`:字符串
### 7.3 在线生成样本与离线导入样本的统一格式
双模式统一约束如下:
- **离线模式**:输入文件本身已包含 `question / contexts / answer / ground_truth`
- **在线模式**:问题集先提供 `question` 与必要元信息,应用调用后补齐 `answer / contexts`,再与参考答案或标注结果结合形成标准样本
统一要求:
- 不允许在线模式绕过标准样本结构直接进入指标执行层
- 不允许离线模式在结果写出前使用独立的资产格式
这保证了后续的指标层、执行层和结果层可以完全共享。
## 8. 场景配置设计
YAML 是未来统一实验入口。README 只给最小示例,详细字段定义在本节固定。
### 8.1 最小骨架字段
```yaml
scenario_name: legal-assistant-offline-baseline
mode: offline
app_adapter: null
dataset: datasets/normalized/legal_assistant_baseline.csv
judge_model: deepseek-v4-flash
embedding_model: text-embedding-v3
metrics:
- faithfulness
- answer_relevancy
- context_recall
- context_precision
output_dir: runs/legal-assistant-offline-baseline
runtime:
batch_size: 4
```
### 8.2 字段说明
- `scenario_name`
- 场景名称,用于标识一次实验
- `mode: offline | online`
- 评测模式
- `app_adapter`
- 应用适配器定义;离线模式可为 `null`,在线模式必须提供
- `dataset`
- 数据集路径或数据集定义引用
- `judge_model`
- 负责评分类指标推理的模型
- `embedding_model`
- 负责向量相关指标的模型
- `metrics`
- 本次启用的指标列表
- `output_dir`
- 本次运行结果输出目录
- `runtime.batch_size`
- 并发批次大小
### 8.3 在线模式的 `app_adapter` 形态
后续建议支持如下两种声明方式:
```yaml
app_adapter:
type: http
endpoint: https://example-rag/api/ask
method: POST
timeout_seconds: 30
```
```yaml
app_adapter:
type: python
callable: apps.legal_assistant.adapter:run
```
说明:
- 这是配置接口约束,不代表当前仓库已经具备解析实现
- 字段可以后续扩充,但类型边界本轮即固定为 `http``python`
## 9. 评测执行层设计
评测执行层负责把一次实验变成稳定、可审计的运行流程。执行顺序固定如下。
### 9.1 数据准备
根据 `Scenario` 加载数据集定义,并读取原始样本。
### 9.2 应用调用或离线加载
- `offline` 模式:直接读取包含评测核心字段的离线样本
- `online` 模式:读取问题集,调用 `AppAdapter` 获取 `answer``contexts`
### 9.3 样本标准化
所有样本统一映射为标准结构:
- `question`
- `contexts`
- `answer`
- `ground_truth`
不合法样本在此阶段被分流为无效样本,而不是在指标执行阶段失败后再回溯处理。
### 9.4 指标编排
根据 `Scenario.metrics` 创建 `MetricPipeline`,并装配:
- judge model
- embedding model
- 指标实例
### 9.5 并发控制
执行层负责并发上限,不把并发策略散落到各指标实现中。
最低要求:
- 允许通过 `runtime.batch_size` 控制最大并发
- 在线调用和指标评分后续可以拥有独立并发策略
- 并发策略由执行层统一调度
### 9.6 错误捕获
错误需要按层次捕获:
- 数据加载错误
- 应用调用错误
- 指标执行错误
- 结果写出错误
原则:
- 单条样本失败不应默认导致整批运行失败
- 失败信息要体现在结果资产中
- 不应只把错误打印到控制台后丢失
### 9.7 结果合并
最终输出应包含:
- 原始样本字段
- 标准化样本字段
- 指标分数
- 错误字段
- 运行元信息
## 10. 结果资产设计
平台统一采用“run 目录”模式,不引入数据库前置依赖。每次运行输出固定如下:
```text
runs/<run_id>/
├── scenario.snapshot.yaml
├── scores.csv
├── invalid.csv
├── summary.md
└── metadata.json
```
各文件职责固定如下。
### 10.1 `runs/<run_id>/scenario.snapshot.yaml`
保存本次评测实际使用的场景快照,确保即使原始场景文件后续变化,也能复现本次运行。
### 10.2 `runs/<run_id>/scores.csv`
保存逐样本评测结果,至少包括:
- 标准样本字段
- 指标分数
- 错误信息
- 模型与运行时间等元信息字段
### 10.3 `runs/<run_id>/invalid.csv`
保存标准化失败、关键字段缺失或格式不合法的样本,用于追踪数据质量问题。
### 10.4 `runs/<run_id>/summary.md`
保存面向人阅读的汇总结论,例如:
- 总样本数、有效样本数、无效样本数
- 各指标均值
- 分组统计
- 低分样本观察
### 10.5 `runs/<run_id>/metadata.json`
保存机器可读元数据,例如:
- `run_id`
- `scenario_name`
- `mode`
- `judge_model`
- `embedding_model`
- `started_at`
- `finished_at`
- 代码版本或 Git commit后续可选
统一约束:
- 无论数据来自在线还是离线模式,结果都统一写入本地文件资产
- 在引入数据库前,不允许出现只写数据库不落本地文件的实现分支
## 11. 推荐代码目录结构
后续推荐代码骨架如下。该结构作为实现边界约束,供后续重构与新增模块直接遵循。
```text
.
├── apps/
│ ├── <app_name>/
│ │ ├── adapter.py
│ │ └── README.md
├── datasets/
│ ├── raw/
│ ├── normalized/
│ └── samples/
├── scenarios/
│ ├── offline/
│ └── online/
├── rag_eval/
│ ├── config/
│ │ ├── loader.py
│ │ ├── schema.py
│ │ └── validators.py
│ ├── adapters/
│ │ ├── base.py
│ │ ├── http.py
│ │ └── python.py
│ ├── datasets/
│ │ ├── loader.py
│ │ ├── normalizers.py
│ │ └── validators.py
│ ├── metrics/
│ │ ├── factory.py
│ │ ├── pipeline.py
│ │ └── registry.py
│ ├── execution/
│ │ ├── evaluator.py
│ │ ├── runner.py
│ │ ├── concurrency.py
│ │ └── errors.py
│ ├── reporting/
│ │ ├── artifacts.py
│ │ ├── summary.py
│ │ └── writers.py
│ ├── shared/
│ │ ├── types.py
│ │ ├── models.py
│ │ └── utils.py
│ ├── compat.py
│ └── settings.py
├── runs/
├── docs/
├── tests/
├── main.py
└── README.md
```
模块分层约束固定如下:
- `rag_eval/config/`:配置加载与校验
- `rag_eval/adapters/`HTTP / Python 应用接入
- `rag_eval/datasets/`:加载、标准化、校验
- `rag_eval/metrics/`:指标装配与扩展
- `rag_eval/execution/`:运行编排、并发、错误处理
- `rag_eval/reporting/`:结果写出、汇总、报告
- `rag_eval/shared/`:通用类型与工具
后续实现不应再把这些职责重新合并回单个入口脚本。
## 12. 演进路线
当前仓库已经完成从单脚本离线评测到统一 CLI 的第一轮收敛,后续重点不再是移除旧入口,而是继续完善场景、接入、结果治理与测试能力。
推荐拆分路径如下:
### 12.1 第一阶段:抽离离线模式公共能力(已完成)
当前已完成以下能力下沉:
- 输入文件加载
- 样本标准化
- 指标装配
- 结果写出
结果是离线模式已经摆脱“单文件全包”的结构,进入统一模块分层。
### 12.2 第二阶段:引入 `Scenario` 与 YAML 配置(已完成)
当前主入口已经改为读取 YAML 场景配置。
### 12.3 第三阶段:引入应用适配器(已完成第一版)
当前已具备在线模式的一等公民适配器:
- HTTP Adapter
- Python Function Adapter
在线和离线模式已经共享同一条标准化与评分流程。
### 12.4 第四阶段:统一 CLI 入口(已完成)
当前统一 CLI 入口如下:
```powershell
python main.py --scenario scenarios/offline/sample.yaml
```
旧的兼容入口已经移除,仓库统一以 scenario 驱动的入口为准。
### 12.5 第五阶段:完善结果治理与测试
补齐:
- `runs/` 目录规范落地
- 汇总报告生成
- 在线/离线统一回归测试
- 多应用与多场景对比样例
## 结论
本设计文档的核心决策已经固定:
- 统一采用 YAML 作为实验入口
- 统一支持在线与离线双模式
- 统一以标准样本结构进入评测核心层
- 统一把结果写入本地 `run` 目录资产
- 统一按照配置层、接入层、数据层、指标层、执行层、结果层拆分代码
后续工程实现应直接遵循这些边界推进,而不再重新讨论整体骨架。

View File

@@ -0,0 +1,416 @@
# RAG 评测引擎链路说明
## 1. 这份文档解决什么问题
`docs/rag-eval-architecture.md` 主要回答“为什么这样分层、模块边界是什么”。
这份文档回答的是另一件事:**一次评测在代码里到底是怎么跑起来的**。
如果你现在对下面这些问题还有点混淆,这份文档就是给你的:
- `rag_eval/` 到底是评测什么的
- `apps/` 在整个架构里扮演什么角色
- `offline``online` 模式的区别是什么
- dataset、adapter、metrics、reporting 是怎么串起来的
---
## 2. 一句话理解整个引擎
这套系统本质上是一条标准化评测流水线:
```text
scenario -> dataset -> normalize -> app adapter -> metrics -> reporting -> run artifacts
```
也可以拆成更容易理解的话:
1. 先读取一份评测场景配置
2. 再读取待评测数据
3. 把原始数据标准化成统一样本结构
4. 如果需要,调用你的 RAG 应用补齐 `answer``contexts`
5.`ragas` 指标计算分数
6. 把结果写到本地 run 目录
---
## 3. 目录职责
### `rag_eval/`
这是**评测引擎本体**。它负责:
- 加载 scenario
- 加载 dataset
- 调用 app adapter
- 执行指标评分
- 写出结果资产
### `apps/`
这是**被评测应用的接入层**,不是评测框架本身。
这里放的是“你的 RAG 应用如何被框架调用”的示例或适配代码。例如:
```text
apps/
└── sample_python/
├── adapter.py
└── README.md
```
`apps/sample_python/` 的意义不是提供评测逻辑,而是演示:
- 如果你的应用是本地 Python 函数
- 它应该暴露什么接口
- 返回值要长什么样
### `scenarios/`
这是评测配置层,用 YAML 声明:
- 评测模式
- 数据集路径
- judge / embedding 模型
- 要跑哪些 metrics
- 输出目录
- 是否要调用 `http``python` adapter
### `datasets/`
这里存放评测输入数据。通常分为:
- `raw/`:原始输入
- `normalized/`:整理后的标准评测样本
### `outputs/` 或 `runs/`
这里存放每次评测生成的结果资产,比如:
- `scores.csv`
- `invalid.csv`
- `summary.md`
- `metadata.json`
---
## 4. 主入口链路
统一主链路最终都会走到:
- [rag_eval/execution/runner.py](/C:/Users/A200477427/Learnings/ragas-template/rag_eval/execution/runner.py:1)
核心入口函数是 `run_scenario()`,它负责把所有子模块串起来。
简化后的执行顺序是:
```text
run_scenario()
-> load_scenario()
-> build_adapter()
-> build_metric_pipeline()
-> Evaluator.evaluate()
-> write_run_artifacts()
```
你可以把它理解成整套系统的 orchestration 层。
---
## 5. Scenario 链路
scenario 是一次评测任务的“总配置”。
相关代码:
- [rag_eval/config/loader.py](/C:/Users/A200477427/Learnings/ragas-template/rag_eval/config/loader.py:1)
- [rag_eval/config/schema.py](/C:/Users/A200477427/Learnings/ragas-template/rag_eval/config/schema.py:1)
- [rag_eval/config/validators.py](/C:/Users/A200477427/Learnings/ragas-template/rag_eval/config/validators.py:1)
它定义的内容包括:
- `mode`: `offline``online`
- `dataset`
- `judge_model`
- `embedding_model`
- `metrics`
- `output_dir`
- `runtime`
- `app_adapter`
作用可以概括为一句话:
**scenario 决定“这次评测要怎么跑”。**
---
## 6. Dataset 链路
数据进入评测引擎后,不会直接拿原始 CSV 去打分,而是要先标准化。
相关代码:
- [rag_eval/datasets/loader.py](/C:/Users/A200477427/Learnings/ragas-template/rag_eval/datasets/loader.py:1)
- [rag_eval/datasets/validators.py](/C:/Users/A200477427/Learnings/ragas-template/rag_eval/datasets/validators.py:1)
- [rag_eval/datasets/normalizers.py](/C:/Users/A200477427/Learnings/ragas-template/rag_eval/datasets/normalizers.py:1)
它做三件事:
1. 读取 CSV / Excel / JSONL
2. 校验必要字段
3. 转成统一内部对象 `NormalizedSample`
统一样本结构定义在:
- [rag_eval/shared/models.py](/C:/Users/A200477427/Learnings/ragas-template/rag_eval/shared/models.py:1)
最关键字段是:
- `question`
- `contexts`
- `answer`
- `ground_truth`
这个统一结构是后续 metrics 和 reporting 能共用一条链路的前提。
---
## 7. Offline 和 Online 的真正区别
这是整个系统最关键的分叉点。
### Offline 模式
离线模式下dataset 里已经有完整评测字段:
- `question`
- `contexts`
- `answer`
- `ground_truth`
所以链路是:
```text
load dataset -> normalize -> score metrics -> write artifacts
```
这个模式不需要调用你的应用。
### Online 模式
在线模式下dataset 往往只有:
- `question`
- 一些 metadata
`answer``contexts` 需要评测时实时调用你的 RAG 应用拿回来。
所以链路会变成:
```text
load dataset -> normalize -> call app adapter -> enrich samples -> score metrics -> write artifacts
```
在线模式比离线模式多出来的核心环节,就是 **adapter 调用**
---
## 8. `apps/sample_python/` 到底是干什么的
这个目录是一个 **Python adapter 示例**
相关文件:
- [apps/sample_python/adapter.py](/C:/Users/A200477427/Learnings/ragas-template/apps/sample_python/adapter.py:1)
- [apps/sample_python/README.md](/C:/Users/A200477427/Learnings/ragas-template/apps/sample_python/README.md:1)
它演示了:如果你的 RAG 应用是本地 Python 代码,那么框架期望你提供一个这样的函数:
```python
def run(question: str, **kwargs) -> dict:
return {
"answer": "...",
"contexts": ["...", "..."],
"raw_response": {...},
}
```
也就是说,`apps/sample_python/` 不是评测引擎的一部分,而是“被评测应用”的一个参考接入模板。
它的作用是:
- 告诉你 Python 类型应用如何接入
-`python` adapter 一个最小可运行示例
- 让你后续把真实 RAG 逻辑替换进去
---
## 9. Adapter 链路
adapter 层的目标是:**把不同类型的目标应用,统一成同一套输入输出协议。**
相关代码:
- [rag_eval/adapters/base.py](/C:/Users/A200477427/Learnings/ragas-template/rag_eval/adapters/base.py:1)
- [rag_eval/adapters/http.py](/C:/Users/A200477427/Learnings/ragas-template/rag_eval/adapters/http.py:1)
- [rag_eval/adapters/python.py](/C:/Users/A200477427/Learnings/ragas-template/rag_eval/adapters/python.py:1)
当前支持两类 adapter
### `python`
适用于本地 Python 应用。
框架会根据 scenario 里的 `module:function` 动态加载函数,然后调用它。
### `http`
适用于独立 HTTP 服务。
框架会构造请求、解析响应,并映射到统一结构。
无论哪种 adapter最后都要返回统一结果
- `answer`
- `contexts`
- `raw_response`(可选)
这一步很关键,因为 metrics 层不应该关心底层到底是 HTTP 服务还是 Python 函数。
---
## 10. Evaluator 链路
评测执行核心在:
- [rag_eval/execution/evaluator.py](/C:/Users/A200477427/Learnings/ragas-template/rag_eval/execution/evaluator.py:1)
`Evaluator.evaluate()` 大致会做这些事:
1. 记录开始时间
2. 加载 dataset
3. 标准化样本
4. 如果是 `online`,先调用 adapter 补齐样本
5. 调用 metric pipeline 打分
6. 合并样本字段和评分结果
7. 返回 `EvaluationResult`
这里可以把 `Evaluator` 理解成:
**一次评测运行的总执行器**
---
## 11. Metric Pipeline 链路
相关代码:
- [rag_eval/metrics/factory.py](/C:/Users/A200477427/Learnings/ragas-template/rag_eval/metrics/factory.py:1)
- [rag_eval/metrics/pipeline.py](/C:/Users/A200477427/Learnings/ragas-template/rag_eval/metrics/pipeline.py:1)
- [rag_eval/metrics/registry.py](/C:/Users/A200477427/Learnings/ragas-template/rag_eval/metrics/registry.py:1)
这里的职责不是“决定评什么”,而是“把要评的指标真正跑起来”。
具体包括:
- 初始化 OpenAI client
- 创建 judge model / embedding model
- 根据 scenario 装配对应的 ragas metrics
- 并发执行单样本或批量评分
当前支持的指标包括:
- `faithfulness`
- `answer_relevancy`
- `context_recall`
- `context_precision`
所以 metric pipeline 的职责可以总结为:
**把标准样本转换成结构化评分结果。**
---
## 12. Reporting 链路
相关代码:
- [rag_eval/reporting/artifacts.py](/C:/Users/A200477427/Learnings/ragas-template/rag_eval/reporting/artifacts.py:1)
- [rag_eval/reporting/summary.py](/C:/Users/A200477427/Learnings/ragas-template/rag_eval/reporting/summary.py:1)
- [rag_eval/reporting/writers.py](/C:/Users/A200477427/Learnings/ragas-template/rag_eval/reporting/writers.py:1)
当评测完成后,结果不会只打印在终端,而是会沉淀成标准资产。
标准输出一般包括:
- `scenario.snapshot.yaml`
- `scores.csv`
- `invalid.csv`
- `summary.md`
- `metadata.json`
这是整套架构很重要的价值点,因为它让每次 run 都具备:
- 可复现性
- 可审计性
- 可对比性
---
## 13. 两条完整链路示意
### Offline 完整链路
```text
main.py
-> run_scenario()
-> load_scenario()
-> load_dataset_records()
-> normalize_records()
-> build_metric_pipeline()
-> score_samples()
-> write_run_artifacts()
```
特点:
- 不调用被评测应用
- 直接对现成样本评分
### Online 完整链路
```text
main.py
-> run_scenario()
-> load_scenario()
-> build_adapter()
-> load_dataset_records()
-> normalize_records()
-> adapter.enrich_sample()
-> build_metric_pipeline()
-> score_samples()
-> write_run_artifacts()
```
特点:
- 会先调用目标应用
- 再对实时生成的 `answer / contexts` 评分
---
## 14. 你应该怎么理解这套架构
如果只记一条心智模型,可以记这个:
- `rag_eval/` 负责“怎么评”
- `apps/` 负责“被评的应用怎么接进来”
- `datasets/` 负责“评测输入是什么”
- `scenarios/` 负责“这次评测要怎么配置”
- `reporting/` 负责“结果怎么沉淀”
从工程拆分上看,这个架构的核心价值不是“能跑一次评测”,而是:
- 可以反复跑
- 可以换应用跑
- 可以换数据跑
- 可以换模型跑
- 可以把每次实验的资产稳定留住
这也是它和一次性离线脚本的根本区别。

View File

@@ -0,0 +1,222 @@
# `sample-pdf-question-bank` 端到端使用说明
这篇文档对应仓库里的真实案例:先把 PDF 解析成 question bank再用 online evaluator 基于证据 chunk 生成答案并打分。
完整链路是:
```text
PDFs
-> dataset_build
-> sample-pdf-question-bank.csv + latest/source_chunks.jsonl
-> online adapter
-> answer + contexts
-> ragas metrics
```
## 1. 先准备环境
先复制环境变量模板:
```powershell
Copy-Item .env.example .env
```
这条案例链路依赖两类能力:
- OpenAI 兼容模型
- `OPENAI_API_KEY`
- `OPENAI_BASE_URL`
- 阿里云文档解析
- `ALIBABA_ACCESS_KEY_ID`
- `ALIBABA_ACCESS_KEY_SECRET`
- `ALIBABA_ENDPOINT`
默认还会用到这些模型配置:
- `DATASET_GENERATOR_MODEL=qwen3.6-plus`
- `RAGAS_JUDGE_MODEL=deepseek-v4-flash`
- `RAGAS_EMBEDDING_MODEL=text-embedding-v3`
如果少了 `OPENAI_API_KEY`dataset build 里的题库生成和 online eval 都无法运行。
如果少了阿里云凭据PDF 解析阶段无法运行。
## 2. 跑 dataset build
使用 sample 配置:
- config: [scenarios/dataset_build/sample-pdf-build.yaml](/C:/Users/A200477427/Learnings/ragas-template/scenarios/dataset_build/sample-pdf-build.yaml)
执行命令:
```powershell
uv run main.py --dataset-build-config scenarios/dataset_build/sample-pdf-build.yaml
or
.\.venv\Scripts\python.exe main.py --dataset-build-config scenarios/dataset_build/sample-pdf-build.yaml
```
这一步会做四件事:
1. 扫描 `datasets/raw/pdfs` 下的 PDF
2. 调用阿里云解析生成结构化 `source chunks`
3. 调用 LLM 生成 question bank 草稿
4. 写出稳定 dataset 和详细 run 资产
## 3. 看 build 产物
跑完以后,你会看到两类输出。
第一类是稳定入口,给后续 online eval 和教程使用:
- question bank CSV:
[datasets/raw/generated/sample-pdf-question-bank.csv](/C:/Users/A200477427/Learnings/ragas-template/datasets/raw/generated/sample-pdf-question-bank.csv)
- latest source chunks:
[source_chunks.jsonl](/C:/Users/A200477427/Learnings/ragas-template/outputs/dataset-builds/sample-pdf-question-bank/latest/source_chunks.jsonl)
- latest dataset draft:
[dataset_draft.csv](/C:/Users/A200477427/Learnings/ragas-template/outputs/dataset-builds/sample-pdf-question-bank/latest/dataset_draft.csv)
- latest metadata:
[metadata.json](/C:/Users/A200477427/Learnings/ragas-template/outputs/dataset-builds/sample-pdf-question-bank/latest/metadata.json)
第二类是带时间戳的 run 级资产,用来审计和排查:
- 目录模式:
`outputs/dataset-builds/sample-pdf-question-bank/<run_id>/`
其中常见文件有:
- `documents.jsonl`
- `semantic_blocks.jsonl`
- `source_chunks.jsonl`
- `dataset_draft.csv`
- `parse_failures.csv`
- `metadata.json`
理解这个区别很重要:
- 稳定入口用于“继续往下跑”
- 时间戳目录用于“回看某次具体构建”
## 4. question bank CSV 里是什么
sample build 输出的是 online-ready question bank不是离线评测格式。
核心字段包括:
- `question`
- `ground_truth`
- `source_chunk_ids`
- `doc_id`
- `doc_name`
它故意不预写 `answer`
原因是这条链路的目标是在线评测:由 adapter 在评测时,根据 `source_chunk_ids``source_chunks.jsonl` 里取证据,再调用模型生成 `answer`
## 5. 跑 online eval
使用 sample online scenario
- scenario: [scenarios/online/sample-pdf-question-bank-online.yaml](/C:/Users/A200477427/Learnings/ragas-template/scenarios/online/sample-pdf-question-bank-online.yaml)
- adapter: [apps/pdf_question_bank/adapter.py](/C:/Users/A200477427/Learnings/ragas-template/apps/pdf_question_bank/adapter.py)
执行命令:
```powershell
uv run main.py --scenario scenarios/online/sample-pdf-question-bank-online.yaml
or
.\.venv\Scripts\python.exe main.py --scenario scenarios/online/sample-pdf-question-bank-online.yaml
```
这个 scenario 的关键点有两个:
1. dataset 指向稳定 question bank CSV
2. `app_adapter.static_kwargs.source_chunks_path` 指向稳定的 `latest/source_chunks.jsonl`
因此,只要你重新跑过 sample dataset buildonline scenario 就不需要再手改时间戳路径。
## 6. online adapter 在做什么
`apps/pdf_question_bank/adapter.py` 的处理方式是固定的:
1. 从题库行里读取 `source_chunk_ids`
2. 打开 `source_chunks.jsonl`
3. 只解析被引用的 chunk
4. 把这些 chunk 文本原样作为 `contexts`
5. 用这些证据 prompt 模型生成 `answer`
6.`resolved_chunk_ids` 和模型响应写进 `raw_response`
所以评测关系是:
- `ground_truth` 是参考答案
- `answer` 是运行时生成答案
- `contexts` 是题目显式引用的证据块
这条链路没有单独做 retrieval。
它评测的是“给定明确证据后,应用/模型能否稳定生成正确答案”。
## 7. 结果在哪里看
online eval 完成后,结果会写到:
- `outputs/online/sample-pdf-question-bank/<run_id>/`
常见文件包括:
- `scores.csv`
- `invalid.csv`
- `summary.md`
- `metadata.json`
优先看这几个点:
- `scores.csv`:逐题指标分数
- `invalid.csv`:哪些样本因为 adapter 失败或空结果被剔除了
- `summary.md`:汇总视图
## 8. 常见问题
### `source_chunk_ids` 找不到
这通常表示 question bank CSV 和 `source_chunks.jsonl` 不是同一次 build 的产物。
正确做法:
1. 重新跑一次 `sample-pdf-build.yaml`
2. 确认 `outputs/dataset-builds/sample-pdf-question-bank/latest/source_chunks.jsonl` 已更新
3. 再运行 online scenario
### dataset build 成功,但 online eval 结果是 invalid
先看 `invalid.csv`
当前实现里,以下情况会进入 invalid
- adapter 生成 `answer` 为空
- adapter 返回 `contexts` 为空
- adapter 在解析 chunk 或调模型时抛异常
### 只想快速看离线 smoke不想重建题库
直接运行:
- [scenarios/offline/sample-pdf-offline-smoke.yaml](/C:/Users/A200477427/Learnings/ragas-template/scenarios/offline/sample-pdf-offline-smoke.yaml)
这个案例是固化好的离线 smoke dataset不依赖 online adapter。
## 9. 换成你自己的 PDF 时改哪里
如果你要复用这条模式处理自己的 PDF最少只改这几个点
1. 复制一份 dataset build YAML
2. 修改 `input.path` 指向你的 PDF 或 PDF 目录
3. 修改 `output.dataset_path` 为你的 question bank CSV
4. 修改 `output.artifact_dir` 为你的 build 资产根目录
5. 复制一份 online scenario
6. 修改 `dataset` 指向你的 question bank CSV
7. 修改 `app_adapter.static_kwargs.source_chunks_path` 指向你的 `artifact_dir/latest/source_chunks.jsonl`
不需要改 question bank CSV 的字段结构。
也不需要把 `answer` 预先写进 CSV。
如果你的 online answer 逻辑仍然是“根据显式证据块生成答案”,就可以继续复用:
- [apps/pdf_question_bank/adapter.py](/C:/Users/A200477427/Learnings/ragas-template/apps/pdf_question_bank/adapter.py)
如果你的应用是 HTTP 服务或有自己独立的 RAG 流程,再换成对应 adapter 即可。