系统篇

15|检索离线链路:文档怎样变成可查询索引

系统篇约 5.8 千字1 组分步图解
本章目录

本章预备知识

索引是为了更快找候选的辅助结构,不是文档本身。关键词检索用词项,向量检索用数值距离;两者都可能找错版本或越过权限边界。

本章各题列出所需基础。可先完成相应基础课,也可通过自检确认掌握程度。

进入深读前的小检查:先给三篇短文建立词到文档的倒排表,再对一个查询找候选。随后再看BM25和HNSW为什么不等于完整事实判断。

术语带虚线时可点击速查。公式中的字母先看定义;不要用猜缩写替代推导。

先把检索分成“离线准备”和“在线查询”。很多所谓Embedding效果不好,实际是解析丢失表格、切块断开条件、权限元数据错误或索引过期。

沿着对象与状态理解本章

离线链路决定以后能检索到什么。文档解析、分块、索引与版本标签各自改变可查对象;在线搜索再聪明,也无法找回从未入库或已被错误切掉的条件。

下方图解的固定输入在“输入与适用条件”中声明。它用于解释本章机制,不与本章其他独立例子的数值暗中接续。

动态图解 · F28

检索先找候选,再判断能否回答

倒排索引和向量索引分别在帮什么忙?

倒排像书后索引:从一个词跳到包含它的页面。向量检索则尝试把表达相近的内容放得接近,帮助不知道精确名称时找候选。二者都不负责自动确认权限、版本和事实正确性,仍要读取与核对证据。

索引为快速找候选而建立的数据结构。
召回从大集合中找出可能相关的候选。
近似搜索以少检查部分候选换速度,可能漏掉真实近邻。

输入与适用条件:三个文档为本书构造;倒排关系可精确核验,语义搜索只作流程说明,不编造向量服务实测分数。

1 / 4 · 先有原文,不是先有向量

第1步,共4步

窄屏可左右滑动图形,或点击“放大图形”。文字解释紧随图后。

检索先找候选,再判断能否回答:先有原文,不是先有向量A部署 使用 DockerB测试 使用 pytestCDocker 镜像 构建

三份短文档都有自己的标识。索引是为查询服务的辅助结构,原文和版本仍应保留。

A/B/C是文档ID

默认静止。用“下一步”看完解释再继续;播放到最后一步自动停止。键盘聚焦播放器后,可用左右键逐步查看、空格播放/暂停。图中颜色辅助区分,含义同时用文字注明。

为什么需要

避免每次全库扫描,但保持原文可追溯。

怎样实现

按合适粒度建索引,保持文档ID、内容、版本和权限关联。

可能的代价与误解

切块丢标题条件、索引未更新、相似度与权限混用都会出错。

如何判断是否做对

用已知相关文档检查召回,并单独测试版本更新和撤权后的可见性。

Q057|RAG建库从原文件到索引经历哪些步骤?Chunk应该怎样切?

核心回答

先解析并保留结构与来源,再清理、划分语义单元、附加元数据,最后分别建立稀疏/稠密索引。Chunk是检索单位,不一定等于最终给模型的阅读单位。切得小提高定位精度,切得大保留上下文,两者需要分层协调。[CTXRETRIEVAL]

一份技术文档的两种错误切法

文档写:“默认重试三次。对于非幂等写操作,禁止自动重试。”若按固定字符把后半句切到下一块,命中第一块的Agent可能给出危险建议。切块需要尊重标题、段落、列表、表格和代码边界,至少保留条件与例外。

另一极端把整篇几万字文档作为一个chunk,向量可能被众多主题平均,短查询难定位具体规则,且把整篇放入上下文代价高。可以采用小块检索、大块阅读:先命中具体段落,再扩展到父节或相邻必要内容。扩展要去重并受预算约束。

元数据至少考虑doc_id、chunk_id、source_version、章节路径、原文范围、语言、权限和有效时间。向量生成使用的文本可含标题与局部上下文,但用于引用的原文应保持可追溯。摘要增强是派生内容,不能冒充原文事实。

