原创教学案例 · 最近核验:2026-10。文档、排序和预算都是小型合成示例,不代表实际服务表现。

上一页里,报名从周五改到周三,助手却可能还在查旧文档。先不接 LLM:我们跟着一段文字,看它怎样进入索引,怎样被找到,又怎样送进模型的上下文。

每个片段都要能找到原文

一个 chunk(文本片段)至少要能追溯到哪份文档、哪次修改、哪个位置。否则删改了一份原文,系统都不知道要清掉哪些旧片段。

字段 例子 用来解决什么
document_id / revision rules / 2 判断是否仍是当前版本
chunk_id rules-v2-deadline 去重、引用、回归定位
section / 原文位置 报名 / 第 2 段 回到原文核查
visibility members 服务端按身份限制候选(candidate)
生效时间(effective time) / 删除状态 发布后生效 区分未来规则、现行规则与已删除内容

示例用整数表示版本,用用户组表示访问范围。实际系统还要处理生效时间、权限变更,以及授权服务故障。

权限检查由服务端执行,要在资料进入重排器、LLM、响应和普通日志之前完成。给 prompt 加一句“不要泄露”不能代替这一步。共享缓存也要区分授权范围和版本,否则两个用户问同一句话,可能拿到本不该共享的结果。

切块时,别把规则和例外拆散

“报名截止到周三”单独切出来很好找;但若下一句是“仅适用于线下活动”,切掉它就改变了含义。按段落或标题边界切,再在必要处保留相邻限定条件,通常比固定字符数粗切更容易检查。

Haystack 的 DocumentSplitter会保留来源信息,支持按长度与重叠切分。框架做的是机械切分;哪些条件必须放在一起,仍要用你的文档验证。

先在同一组问题上比较两种切法:证据是否完整、重复块占了多少上下文、总 token 是否增加。不要只数“找到相关块”——十块都来自同一个段落,往往不如两块分别覆盖规则和例外。

先限制可见集合,再取 top-k

假设关键词分数前三名是:私有预算 9 分、旧规则 8 分、新规则 7 分。先取 top-2 再过滤,会得到空集合;这不等于没有可用答案。

教学实现先按当前版本和权限过滤,再排序。真实索引是否支持这种 pre-filter,需要按实现核验;如果只能后过滤,就要测过滤后的召回损失和补取成本。不能为了凑齐 k 条而放宽权限。 返回前还应按当前授权状态再检查一次,防止请求期间撤权或删除。

关键词和向量结果,怎么合并?

关键词分数 12 和向量相似度 0.8,不能直接说前者更好。可以先用 RRF(reciprocal rank fusion,倒数排名融合)做一个可解释的基线(baseline):

$$ \operatorname{RRF}(d)=\sum_{r:\,d\in L_r}\frac{1}{c+\operatorname{rank}_{L_r}(d)}. $$

排名从 1 开始,c > 0。下面故意取 c=10,方便算;不是推荐的生产参数。它不需要两路原始分数同尺度,但候选窗口和 c 仍要验证。Elasticsearch 的 RRF 定义

片段 关键词排名 向量排名 RRF 分数
A 1 2 1/11 + 1/12 ≈ 0.1742
B 2 未出现 1/12 ≈ 0.0833
C 未出现 1 1/11 ≈ 0.0909

结果为 A、C、B。A 在两路里都排得靠前,所以融合后排第 1;C 只在向量结果中出现,但位置比 B 在关键词结果中更高。

同一路重复返回 A 时,先去重,不能重复加分。两路都找到 A 也不等于它一定正确,RRF 只是合并排序的方法,相关性仍要评估。

上下文装不下时,怎么取舍?

假设还可以放 10 个 token,3 个片段按优先级排列,长度分别是 6、6、4。先试一个简单做法:按顺序选,放不下就跳过,继续看下一段。

处理到哪段 本段长度 剩余空间 这一步怎么做
第 1 段 6 10 → 4 保留
第 2 段 6 4 → 4 放不下,跳过
第 3 段 4 4 → 0 保留

最后保留第 1、3 段。这个贪心方法容易实现,但不保证选得最好:两段可能重复,第 2 段也可能恰好包含关键例外。因此不仅要检查是否超长,还要看回答所需的信息有没有保留下来。

实际预算应减去系统指令、问题、引用包装和输出预留,用生成模型的 tokenizer计算。示例用空白分词计数,只为了看到预算如何变化;它既不适合中文 token 计费,也不能保证某个模型的上下文不超限。

跑一遍,再改坏一个条件

在仓库根目录运行:

python3 practice/rag/code/evidence_pipeline.py
python3 -m unittest discover -s site/tests -p 'test_practice_projects.py'

完整代码会输出:当前可见结果是 rules-v2;两路结果融合后为 A, C, B;长度限制下保留第 1、3 段;不属于本次提供资料的引用 rules-v1 被标为无效。

试着从 current_revisions 中移除 rules:无法确认哪个版本生效,就不返回这份文档。再把查询改成没有共同词的同义问法,看看关键词检索(retrieval)会漏掉什么。以后加入 embedding 检索时,可以用这些问题检查它是否真的补上了遗漏。

代码没有接 ANN、授权服务或 LLM,也没有实现自动同步;它检验的是过滤、融合和预算约定。接服务时,再补超时、索引滞后、删改事件与返回前复核。

下一篇:答案有引用,就算答对了吗?

本页术语 4
候选 candidate
召回阶段捞出来、等待精排挑选的内容
生效时间 effective time
某一版本的数据或属性从什么时候开始对外有效
基线 baseline
用来比较新方法的参考方法或配置
检索 retrieval
从外部语料里找出相关内容