Establish a safe foundation before implementing the geology RAG system
Some checks failed
secret-scan / scan (push) Has been cancelled

The project begins with architecture, data governance, reproducible evaluation, deployment boundaries, and secret-handling contracts so later code has measurable acceptance criteria.

Constraint: Real provider credentials, workspace identities, and restricted geological data must never enter Git

Rejected: Add placeholder runnable services in the design commit | would imply unverified implementation readiness

Confidence: high

Scope-risk: narrow

Reversibility: clean

Directive: Run make verify before every commit and update ADRs when architecture boundaries change

Tested: Secret scan, Markdown links, YAML parse, shell syntax, and staged diff validation

Not-tested: Application runtime is intentionally deferred to the implementation stage
This commit is contained in:
2026-07-12 14:42:11 +08:00
commit ec1acb36b5
55 changed files with 2882 additions and 0 deletions

View File

@@ -0,0 +1,774 @@
# 数据构建、向量入库与系统评测设计
| 项目 | 内容 |
|---|---|
| 文档版本 | v1.0-design |
| 更新日期 | 2026-07-11 |
| 默认切片 | 目标 512 Token最大 800重叠 64 |
| 默认向量 | `text-embedding-v4`1024 维Cosine |
| 正式验证集 | 建议 300 题(构建/开发/盲测各 100500 题为扩展目标 |
## 1. 数据治理是入库前置条件
### 1.1 语料来源优先级
| 优先级 | 来源 | 适合内容 | 使用边界 |
|---|---|---|---|
| A | 学校、实验室、合作单位自有报告 | 勘查报告、钻孔描述、化验表 | 取得书面授权,确认不涉密且允许发送到云模型 |
| A | 明确 CC BY、CC0 等许可的数据 | 地层、地球化学、矿床描述 | 保存许可、DOI、版本和引用方式 |
| A | USGS 自制报告与数据 | 英文矿床模型、调查数据 | 多数自制内容属美国公共领域,但第三方图件例外,逐项检查[版权说明](https://www.usgs.gov/faqs/are-usgs-reportspublications-copyrighted) |
| B | 中国地质调查局“地质云”等公开服务 | 区域地质、资源环境产品 | 在线可看不等于允许批量下载、衍生入库或再分发,检查每项条款 |
| B | 国家科学数据中心 | 各类专题数据集 | 每个数据集许可独立,不用平台整体印象代替许可核验 |
| C | 开放获取论文 | 术语、矿床成因、方法综述 | 仅明确开放许可时保存全文,否则只保存合法题录/摘要 |
| 禁止默认纳入 | 商业数据库、付费标准、教材、来源不明扫描件 | — | 必须取得明确授权,不能因“下载得到”而入库 |
“公开访问”“研究可用”“允许批量下载”“允许建立衍生知识库”“允许公开发布”是五种不同授权。矿产地、钻孔、精确坐标和大比例尺图件还可能涉及受限地理信息或商业秘密。状态不明确时默认拒绝,而不是“先入库以后再处理”。
### 1.2 文档硬门禁
任何一个条件不满足,文档保持 `QUARANTINED_LOCAL_REVIEW`。授权人员必须在隔离、无外网、无模型调用的环境中完成完整解析/OCR、规范化、结构化切分和字段级脱敏生成待出域 bundle完成本地复核后进入 `LOCAL_PARSED_PENDING_CLOUD_REVIEW`。只有审核人批准这一个确切 bundle 后才能进入 `CLOUD_APPROVED`;在此之前严禁向量化、百炼调用、对普通用户展示或激活检索:
- 来源 URL/交付人、权利人、许可或授权证据可追踪;
- 保密/涉密等级已确认;
- 允许本项目处理和建立索引;
- 允许把选定字段发送到外部模型服务;
- 页码、报告编号或版本足以支持引用;
- 原始 SHA-256 与审核时一致;
- 是否允许随论文、演示或仓库发布衍生样例已单独标记。
原始 `raw_text` 永久保留在许可允许的受控边界内;另行生成每个待发送块的 `cloud_text`。精确坐标、单位名称或项目编号需要脱敏时,只修改 `cloud_text`,不得破坏原件。审批对象必须绑定 `raw_sha256 + parser_profile_hash + ocr_profile_hash + normalization_profile_hash + chunk_profile_hash + cloud_policy_id + outbound_manifest_sha256`manifest 对所有可能发送的 `cloud_text`、允许附带的标题/章节元数据、顺序和各自哈希做承诺。上述任一输入、处理器、规则、文本或元数据变化,数据库约束自动撤销原审批并回到 `LOCAL_PARSED_PENDING_CLOUD_REVIEW`。Embedding、Rerank 和 LLM 只能读取与当前已批准 manifest 完全一致的 `cloud_text`Worker 在发请求前再次核对哈希。
### 1.3 文档清单格式
`documents.jsonl` 示例仅使用虚构数据:
```json
{
"doc_id": "doc_demo_001",
"title": "某地区铜矿地质特征与找矿标志(演示)",
"authors": ["示例作者"],
"publisher": "示例机构",
"report_no": "DEMO-2026-001",
"doi": null,
"source_url": "https://example.invalid/document",
"retrieved_at": "2026-07-11",
"published_at": "2026-01-01",
"source_version": "v1",
"raw_sha256": "<sha256>",
"license_id": "INTERNAL-DEMO",
"license_url": null,
"rights_holder": "示例机构",
"permission_evidence": "<private-evidence-reference>",
"redistribution_allowed": false,
"confidentiality": "internal",
"security_review": "passed",
"review_state": "CLOUD_APPROVED",
"cloud_policy_id": "cloud-policy-v1",
"cloud_text_sha256": "<sha256>",
"outbound_manifest_sha256": "<sha256>",
"parser_profile_hash": "<sha256>",
"ocr_profile_hash": "<sha256-or-disabled>",
"normalization_profile_hash": "<sha256>",
"chunk_profile_hash": "<sha256>",
"cloud_approved_at": "2026-07-11T00:00:00Z",
"cloud_approved_by": "<reviewer-id>",
"regions": ["示例地区"],
"mineral_types": ["铜"],
"deposit_types": ["斑岩型"],
"geological_ages": ["侏罗纪"],
"language": "zh-CN"
}
```
真实授权文件放在受控文档系统,不提交到 Git仓库只保存字段定义和脱敏后的许可类型。
## 2. 原始资料到切片的数据流水线
```text
原始文件只读归档
-> MIME/加密/大小检查
-> 隔离环境完整解析/OCR/页码章节恢复/规范化/切分/脱敏
-> 人工复核低置信页与最终待出域内容
-> 生成 outbound manifest
-> LOCAL_PARSED_PENDING_CLOUD_REVIEW
-> CLOUD_APPROVED / REJECTED绑定 manifest
-> 向量化
-> 数据库校验
-> 文档版本激活
```
每一层都保存:输入哈希、处理器名和版本、配置哈希、输出哈希、开始/结束时间、错误码。原始文件不得被规范化结果覆盖。
### 2.1 数字 PDF
建议用 PyMuPDF 提取页级文本块和坐标,用 pdfplumber 辅助表格:
- 保存 `page_no``bbox`、阅读顺序、字体/字号等可用信息;
- 恢复多栏阅读顺序,不能直接按底层对象顺序拼接;
- 跨页重复出现于多数页面的页眉/页脚作为噪声候选,但删除前保留规则和样例;
- 不删除章节标题、图表编号、地层代号、钻孔号、元素符号、坐标、正负号和单位;
- 页码同时保存 PDF 物理页和报告印刷页,回答引用统一展示可理解的页码;
- 解析失败页单独标记,不能因整份文档大部分成功就静默跳过。
### 2.2 扫描 PDF 与 OCR
推荐流程:
1. 页面以 300 DPI 渲染;
2. 自动旋转、倾斜矫正、去噪和对比度增强;
3. PaddleOCR/PP-Structure 输出文本、坐标和置信度;
4. 地质词典用于提出纠错候选,不直接覆盖原始 OCR
5. 同时保存 `raw_text``normalized_text`、置信度和修订人;
6. 数值密集页、低置信页和表格页进入人工复核队列。
金标准页建议目标:
| 指标 | 目标 |
|---|---:|
| OCR 字符错误率 CER | ≤ 3% |
| 地质专业术语准确率 | ≥ 95% |
| 数字、符号和单位准确率 | ≥ 98% |
| 入库切片可追溯页码 | 100% |
平均置信度低于 0.85、关键数值置信度低、阅读顺序异常或单位缺失时,不允许自动激活。
### 2.3 表格
表格至少保存四种表示:
1. 原始页截图或坐标引用;
2. 结构化 JSON
3. 可展示 Markdown
4. 面向检索的逐行语义文本。
虚构示例:
```json
{
"table_id": "table_demo_p34_02",
"title": "ZK1203 钻孔样品分析结果(演示)",
"page_no": 34,
"columns": ["样品号", "起始深度_m", "终止深度_m", "Cu_pct"],
"rows": [
{
"样品号": "H12",
"起始深度_m": 125.4,
"终止深度_m": 127.2,
"Cu_pct": 0.86
}
],
"unit_notes": "Cu 品位单位为 %",
"source_bbox": [88, 201, 512, 690]
}
```
对应检索文本:
> 在演示表“ZK1203 钻孔样品分析结果”中,样品 H12 位于 125.4127.2 m铜品位为 0.86%。
表格按 1020 行切片,每个切片重复表名、列名、单位和页码。跨页表、续表、合并单元格必须抽检。目标为行列对齐 ≥ 97%,数值和单位 ≥ 99%。
### 2.4 图件与地质图
纯文本模型可处理图名、图注、图例 OCR、邻近解释文字和人工复核摘要但不能因此宣称理解断层、矿体或蚀变带在整幅图中的几何关系。
第一版把以下问题放入挑战集:
- 某断层位于矿体哪一侧;
- 某地层与侵入体在图上的接触关系;
- 剖面图中的空间延伸;
- 只存在于图形、没有文字描述的测量值。
后续如引入多模态向量和重排,需要新增数据格式、模型 ADR 和独立指标。
## 3. 规范化、切分和去重
### 3.1 规范化规则
允许:
- Unicode NFKC、全角/半角统一;
- 连续空白压缩、OCR 断行和错误连字符修复;
- 可逆的单位别名映射;
- 页眉页脚规则化删除;
- 在单独字段中保存术语同义词。
禁止:
- 全部英文转小写而破坏元素符号;
- 从受控原件删除数字、坐标、钻孔号、地层代号;云处理所需脱敏只作用于单独的 `cloud_text`
- 无原文依据扩写缩写;
- 把不同年代或不同报告的结论合并成“统一事实”;
- 用 LLM 改写后的正文替代原文。
### 3.2 基线切分
| 参数 | 初始值 |
|---|---:|
| 目标长度 | 约 512 Token |
| 最小长度 | 120 Token |
| 最大长度 | 800 Token |
| 相邻重叠 | 64 Token |
| 章节边界 | 不跨一级/二级标题 |
| 表格 | 1020 行,重复表头和单位 |
优先级为“章节 > 自然段 > 句子 > 长度兜底”。定义、枚举、完整成矿阶段和结论尽量保持在同一切片。若没有准确的百炼 tokenizer使用保守估算并让切片远小于 8192 Token而不是依赖服务端截断。
```text
display_text = 权限允许时贴近原文,用于展示和引用
cloud_text = 经字段级策略批准、可发送到云端的文本
embedding_text = [允许发送的标题/地区/矿种/章节路径] + cloud_text
```
是否添加元数据前缀由消融实验决定,不能把经验判断写成既定收益。
### 3.3 去重和版本
- 精确重复:规范化文本 SHA-256 相同;
- 文档近重复MinHash/Jaccard 参考阈值 0.92,进入人工确认;
- 切片近重复:参考阈值 0.95,保留主切片和来源别名;
- 修订版、扫描版、OCR 版归入同一 `duplicate_family_id`
- 有冲突的历史结论不去重,标记 `supersedes`/`conflicts_with`
- 同一重复族必须进入同一数据划分,避免评测泄漏。
## 4. Ground-truth 验证集生成
### 4.1 规模与题型
| 阶段 | 文档 | 切片 | 问题 |
|---|---:|---:|---:|
| 管线冒烟 | 510 | 100500 | 2030 |
| 试运行 | 50 | 2,00010,000 | 100 |
| 正式毕设 | 200500 | 10,00050,000 | 300500 为扩展) |
推荐正式最少 300 题,构建/开发/盲测各 100有足够标注人力时扩展至 500 题。题型比例:
| 类型 | 比例 |
|---|---:|
| 直接事实、术语定义 | 20% |
| 数值、钻孔、表格 | 20% |
| 地层—构造—矿化关系 | 15% |
| 多证据、多跳推理 | 15% |
| 区域或矿床比较 | 10% |
| 找矿方法与工作流程 | 10% |
| 无答案、歧义或冲突 | 10% |
每题标注模态、矿种、地区、难度、单跳/多跳、是否可回答和证据组,保证结果可以分层分析。
### 4.2 人工 + 半自动流程
1. 先按文档族完成数据划分。
2. 在每个 split 内按来源、矿种、题型和模态分层抽样。
3. LLM 只根据给定切片生成候选问题、短答案、证据原句和页码。
4. 自动检查证据是否真实存在、数字/单位是否一致、页码是否有效。
5. 标注员 A 修改或拒绝候选。
6. 标注员 B 独立验证答案、证据和可回答性。
7. 分歧交由地质专业教师或第三名标注员裁决。
8. 将所有参评的 Dense、词法、Hybrid 和 Rerank 系统候选取 union 建池,记录系统版本与 pooling depth初始建议每系统 Top 20。
9. 人工给池中候选 03 级相关性;新系统若在计分深度内返回未判定候选,必须补标,不能默认当作 0。
10. 冻结测试集、源证据锚点、corpus manifest 和整个数据集哈希。
大模型不能同时成为唯一出题者、唯一裁判和最终评分者。若团队资源有限,至少保证正式测试集双人审核,且 LLM 评分抽取 20% 以上由人复核。
### 4.3 评测问题格式
```json
{
"qid": "q_test_0042",
"query": "演示钻孔 ZK1203 在 125.4 至 127.2 米的铜品位是多少?",
"answerable": true,
"reference_answer": "0.86%",
"answer_aliases": ["0.86 %"],
"numeric_tolerance": {"absolute": 0.001, "unit": "%"},
"question_type": "table_numeric",
"difficulty": "medium",
"modality": "table",
"gold_evidence_groups": [
{
"required": true,
"doc_id": "doc_demo_001",
"document_version_id": "docver_demo_001",
"page_no": 34,
"char_start": 1280,
"char_end": 1348,
"bbox": [88, 201, 512, 238],
"evidence_sha256": "<sha256>"
}
],
"split": "test",
"security_classification": "internal",
"redistribution_allowed": false,
"created_by": "llm_assisted",
"review_status": "double_reviewed"
}
```
Gold 主锚点必须是跨切分配置稳定的源位置,不是 `chunk_id`。每次实验按 `anchor_resolver_version``doc/version/page/char offsets/bbox/evidence hash` 映射到本次切片并生成 run-specific qrels跨边界或 OCR 变化的歧义映射进入人工复核。这样 256/512/800 Token 等消融才使用同一事实真值。`evidence_text` 只可存在于有权限的内部 benchmark公开版本只保留哈希、允许公开的短引文或完全脱敏锚点。
Pool 中所有等级 1/2/3 的人工 judgment 也必须保存候选的源位置锚点、证据哈希、等级和判定理由,不能只有 Gold 使用稳定锚点。等级 0 保存候选来源锚点与“不相关/实体冲突”等理由。重新切片时解析全部 anchored judgments无法可靠映射的候选重新建池和补标否则该 run 不计算 nDCG。
### 4.4 无答案与困难负例
无答案题应覆盖:
- 语料完全没有的矿种或地区;
- 将真实问题的地区、矿床或年份替换形成的错误前提;
- 同矿种不同矿区、同地区不同矿种;
- 相邻但不能回答问题的切片;
- 新旧资料冲突且问题未限定时间;
- 缺少关键条件的歧义问题。
判定“无答案”前至少用关键词、向量和人工三种方式确认没有支持证据。随机无关句不够难,不能代表真实拒答能力。
### 4.5 构建、开发与测试隔离
RAG 不训练基础模型,这里的 train 更准确地说是“构建/校准集”:
- corpus 按文档族做 60/20/20而不是按切片随机划分问题集建议构建/开发/盲测各 100 题;
- 相同 DOI、报告号、扫描版、修订版和近重复文档必须在同一 split
- 构建集用于开发解析器、词典、Prompt 和初始参数;
- 开发集用于选择维度、切片、Top K、重排数量和拒答阈值
- 测试问题和答案在调参期间不可查看;
- Gold 问答、参考答案、qrels 绝不能写进 RAG 知识库;
- 主指标始终在冻结的全量 `train + dev + test` 文档索引上检索,并把 `corpus_manifest_sha256` 写入 run只检索 test 文档子库仅作诊断,不能与主指标混报;
- 盲测问题、答案和标签由独立保管人或受控凭据管理,开发者在参数冻结前不可访问;
- 同一次论文结论只运行一次冻结盲测。若盲测结果被用于修改参数,该测试集立即退役并转为开发数据,最终结论必须使用新的未见盲测集;更换 `run_id` 不能恢复盲测有效性。
标注一致性目标:相关/不相关二分类 Cohen's kappa ≥ 0.75,证据定位 F1 ≥ 0.85。数值、冲突和多跳问题全部双人审核。
### 4.6 评测数据的安全边界
问题、参考答案和证据锚点可能复制或推断受限原文,因此评测项继承源文档的许可、保密级别和销毁策略:
- 内部完整 benchmark 使用独立存储和独立访问凭据,不进入 Git
- 公开 benchmark 只包含获得再分发许可或充分脱敏的内容;
- 通用知识库导入器以 allowlist 指定语料目录,自动测试保证 `eval/``runs/` 和私有标注目录永不被扫描入库;
- 导出论文附件前再次执行版权、坐标和商业秘密检查;
- 评测备份、保留和删除与源资料策略一致。
## 5. 向量化 API 与写库测试
### 5.1 基线配置
```yaml
model: text-embedding-v4
dimension: 1024
encoding_format: float
distance: cosine
synchronous_batch_items: 8
chunk_target_tokens: 512
chunk_max_tokens: 800
```
百炼官方说明 `text-embedding-v4` 支持 642048 维、单次最多 10 条、单条最多 8192 Token北京单请求总计最多 33000 Token并推荐 1024 维作为通用平衡点,见[Embedding 规格](https://help.aliyun.com/zh/model-studio/embedding)。同步接口适合首版OpenAI Batch 适合数十万切片以上的离线任务,可降低调用价格,但需要提交、轮询、下载和失败行重试,因此不进入最小运行链路。
生产表固定 `vector(1024)`。512/1536 维消融每个维度使用独立 schema/table/HNSW index或一次性独立评测数据库通过相同不可变证据锚点生成各自 qrels不同维度绝不共用同一普通 HNSW 索引。
### 5.2 三层验证数据
第一层是 API 契约样例,验证字段、下标和维度;第二层是 100 组语义三元组;第三层是正式 qrels 验证集。
语义三元组格式:
```json
{
"id": "triplet_demo_001",
"query": "斑岩铜矿常见哪些蚀变找矿标志?",
"positive": "斑岩铜矿常见钾化、绢英岩化和青磐岩化分带。",
"hard_negative": "矽卡岩矿床常见于侵入岩与碳酸盐岩接触带。",
"tags": ["synonym", "deposit_type", "hard_negative"]
}
```
三元组至少覆盖:
- 地质术语同义表达;
- 同矿种不同地区;
- 同地区不同矿种;
- 数值和单位差异;
- 地层代号、钻孔号、图幅号;
- 中文简称与全称;
- OCR 轻微错误;
- 标题前缀有无。
建议门槛:`sim(query, positive) > sim(query, hard_negative)` ≥ 90%;原文式查询目标切片 Top 1合理改写 Top 3。不要使用未校准的固定余弦分数判断所有问题。
### 5.3 API 契约断言
用已轮换的新 Key、从 Secret 注入后执行:
1. 空字符串在客户端拒绝。
2. 批次数量超过 10 在客户端拒绝。
3. 单条超长文本由切分器拒绝或重新切分,不依赖服务静默截断。
4. 批次估算总 Token 超过 33000 时客户端重新分批。
5. HTTP 200 且返回模型可识别。
6. `len(data) == len(input)`
7.`data[].index` 恢复原输入顺序。
8. 每个向量长度严格为 1024。
9. 每个元素为有限浮点数,无 NaN/Inf。
10. L2 norm 大于 0。
11. 同一文本重复请求的余弦相似度接近 1。
12. 保存 request ID、Token 和耗时,不保存鉴权头。
13. 401/403/404 不自动重试429/5xx 有上限退避。
### 5.4 入库算法
外部请求和数据库事务分离:
```text
解析并持久化 chunks(status=PENDING)
-> 选取未向量化批次并建立幂等键
-> 调用 Embedding事务外
-> 验证返回数量、下标、维度和数值
-> 短事务批量 UPDATE/UPSERT embedding
-> 提交检查点
-> 全部完成后执行文档级一致性校验
-> 单事务更新 documents.active_version_id 和 searchable chunks 投影
```
Embedding profile 与缓存键分离:
```text
embedding_profile_hash = SHA256(
provider + endpoint_region + endpoint_identity_hash + api_mode + requested_model
+ resolved_model_revision_or_cache_epoch
+ dimension + output_type + text_type + instruct_hash
+ normalization_version
)
embedding_cache_key = SHA256(
embedding_text_sha256 + embedding_profile_hash
)
chunk_embedding_assignment = UNIQUE(chunk_id, embedding_profile_hash)
```
相同文本可以安全复用向量缓存,但每个 chunk 都必须建立独立 assignment不能用缓存键替代任务/行唯一键。模型别名无法解析到稳定 revision 时,每次冻结实验显式设置 `cache_epoch`,避免别名漂移后复用旧向量。激活前再次核对当前 `embedding_text_sha256`、完整 profile 与 assignment任何一项变化都生成新索引版本。
`endpoint_identity_hash` 由规范化后的工作空间/部署端点身份计算,仅保存哈希而不在实验导出中暴露真实工作空间。这样同地域、同模型别名但不同 MaaS 工作空间的向量不会错误共用缓存。
### 5.5 数据库完整性查询(示意)
```sql
-- 1. 待激活且当前未删除版本中必须没有空向量
SELECT count(*) AS missing_vectors
FROM chunks
WHERE document_version_id = :version_id
AND approval_status = 'APPROVED'
AND deleted_at IS NULL
AND (
embedding IS NULL
OR index_status <> 'READY'
OR embedded_text_sha256 <> embedding_text_sha256
OR embedding_profile_hash <> :expected_profile_hash
);
-- 2. 所有向量维度必须一致
SELECT vector_dims(embedding) AS dims, count(*)
FROM chunks
WHERE document_version_id = :version_id
AND approval_status = 'APPROVED'
AND deleted_at IS NULL
GROUP BY vector_dims(embedding);
-- 3. 预期切片数、实际向量数应相等
SELECT
count(*) AS chunk_count,
count(embedding) AS vector_count
FROM chunks
WHERE document_version_id = :version_id
AND approval_status = 'APPROVED'
AND deleted_at IS NULL;
-- 4. Cosine 检索
SELECT id, document_version_id,
1 - (embedding <=> :query_vector) AS cosine_similarity
FROM chunks
WHERE searchable = true
AND deleted_at IS NULL
AND knowledge_base_id = ANY(:allowed_kb_ids)
AND access_scope_id = ANY(:allowed_scope_ids)
ORDER BY embedding <=> :query_vector
LIMIT 50;
```
`documents.active_version_id` 是唯一权威激活状态;`chunks.searchable` 只是为了 filtered HNSW 的事务性派生投影。激活事务先验证新版本全部 READY/哈希/profile 一致,再将同文档旧切片设为不可检索、新版本切片设为可检索,并更新 `documents.active_version_id` 后一次提交。约束触发器和 reconciliation 检查保证每份文档只有一个 active version且所有 searchable chunk 都属于该版本。权限过滤在候选生成时生效,而不是检索后才删除未授权结果。
### 5.6 写库验收清单
- `approved chunks == vector rows`
- `chunk_id` 唯一,重复任务行数不增长;
- 文本未变时不产生新模型费用;
- 文本改变时旧版本退出检索,新版本完整激活;
- `documents.active_version_id`、searchable 投影、当前文本哈希和 embedding profile 完全一致;
- 删除文档后立即无法检索,后台完成物理清理;
- 中断后从检查点恢复,不重复已提交批次;
- Docker 重启后数据库和文件仍存在;
- 备份恢复后行数、哈希和随机抽样向量一致;
- 每个向量可回溯模型、维度、文本哈希和请求 ID。
### 5.7 HNSW 质量测试
首个 schema 版本即建立 HNSW在专用评测事务中关闭 index/bitmap scan并使用完全相同的知识库和 access-scope 过滤得到 exact 真值。对至少 200 个查询比较 ANN Top 10 与 exact Top 10
```text
ANN Recall@10 = |ANN Top10 ∩ Exact Top10| / 10
```
Exact 基线在隔离连接中使用 `SET LOCAL enable_indexscan = off``SET LOCAL enable_bitmapscan = off`,查询仍保留相同的 `allowed_kb_ids/allowed_scope_ids`;不能拿无权限过滤的全库 exact 结果与过滤后的 ANN 比较。
建议目标 ≥ 0.98,同时记录索引构建时间、索引大小、查询 p50/p95、`ef_search`、iterative scan 和 over-fetch。数据很小时仍保留 exact 指标,不把 HNSW 的存在当作质量结论。
## 6. Rerank 与端到端验证
### 6.1 Rerank 契约
基线Dense Top 50 -> `qwen3-rerank` 保存 Top 10 供 MRR/nDCG@10 评测 -> 前 8 条进入生成上下文。
断言:
- 请求文档数和总 Token 未超限;
- 总 Token 按 `Query Tokens × Document 数量 + Document Tokens 总和` 计算,断言 Query 被按候选数重复计入;
- 候选在本地保留,响应 `index` 必须映射到正确候选;
- `results` 数量不超过 `top_n`
- 分数为有限数值且结果按降序;
- 记录是否发生客户端裁剪;
- 用同一 qrels 比较重排前后的 MRR/nDCG
- 初召回没有 Gold 时,错误归为 `RETRIEVAL_MISS`,不能归因给 Rerank。
### 6.2 生成验证
端到端结果保存:
```json
{
"qid": "q_test_0042",
"retrieved_chunk_ids": ["..."],
"vector_scores": [0.0],
"reranked_chunk_ids": ["..."],
"rerank_scores": [0.0],
"answer": "...",
"citations": [{"label": "S1", "chunk_id": "..."}],
"latency_ms": {"embedding": 0, "retrieve": 0, "rerank": 0, "generate": 0},
"usage": {"embedding_tokens": 0, "rerank_tokens": 0, "llm_input_tokens": 0, "llm_output_tokens": 0}
}
```
模型生成的引用编号必须由后端映射到真实切片。引用存在不等于支持该声明,还需人工或经过验证的 claim-evidence 评审。
## 7. 指标与建议目标
这些是项目目标而不是预先宣称的结果。试运行后、正式盲测前冻结。
| 层级 | 指标 | 建议目标 |
|---|---|---:|
| 数据 | searchable 切片可追溯当前文档版本/页码 | 100% |
| OCR 金标准页 | CER | ≤ 3% |
| 表格 | 数值及单位准确率 | ≥ 99% |
| 初召回 | Hit@20 | ≥ 0.85 |
| 初召回 | Hit@50 | ≥ 0.93 |
| 多跳初召回 | CompleteHit@20 | ≥ 0.70 |
| 多跳初召回 | EvidenceGroupRecall@20 | ≥ 0.85 |
| 初召回 | MRR@10 | ≥ 0.60 |
| 初召回 | nDCG@10 | ≥ 0.65 |
| 重排 | Hit@5 | ≥ 0.80 |
| 重排 | MRR@10 | ≥ 0.70 |
| 重排 | nDCG@10 | ≥ 0.75 |
| 重排收益 | nDCG@10 绝对提升 | ≥ 0.05 |
| 生成 | 专家答案通过率03 级中 ≥ 2 | ≥ 0.80 |
| 生成 | 事实忠实度 | ≥ 0.95 |
| 引用 | Citation Precision | ≥ 0.90 |
| 引用 | Citation Recall | ≥ 0.85 |
| 数值题 | 数值及单位正确率 | ≥ 0.95 |
| 无答案 | 拒答 F1 | ≥ 0.85 |
| 风险 | 无证据事实占比 | ≤ 5% |
| 端到端 | 完整成功率 | ≥ 0.75 |
| 多跳困难集 | 完整成功率 | ≥ 0.60 |
| 系统 | 稳态错误率 | < 1% |
| 系统 | 端到端 p95 | 建议 ≤ 10 秒 |
qrels 相关等级:
| 等级 | 定义 |
|---:|---|
| 3 | 直接回答问题或属于必要 Gold 证据组 |
| 2 | 明确支持关键子结论,但单独不足以完整回答 |
| 1 | 主题相关或提供背景,不能支持关键答案 |
| 0 | 无关、实体混淆或与问题条件不匹配 |
指标定义与分母:
- `Hit@K`Top K 至少包含一个等级 ≥ 2 的证据;
- `CompleteHit@K`:多跳问题的所有必要证据组都在 Top K
- `EvidenceGroupRecall@K`Top K 覆盖的必要证据组 / 全部必要证据组;
- `MRR@K`:第一个等级 ≥ 2 结果排名倒数的均值;
- `nDCG@K`:使用完整 03 等级衡量排序;
- 忠实度:可被证据支持的原子事实声明 / 全部事实声明;
- Citation Precision正确支持相关声明的引用 / 全部引用;
- Citation Recall带正确引用的必要事实 / 全部必要事实;
- 完整成功:答案正确、必要证据齐、引用正确且无关键无依据声明。
生成评分按以下冻结规约执行:
1. **专家答案等级**3 表示结论完整正确且无实质错误2 表示核心结论正确、仅有非关键遗漏1 表示只答对局部或有重大遗漏0 表示错误、矛盾、捏造,或应回答却拒答。`answerable=true` 的答案通过率以等级 ≥ 2 二值化;数值题还必须落在单位换算后的容差内。
2. **原子事实**:把答案拆为单一主语—谓语—宾语、单一数值+单位+对象、或单一时间/空间限定命题;“且/以及/分别”连接的可独立验证命题拆开,语义重复命题只计一次。纯格式、寒暄和引用标签不是事实。
3. **证据边界**:忠实度和 Citation Precision 的“证据”只指本次实际送入生成器的上下文切片;任意 Gold 原文不能事后补救未检索到的证据。专家正确性对照 reference answer 与稳定 Gold 锚点。一个声明的多个引用可按其并集判断是否充分蕴含。
4. **空分母**:正确拒答且没有事实声明时,忠实度和 Citation Precision 记为 N/A 并从宏平均分母排除;可回答题无引用时 Citation Recall 记 0Citation Precision 为 N/A有事实但无支持时这些事实全部计为无证据。无答案题交由拒答指标不用空答案抬高生成分数。
5. **一致关系**`无证据事实占比 = 1 - 事实忠实度`,两者使用同一原子事实分母,所以 ≥ 0.95 与 ≤ 5% 是同一验收约束,不重复计为两个独立成功条件。
6. **裁决**:冻结测试集的答案等级、原子事实边界和 claim-evidence 关系由两名评审独立标注;分歧由第三名地质专业评审裁决。报告原始一致率与 Cohen's kappa并保存脱敏后的裁决理由、rubric 版本和 reviewer blind 状态。
Hit/MRR/nDCG/CompleteHit 只在 `answerable=true` 上计算。无答案题单独计算拒答 Precision/Recall/F1正类定义为 `should_refuse=true`;不能把无答案题当作所有检索结果均错误后混进 MRR 分母。按问题类型、来源、模态、难度分别报告,并对查询做 1000 次 bootstrap 给出 95% 置信区间。RAGAS 可作为辅助自动评测,不能代替专家金标准;其方法将 RAG 拆为检索上下文、忠实使用和回答质量多个维度,见[RAGAs 论文](https://aclanthology.org/2024.eacl-demo.16/)。
Pooling 使用所有参评系统结果的 union。保存参与系统、模型/切片配置和 depth新系统若在指标计算深度内产生未判定候选先补标再计分。`unjudged` 不得默认等于 0否则指标会系统性偏向参与建池的系统。
## 8. 消融实验矩阵
为了避免组合爆炸,先在开发集逐项改变一个变量,再对最佳配置运行冻结测试集:
1. Closed-book LLM 与完整 RAG
2. 词法、Dense、Hybrid
3. 512、1024、1536 维;
4. 256、512、800 Token 切片;
5. 0、64、128 Token 重叠;
6. 原文与“标题/地区/矿种/章节”前缀;
7. OpenAI 兼容 Embedding 与原生 `query/document` 模式;
8. Top 20、50 候选重排;
9. 无 Rerank 与 `qwen3-rerank`
10. 上下文 Top 3、5、8
11. 表格 Markdown 与逐行语义化;
12. Query 改写开关;
13. `enable_thinking` 开关(只用于复杂题子集);
14. 数字 PDF 主集与 OCR/表格挑战集。
每个实验同时报告质量、p50/p95、Token、调用次数和每 1000 查询估算成本。不得只报告更高准确率而忽略费用和延迟。
## 9. 错误分类
| 代码 | 含义 |
|---|---|
| `SOURCE_LICENSE` | 许可或来源问题 |
| `CORPUS_MISSING` | 语料不存在答案 |
| `PARSE_LAYOUT` | 阅读顺序或版面错误 |
| `OCR_TERM` / `OCR_NUMBER` | 术语或数值 OCR 错误 |
| `TABLE_ALIGNMENT` | 表格行列错位 |
| `FIGURE_UNDERSTANDING` | 依赖图件空间关系 |
| `CHUNK_BOUNDARY` | 必要信息跨切片 |
| `DUPLICATE_CROWDING` | 重复切片占满候选 |
| `ENTITY_COLLISION` | 同名矿床/地区/地层混淆 |
| `RETRIEVAL_MISS` | 初召回未找到证据 |
| `RERANK_WRONG_ORDER` | 已召回但重排顺序错误 |
| `RERANK_TRUNCATION` | 候选被截断 |
| `GEN_HALLUCINATION` | 生成无依据内容 |
| `GEN_OMISSION` | 证据存在但答案遗漏 |
| `NUMERIC_UNIT` | 数值或单位错误 |
| `CONFLICT_VERSION` | 忽略资料版本冲突 |
| `CITATION_MISMATCH` | 引用不支持声明 |
| `REFUSAL_OVER/UNDER` | 过度拒答或应拒未拒 |
| `SYSTEM_TIMEOUT/RATE_LIMIT` | 外部超时或限流 |
错误记录包含 qid、运行配置、期望、实际、召回/重排列表、回答、引用、根因、严重性和修复建议。
## 10. 可复现实验目录与配置
```text
data/
raw_manifest.jsonl
documents.jsonl
pages.jsonl
chunks.jsonl
tables.jsonl
eval/
questions.jsonl
anchored_judgments.jsonl
judgment_set_manifest.json
evidence_groups.jsonl
runs/<run_id>/
config.yaml
qrels.tsv
anchor_resolution.jsonl
predictions.jsonl
metrics.json
errors.jsonl
```
真实受限数据位于 Git 之外;上面是运行时或受控数据区的逻辑结构,不代表可以提交原文。
`anchored_judgments.jsonl` 是全部 03 级 judgment 的权威快照;每行至少包含 schema 版本、qid、grade、理由、doc/version/page/char/bbox 锚点、evidence hash、标注人盲化 ID 和裁决状态。`judgment_set_manifest.json` 冻结 schema 版本、judgment set 版本、条目数、文件 SHA-256、问题集 SHA-256、创建时间和审批人。文件只追加新版本不原地覆盖。
每个 run 的 `qrels.tsv` 由同一版本的全部不可变、已判定源锚点(不仅是 Gold解析为切片级视图不是人工 judgment 的唯一存储:
```text
qid 0 chunk_id relevance_grade
q001 0 chk_a 3
q001 0 chk_b 1
```
补标或裁决变化会产生新的 judgment set 版本和哈希;同一比较表中的所有系统必须用该新版本重新生成 qrels 并重新计分。不同切片配置可以得到不同 chunk ID但必须来自同一 anchored judgment snapshot、resolver 版本和 corpus manifest禁止让不同系统偷偷使用不同真值集合。
`config.yaml` 至少冻结:
```yaml
dataset_version: geo-rag-eval-v1.0
dataset_sha256: <sha256>
anchored_judgments_schema_version: 1
judgment_set_version: geo-rag-judgments-v1.0
judgment_set_sha256: <sha256>
corpus_manifest_sha256: <sha256>
git_commit: <commit>
docker_image_digest: <digest>
database_schema_version: <revision>
parser_version: <version>
parser_config_hash: <sha256>
anchor_resolver_version: <version>
anchor_resolution_sha256: <sha256>
qrels_sha256: <sha256>
ocr_model: <version-or-disabled>
chunk_size: 512
chunk_overlap: 64
chunk_profile_hash: <sha256>
embedding_model: text-embedding-v4
embedding_dimension: 1024
embedding_index_namespace: embedding_1024_v1
embedding_api_mode: openai-compatible
embedding_profile_hash: <sha256>
embedding_endpoint_region: cn-beijing
embedding_endpoint_identity_hash: <sha256>
embedding_requested_revision: <provider-model-alias>
embedding_resolved_revision: <provider-revision-or-unknown>
embedding_cache_epoch: <controlled-epoch>
vector_distance: cosine
retrieval_mode: dense
retrieval_top_k: 50
hnsw_ef_search: 100
hnsw_iterative_scan: strict_order
ann_overfetch_k: 200
lexical_analyzer_version: <disabled-or-version>
hybrid_fusion: disabled
hybrid_dense_weight: null
hybrid_lexical_weight: null
rrf_k: null
rerank_model: qwen3-rerank
rerank_api_mode: bailian-compatible-rerank
rerank_endpoint_identity_hash: <sha256>
rerank_resolved_revision: <provider-revision-or-unknown>
rerank_cache_epoch: <controlled-epoch>
rerank_top_n: 10
context_top_n: 8
max_context_tokens: 10000
rerank_instruct_hash: <sha256>
generator_model: deepseek-v4-flash
generator_api_mode: openai-compatible
generator_endpoint_identity_hash: <sha256>
generator_resolved_revision: <provider-revision-or-unknown>
generator_cache_epoch: <controlled-epoch>
provider_capability_fingerprint: <sha256>
prompt_hash: <sha256>
enable_thinking: false
enable_search: false
temperature: 0.0
top_p: 1.0
max_output_tokens: 1200
random_seed: 42
```
若供应商响应不暴露 resolved revision字段必须写 `unknown`,同时保存脱敏 endpoint identity、受控 cache epoch、能力探测指纹和响应中的 model 字段;观察到别名行为漂移时提升 epoch禁止继续复用旧缓存。只有配置、代码、数据集/判断集哈希和镜像均冻结,论文中“同一实验”才具有可复现含义。