技术笔记

RAG 检索工程:版本、分块与引用闭环

检索命中相似文本并不代表回答可核验。

2026年5月20日阅读约 2 分钟
RAG分块引用

问题与数据契约

检索命中相似文本并不代表回答可核验。发布正文、索引正文和用户打开的文章必须属于同一版本。本文讨论单站知识库,公开文章进入检索,私有账户数据不进入公共索引。

记录至少包含 slug、collection、title、content、contentHash、sectionHeading、startLine 和 endLine。正文存储为完整 LONGTEXT,标签存储为 JSON 数组。内容哈希基于原始正文计算;不要对检索文本做归一化后拿它替代发布版本的哈希。

分块与排序边界

先解析 Markdown 语法树,再以 H2/H3 建立父子层次。段落、列表和代码块是原子节点,不能用截取前 1500 字的方法控制长度。特别长的代码块应单独保存,并在上下文装配时明确决定是否纳入,而不是静默截断。

词法检索适合型号、标识符和精确术语;向量检索适合改写问题。两套排名可通过 RRF 合并:每个文档的分数等于各排名中的 1/(k+rank) 之和。它不要求不同检索器的原始分数具有相同尺度,但 k 和候选数量仍需用实际查询集验证。

可运行的排名融合示例

以下代码只演示候选融合,不执行 Embedding,也不代表完整 RAG 系统。

import assert from "node:assert/strict";
function rrf(rankings, k = 60) {
  const scores = new Map();
  for (const ranking of rankings) {
    [...new Set(ranking)].forEach((id, i) =>
      scores.set(id, (scores.get(id) || 0) + 1 / (k + i + 1)));
  }
  return [...scores].sort((a, b) => b[1] - a[1]);
}
assert.equal(rrf([["a", "b"], ["b", "c"]])[0][0], "b");
assert.deepEqual(rrf([]), []);

引用与失效处理

引用必须携带版本和段落锚点。页面改版后,旧回答中的引用可能指向新版内容,因此应展示引用时的版本摘要;需要长期证据时,应另存不可变修订。不存在的 slug 返回 404,撤稿内容必须同时从列表、索引和 sitemap 移除。

上线验收应覆盖:中文改写、精确代码术语、无相关资料问题、撤稿、超长代码块和重复标题。记录 Recall@k、引用有效率及拒答样本,不能只展示几个命中截图。本项目当前提供词法基线与结构化分块,Embedding 和重排服务需要独立评测后接入。

原始资料

感谢阅读本文

如果你觉得内容有帮助,欢迎点赞支持或分享给同行开发者。

1565 次阅读123 人赞过