表格尤其容易出错。只保留单元格数值却丢行列标题,模型不知道“30”是秒、天还是金额。代码块被清洗器折叠缩进,可能改变语义。建库前应抽检解析结果,而不是等检索失败后才调向量模型。

运行示例 · Q057

把一句条件切断,为什么召回到了也可能答错

输入与条件。原文:“试用账户:仅工作日可以退款,申请须在7天内;付费账户:30天内可申请。”

步骤与对象内部动作中间结果 / 新状态
1坏分块块A只有“7天内”;块B只有“30天内”数值存在,但主体和条件失去关联。
2按结构分块分别保留试用/付费主体及完整条件块可独立解释。
3附元数据记录文档ID、版本、章节和权限便于过滤与回溯。
4父段回读用户问周末试用退款,召回试用块必要时补读完整条款,而非只匹配“退款”。
5更新处理规则从7天改14天新版本入库并使旧块不再作为当前有效证据。

把过程连起来。分块的基本单位是可解释证据,不只是固定字符数。

边界与反例。退款内容为虚构业务,不构成法律或真实产品政策;最优块大小需要实际数据验证。

依据与适用范围:[DERIVE]。示例输入和场景由本书明确设定。

追问:Overlap越大,召回越好吗?

重叠能缓解边界断裂,但会产生重复候选,挤占Top-k并增加存储。大量近重复还可能让模型误认为多个独立来源共同支持结论。应评估跨边界问题和候选去重,而不是固定追求高重叠。

追问:为什么文档更新不能只新增向量?

旧chunk可能仍在索引,查询同时返回新旧规则;长度变化也会使原chunk编号指向不同内容。需要版本化替换、删除/失效旧块,并更新稀疏与稠密索引的一致视图。允许短暂不一致时必须明确读路径如何选择版本。

追问:所有内容都适合向量化吗?

精确ID、错误码、配置键和数字范围常更适合精确/词法/结构化查询。向量有助于语义召回,但不能取代数据库条件和权限过滤。检索系统应保留多种索引,而非把所有数据压成一个向量后失去结构。

Q058|BM25的公式每一项做什么?为什么词出现四次不一定比两次得分高?

核心回答

BM25结合词的区分度、词频饱和与文档长度归一化。高频出现的查询词有帮助,但收益递减;冗长文档中重复出现不应无限压倒简短相关文档。具体IDF写法和参数由实现而定。[BM25]

逐项拆公式并计算

一个常见正值IDF版本为:

score(q,d)=Σt∈q IDF(t) × tf(t,d)(k1+1)
                          / [tf(t,d)+k1(1−b+b|d|/avgdl)]
IDF(t)=ln[1+(N−df(t)+0.5)/(df(t)+0.5)]

N是文档数,df是包含该词的文档数;tf是该词在当前文档出现次数;|d|是长度,avgdl是平均长度。k1影响饱和速度,b控制长度归一化,b=0不按长度调整,b=1更充分按相对长度调整。分词与索引单位会影响所有统计。

教学例:N=100,df=10,k1=1.2,b=0.75,avgdl=100,则IDF约2.263745。三个文档只考虑一个查询词:

文档 tf 长度 得分
D1 2 100 3.11265
D2 4 400 2.52164
D3 1 50 2.84585

D2出现4次仍低于D1,因为它长四倍;D3只出现1次但较短,得分也高于D2。这不是“长文档一定差”,而是平衡匹配信号与冗长带来的偶然命中。数值已由本包脚本计算。

运行示例 · Q058

两个文档都含“排除”,BM25为什么不只数次数

输入与条件。文档A=[排除,路径],B=[排除,排除,路径,缓存],C=[日志,输出]。N=3,平均长度8/3,k1=1.2,b=0.75;用非负IDF形式ln(1+(N−df+0.5)/(df+0.5))。

