# RAG 评测实战（二）：可复现切块实验

这是刻意构造的合成故障样例，不是策略性能榜单。文档、查询与原文证据由作者设计；脚本真正执行切块、词法检索、排序、上下文选择和证据评分，不读取预设排名。无 Embedding、BM25、重排器、生成模型、网络请求、密钥或第三方依赖。

## 运行

把 `evaluate.mjs` 与 `fixture.json` 下载至同一目录，先审阅代码，然后用 Node.js 18+：

```sh
node evaluate.mjs fixture.json
node evaluate.mjs fixture.json > results.json
```

若要运行公开回归测试，再下载 `test.mjs` 和已发布的 `results.json`，四个文件置于同一目录：

```sh
node test.mjs
```

仓库内：`node scripts/tests/test-rag-chunking.mjs`。它调用同一公开测试，并校验现有结果与重新执行结果在 JSON 和 CLI 字节层面一致。不要先覆盖已有 `results.json` 再用它验证旧快照。`fixture.json` 内参数可改；改动后须主动审核和更新结果及失败断言。

## 固定数据和公平比较的边界

参数：固定块长 120、重叠 60、Top-k=2、字符预算 240。全部文档为英文 ASCII，偏移按 JavaScript 字符串索引计数；因此本 fixture 中字符数、UTF-16 code unit 数和 ASCII 字节数相同。范围均为半开区间 `[start,end)`，不含 end；版本分别保存，文档 ID 不随切块变化。

5 条文档版本记录、4 个稳定文档 ID：

| docId | version | 原文字符数 | 用途 |
| --- | --- | ---: | --- |
| retry | v2 | 243 | 规则含条件，证据在 `[100,178)`，整篇只有一个段落 |
| retry-guide | v2 | 50 | 相关导航，不提供重试条件 |
| logs | v2 | 213 | 保留期证据 `[65,90)`；导出证据 `[187,213)` |
| retention | v1 | 240 | 已过期的 30 天规则，两段重复相关文字 |
| retention | v2 | 24 | 当前 7 天规则；另一种等价支持来源 |

填充点号与重复措辞是为稳定制造边界和排序故障，不模拟真实文本分布。原始英文提问是短关键词查询，不代表自然语言问题集：

| 问题 ID | 查询 | 参考事实 |
| --- | --- | --- |
| boundary-condition | R-17 wait seconds | 30 秒重试且仅限幂等请求，完整规则算一个事实 |
| two-facts | logs retention export format | 7 天保留期、JSON 导出，共两个事实 |
| current-version | current logs retention policy | 当前 7 天保留期，一个事实 |
| no-evidence | maximum extended archive duration | 无证据，零个参考事实 |

保留期事实有两个备选支持组：`logs/v2 [65,90)` 或 `retention/v2 [0,24)`，命中任意组即可。后一种表达没有命中本组查询词，不保证所有等价事实都能被简单词法检索找到。

## 完整算法约定

- `fixed`：从 0 开始按 120 字符非重叠切块，尾块可短于 120
- `overlap`：120 字符窗口、60 字符步长；窗口首次触及文档末尾即停止，不再生成完全冗余尾窗
- `paragraph`：按空行分段，不包含分隔空行，保留原文 offset；不设最大段长，也不切分过长段落
- 每个策略分别执行版本过滤关/开；打开时，在检索前只保留 `currentVersions` 对应版本。过滤是独立开关，不能把其收益归因于切块
- 小写化后，用 `[a-z0-9]+(?:-[a-z0-9]+)*` 抽取唯一词项。score = 查询唯一词项与块唯一词项的交集大小。无词干化、停用词表、词频加权、IDF或语义匹配；score=0 的块不入候选
- 按 score 降序；同分按 docId、version 的 JavaScript 字符串字典序，再按 start/end 升序排序。ID会影响同分结果，结果对文档输入顺序不敏感
- Top-k：从同一候选列表取前2个，完全不限制字符数
- 固定预算：从同一完整候选列表依序扫描，若整块可放入剩余240字符预算则选中，否则跳过并继续；不截断、不做最优装箱、不设额外k限制、不去重
- 成本为所选块正文长度之和，重叠文字重复收费；不包含分隔符、元数据、提示词。字符预算不是 tokenizer token 预算，也不是价格或延迟测量
- 一条证据 span 被覆盖，当且仅当所选块中同 docId、同 version 的原文区间并集完整包含它。相接的两个块可以联合覆盖，缺一个字符也失败，旧版本不能补齐当前证据
- 一个事实包含若干备选支持组（OR），每组可以有多条必需 span（AND）。每个事实最多计1次；多份重复、重叠或等价证据不会扩大分子或分母
- 单题 Evidence Recall = 覆盖事实数 / 必需事实数。空分母记 `null`，不纳入3道可回答题宏平均；无答案题不因为检索返回零项而自动算正确拒答
- `uniqueSourceChars` 是按 docId/version 合并区间后的原文字符数；`duplicateSourceChars` 是输入字符总数减去它。二者只衡量原文位置重复，不检测不同文档的语义重复

