RAG 从数据源契约开始
RAG 的原始研究把参数化模型与可检索的非参数知识结合,以改善知识密集型任务和来源更新问题,但 论文中的受控知识库不等于企业数据天然可用。接入前确认内容 Owner、权威范围、访问协议、更新频率、许可、敏感级别、删除要求和服务等级。一个 Wiki 页面可能只是讨论稿,一份 PDF 可能只对特定产品版本有效;若不在源契约中表达,检索分数会把它们伪装成同等事实。
每个源对象需要稳定 sourceId、version 或内容哈希、lastModified、ACL 与分类标签。URL 不是稳定主键,标题也不是版本。内容移动、重命名和重发必须能识别“同一对象的新版本”与“新对象”。
采集与解析要保留文档结构
解析目标不是提取最多字符,而是保留能决定语义和引用的位置结构:标题层级、段落、列表、表格行列、代码文件/行号、页码、图片说明与脚注。解析器输出同时携带质量信号;无法可靠解析的文档进入隔离队列,不应静默生成空或乱码 Chunk。
不同格式需要不同解析策略
HTML、Markdown 与 Office
HTML 先识别正文与模板区域,删除导航、页眉和重复 Cookie 文案;Markdown 保留标题路径和代码围栏;Office 文档需处理批注、页眉和嵌套表格。直接转纯文本会丢失哪些单元格属于哪个列头。
PDF 与扫描件
PDF 是排版容器,不保证阅读顺序。区分可提取文本、布局解析与 OCR;保存页码、区域、旋转、OCR 置信和跨页表格关系。双栏文字若按坐标错误交错,后续 Embedding 再强也只会检索乱码。
表格与代码
表格至少把列头与行绑定,必要时同时保留结构化 JSON 和可读文本;代码按符号、文件和调用关系切分,并保留仓库版本。按固定字符切开函数签名和实现,会让查询命中却无法回答。
| 信号 | 检测 | 处置 |
|---|---|---|
| 空白/乱码 | 可打印字符比例、语言异常 | 重试替代解析器或隔离 |
| 顺序错误 | 标题层级、阅读顺序抽样 | 切换布局模型并保留页区域 |
| 重复模板 | 跨页/跨文档高频块 | 去噪但保留规则与版本 |
| 表格损坏 | 列数漂移、表头缺失 | 结构化专用路径,不入普通段落索引 |
Chunking 由查询和答案粒度决定
Chunk 是检索与引用单位,不是存储切片。先分析真实问题需要返回一句定义、一段步骤、整张表还是跨章节比较,再选择边界。固定长度容易实现但可能切断语义;递归字符切分保护段落;结构/语义切分更贴合内容,却依赖解析质量和模型。
Overlap 能缓解边界断裂,也会制造重复召回、Token 浪费和相同证据被多次计数。不要沿用“10% 或 20%”经验值作为事实,应在目标问题集上比较 Recall、重复率和答案质量。Microsoft 的 Azure AI Search 文档也把固定、句子/结构化切分列为不同策略,而非唯一最佳参数。
每个 Chunk 保存父文档、标题路径、前后邻接和字符/页码范围。Parent/Child 检索可以用小 Chunk 召回,再展开更完整父区域;摘要索引用于发现,最终回答仍引用原文范围。
索引前补充可过滤元数据
来源、作者、产品、版本、地域、语言、内容类型、有效期、租户、ACL 和数据分类应成为一等字段。自动抽取的实体和关键词标记提取器版本与置信,不能覆盖源系统字段。
type Chunk = {
chunkId: string; sourceId: string; sourceVersion: string
text: string; headingPath: string[]; locator: { page?: number; start: number; end: number }
tenantId: string; aclPrincipals: string[]; classification: string
parserVersion: string; chunkerVersion: string; embeddingVersion: string
contentHash: string; indexedAt: string; validUntil?: string
}
// 查询前过滤是授权控制的一部分
const visible = (chunk: Chunk, actor: { tenantId: string; principals: Set<string> }) =>
chunk.tenantId === actor.tenantId &&
chunk.aclPrincipals.some(principal => actor.principals.has(principal))
索引服务不同召回路径
原始 Artifact 与搜索索引分离:对象存储保存可验证原文,索引保存检索投影和定位符。这样可以重建、迁移 Embedding,也能在引用时读取原文校验。
向量索引
Embedding 擅长语义近邻,但模型、维度、归一化和距离函数必须匹配并版本化。近似最近邻索引用速度换 Recall;例如 pgvector 官方文档明确说明 HNSW/IVFFlat 的候选参数和后过滤可能导致结果数下降。生产系统需用业务数据测速度—召回曲线。
倒排/BM25
BM25 对错误码、专有名词、版本、ID 和精确短语常有不可替代的价值。Elastic 文档把 BM25 作为默认全文相似度,并暴露词频饱和与文档长度归一化参数。它不是“旧技术兜底”,而是 Hybrid 的独立证据通道。
分区不是只为性能。语言、租户、数据域、地域或密级分区可以减少候选空间并强化隔离,但分区过细会让跨域问题漏召回。按安全硬边界先分区,按业务域做可路由索引,热度只作为性能层。
更新与删除必须端到端传播
采集任务使用源版本、内容哈希和幂等写入键实现可重放;它们只能识别重复输入,仍需数据库约束确保不会产生重复可见版本。更新时先产生新版本 Chunk,再原子切换可见版本并失效旧缓存;直接原地覆盖会在部分失败时留下新旧混合。Embedding 升级使用双索引回填、影子查询、指标对照和切换回滚,而不是停机全量重建。
删除与撤权优先级高于普通新鲜度。传播到 Chunk、Embedding、倒排/向量索引、摘要、缓存和 Eval 副本,并使用 Tombstone 阻止滞后的抓取任务复活内容。完成标志不是“消息已发出”,而是各下游返回可核验状态且查询不再命中。
数据链路也需要 SLO
监控采集延迟、解析成功率、隔离率、索引新鲜度、Chunk 数量分布、重复率、ACL 缺失率和删除传播时间。从线上检索结果必须能回溯 source → parser → chunker → embedding → index 版本,才能把“回答变差”定位到数据还是查询策略。
数字指标不能替代抽样:定期检查 PDF 阅读顺序、表格、代码、标题路径、引用定位和不同语言。建立小型 Golden Query 集,在任何解析器、Chunk、Embedding 或索引变更时运行数据面回归。
主动提取:为什么 Chunk 越小不一定检索越准?
故障定位:答案引用了旧政策,首先应该看哪些证据?
架构证据:如何证明一次 Embedding 升级可安全迁移?
资料与事实边界
以下以官方文档、标准、论文和原始工程资料为主。厂商 API、价格、限额与模型能力会变化,落地时仍需按目标版本做能力探测和回归。
- Lewis et al.:Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks — RAG 结合参数化模型与显式非参数知识的原始工作
- Microsoft:Chunk large documents for RAG and vector search — 固定、重叠、结构化切分的当前工程实现与限制
- Microsoft:Chunk and vectorize by document layout — 布局保留、父子字段和结构化 Chunk 的工程参考
- Elasticsearch:Similarity settings — BM25 的当前实现参数与默认行为
- pgvector:Official repository — HNSW/IVFFlat、过滤、索引构建和速度—召回边界