步骤与对象内部动作中间结果 / 新状态
1文档频率“排除”出现在A和Bdf=2,不是出现总次数3。
2IDFln(1+1.5/2.5)0.470004。
3A词频因子1×2.2/(1+1.2×(0.25+0.75×2/(8/3)))约1.113924。
4B词频因子2×2.2/(2+1.2×(0.25+0.75×4/(8/3)))约1.205479。
5得分各因子乘IDFA≈0.523548,B≈0.566580,C=0。
6观察B出现两次但更长得分只是略高,而不是简单变成A的两倍。

把过程连起来。BM25同时考虑词稀有程度、频次饱和和长度归一化。

边界与反例。明确使用非负IDF变体,不与其他教材的无加1对数形式混同;中文先用给定Token,不涉及实际分词器。

依据与适用范围:[BM25]。示例输入和场景由本书明确设定。

追问:BM25为什么找不到同义表达?

基本形式依赖词项重叠,查询“认证过期”与文档“token失效”可能没有足够共同词。可通过分词、同义词、查询改写或稠密召回补充,但改写要保留原始精确键,避免把错误码、函数名改没。

追问:中文和代码分词有什么特别之处?

中文没有天然空格,分词策略会影响词项;代码含snake_case、camelCase、路径与符号,既需要拆词召回,也常需要保留完整标识符用于精确匹配。通用自然语言分词器未必适合代码,应该用真实查询评测。

追问:不同查询的BM25分数能当统一置信度吗?

一般不能。查询长度、词频与语料分布不同,分数尺度会变化。某查询得10不代表比另一查询得5更有事实依据。排序分数是候选相关性信号,不是答案正确概率。

Q059|Embedding检索模型怎样训练?为什么语义相似不等于回答相关?

核心回答

常见稠密检索用query encoder和document encoder产生向量,通过对比学习拉近相关对、推远负例。查询与文档可以分开编码以便离线建索引。向量相似度是训练目标下的相关性近似,不自动理解否定、版本、权限或精确数值条件。[DPR]

双塔模型的训练与查询

对查询q,正例d⁺和负例d⁻,计算s(q,d)=emb(q)·emb(d)或规范化余弦。一种对比目标是:

L = −log exp(s(q,d⁺)/τ) / Σd∈候选 exp(s(q,d)/τ)

分母中负例迫使模型区分看似相关但不回答问题的段落。随机负例可能太容易;hard negatives更接近真实搜索难题,比如同一函数不同版本、同一产品不同平台。但误把实际相关文档当负例会损害训练。

离线阶段将每个文档chunk编码并保存;在线只编码query,再做相似度搜索。这比把query与全库每篇文档联合输入大模型便宜得多。若更换embedding模型,旧向量通常不能与新query向量直接混用,需要重建或维护独立索引版本。

语义相似的两个段落可能观点相反:“允许自动重试”与“禁止自动重试”主题非常接近;旧版与新版接口说明也高度相似。检索之后必须看原文条件和元数据,不能从向量距离直接下结论。

运行示例 · Q059

检索向量为何需要训练,而不是随便放坐标

输入与条件。查询向量q=[1,0],相关文档a=[0.8,0.6],不相关文档b=[0,1],均为单位向量;以点积得分,温度1。

步骤与对象内部动作中间结果 / 新状态
1计算相似度q·a=0.8,q·b=0目前相关文档更靠近。
2转为训练概率exp(0.8)/(exp(0.8)+1)相关项概率约0.689974。
3对比损失−ln(0.689974)约0.371101。
4分数梯度softmax减目标[1,0]约[-0.310026,0.310026],推动相关得分上升、不相关下降。
5困难负例若b是词很像但语义不同的文档比完全无关负例更能检验细粒度边界,但错误标注会误导训练。

把过程连起来。检索Embedding的几何关系来自任务目标和训练样本,不是Token编号之间天然存在的距离。

边界与反例。例子只到分数梯度;完整编码器训练还需链式法则、批负例、归一化等实现,不能把2维教学点当真实语义空间。

依据与适用范围:[DERIVE]。示例输入和场景由本书明确设定。

追问:为什么必须注意query/document前缀和pooling?

某些embedding模型训练时对查询和文档使用不同任务前缀、编码方式或pooling。推理时忽略约定会改变分布。维度正确不代表语义空间正确;部署要固定模型、Tokenizer、预处理、归一化和版本。