输出包含12组实验，每组有完整正分候选 `ranked`（含原文、范围、分数、匹配词）、`selected`、跳过原因、实际上下文字符数、重复字符数、命中的事实 ID、逐题召回和汇总。不存在的/未知版本证据、越界标注、重复文档版本或题目/事实 ID、缺失字段、非法参数会报错。

## 实际结果

三道可回答题依次为边界条件 / 两个事实 / 当前版本。无答案题每组均为 `null`。

| 策略 | 版本过滤 | 选择方式 | 边界 | 两事实 | 当前版本 | 宏平均 |
| --- | --- | --- | ---: | ---: | ---: | ---: |
| fixed | 关 | Top-2 | 0 | 1 | 0 | 1/3 |
| fixed | 关 | 240字符 | 0 | 1 | 0 | 1/3 |
| fixed | 开 | Top-2 | 0 | 1 | 1 | 2/3 |
| fixed | 开 | 240字符 | 0 | 1 | 1 | 2/3 |
| overlap | 关 | Top-2 | 1 | 1/2 | 0 | 1/2 |
| overlap | 关 | 240字符 | 1 | 1/2 | 0 | 1/2 |
| overlap | 开 | Top-2 | 1 | 1/2 | 1 | 5/6 |
| overlap | 开 | 240字符 | 1 | 1/2 | 1 | 5/6 |
| paragraph | 关 | Top-2 | 1 | 1 | 0 | 2/3 |
| paragraph | 关 | 240字符 | 0 | 1 | 0 | 1/3 |
| paragraph | 开 | Top-2 | 1 | 1 | 1 | 1 |
| paragraph | 开 | 240字符 | 0 | 1 | 1 | 2/3 |

### 从逐题记录解释

1. **切断条件**：固定切块选了导航50字符和 `retry [0,120)` 120字符，共170；包含规则开头，但缺 `[120,178)`。后续块的词项没有命中查询，得分为0，未进入正分候选；这里是检索阶段损失，不能归因于预算。重叠策略选 `retry [60,180)` 和导航，共170，覆盖完整规则。评分看完整条件，不把“看见R-17”当作命中。
2. **重叠挤占预算**：开启版本过滤时，两事实题的 fixed 选 `logs [0,120)` 和 `[120,213)`，花213，覆盖2/2；overlap 选 `[0,120)` 和 `[60,180)`，花240但有60个重复原文字符，导出证据 `[187,213)` 未进入，只有1/2。这里重叠有成本但无新增事实，未实现 MMR 或去重救济。
3. **旧版相关但不正确**：current-version 的旧版文本匹配4个查询词，而当前 logs 块匹配2个。关过滤时 fixed/overlap 的前2块来自 retention/v1，240字符全部耗尽，召回0；paragraph的两个旧版段落共238字符，也为0。开过滤后这题三策略都为1。旧版即使有出处也不能满足当前版本证据。
4. **相同k不是相同成本**：paragraph 的 boundary-condition Top-2 选243字符规则段落和50字符导航，合计293，召回1；240字符预算下，过长段落整个跳过，只留下导航50，召回0。没有把段落法偷偷改成“最多120字符的段落法”。
5. **无答案不等于无候选**：no-evidence 可能因 `archive` 匹配检索出正文，但没有最大扩展时长证据；仍为null。不调用模型，因此不报告拒答率。

## 测试和局限

`test.mjs` 覆盖半开边界、相接/重叠/缺口、缺失证据、错误文档/版本、混版本拼接、重复不增分、备选组OR/组内AND、空分母、切块偏移、稳定同分排序、预算刚好用满/跳过后继续、参数校验、坏fixture、文档顺序置换、重复执行、失败案例断言、结果快照、CLI行为。

本实验刻意挑选失败条件，同一fixture同时用于演示与回归，没有留出集、统计显著性或真实语料代表性。它不衡量自然语言正确性、答案支持度、生成时条件遗漏、上下文顺序效果、模型实际拼接能力、真实token成本、延迟、权限、注入安全或线上质量。版本过滤依赖给定元数据正确；简单区间覆盖不是语义理解，证据标注仍依赖人工审定。三个策略并不都具有相同块长分布，字符预算更接近输入长度控制，但不消除检索器和手工数据的偏差。不能根据此表推荐任何策略用于生产。