追问:向量维度越大越好吗?

更高维度可增加表示容量,也增加存储、索引和计算;收益取决于训练与任务。高维未必解决精确ID或时效问题。应在相同语料、查询与Top-k预算下测召回和下游完成率。

追问:模型参数里的知识与embedding索引有什么区别?

参数知识通过训练压入权重,难逐条更新;索引保存外部文档表示,可定位原文和版本。Embedding本身通常不能无损恢复原文,因此仍要保留原始内容。把向量库当唯一知识备份会丢掉可读证据。

Q060|HNSW怎样近似找最近邻?权限过滤放在哪一步才安全?

核心回答

HNSW用分层近邻图导航:高层快速靠近目标区域,低层扩大候选搜索,得到近似最近邻。它用速度、内存和召回做取舍。权限过滤属于安全与检索语义,不能只在最终答案里要求模型“不泄露”。[HNSW]

从入口节点到候选堆

高层图较稀疏,从入口节点比较邻居距离,贪心移动到更接近查询的位置;逐层下降,到底层用更宽的候选集合探索。efSearch类参数控制搜索宽度,增大通常提高ANN召回并增加延迟;建图的连接数和构建搜索宽度影响图质量与内存。不同实现参数名和细节可能变化。

近似检索有两种不同失败:没有找到向量空间里真正最近的点,是ANN算法召回问题;找到了最近点但它不回答问题,是语义表示/任务相关性问题。调大efSearch只针对前一种,不会自动修复错误embedding或切块。

权限方面,若先从全库Top10取候选,再删除无权限项,可能剩零条,尽管有权限区域还有很相关的结果;这是后过滤导致的召回损失。更严重的是,如果无权限正文已经送到外部reranker或LLM,最终删掉引用也不能消除泄露。应在受信任边界内按身份过滤,或使用支持安全过滤的索引/命名空间,再把允许内容交给后续模型。

运行示例 · Q060

近似搜索只看到两点,怎样漏掉真实最近邻

输入与条件。查询q=0。候选A=0.2、B=0.1、C=0.9,使用绝对距离。教学搜索过程由于访问预算只检查A、C。

步骤与对象内部动作中间结果 / 新状态
1精确基准检查全部三点B距离0.1最近。
2受限搜索只访问A、C返回A,距离0.2。
3识别误差A不是不存在的文档错误是搜索未访问B,而非相似度公式算错。
4加权限若B无权访问在授权集合A、C里A反而是正确结果。
5调参对照扩大访问预算并保持数据/权限不变测召回、延迟与资源,不仅看返回数量。

把过程连起来。近似误差、过滤导致的候选变化和向量表达误差必须分开诊断。

边界与反例。这是访问预算造成漏召回的最小反例,不是完整HNSW构图或遍历实现;HNSW实际还含层级、邻接和候选队列。

依据与适用范围:[DERIVE]。示例输入和场景由本书明确设定。

追问:预过滤是否一定更快?

不一定。复杂过滤会改变图搜索路径或需要额外结构,某些实现可能退回精确搜索。选择应同时满足隔离正确性和性能;不能为了速度跳过授权。小数据集上精确向量扫描可能已足够简单可靠。

追问:ANN召回率如何测?

固定向量与距离定义,用精确Top-k作为参考,比较ANN返回集合的重合;这只评估索引近似误差。另需人工/任务标注评价真实相关文档召回。两种“recall”分母不同,报告时必须说明。

追问:删除文档后向量还在会怎样?

可能返回已经撤销的数据、旧权限或失效内容。删除要贯穿原文、稀疏索引、向量索引、缓存、摘要和派生记忆;至少查询时有权威失效检查。仅隐藏UI中的文件名不是完成数据删除。

记录保存在本机浏览器。

输入关键词即可检索。

↑ ↓ 选择 Enter 打开Esc 关闭 · 仅本地搜索

阅读设置

阅读设置仅保存在本机浏览器,不上传任何数据。