第三章 索引构建

0 阅读24分钟

向量嵌入

基础概念

向量嵌入(Embedding)

向量嵌入(Embedding) 是一种将真实世界中复杂、高维的数据对象(如文本、图像、音频、视频等)转换为数学上易于处理的、低维、稠密的连续数值向量(一个固定长度的一维数组)的技术。

image.png

向量空间的语义表示

Embedding 产生的向量是对数据语义的数学编码。

在 Embedding 构建的向量空间中,语义上相似的对象,其对应的向量在空间中的距离会更近;而语义上不相关的对象,它们的向量距离会更远。

通常使用以下数学方法来衡量向量间的“距离”或“相似度”:

  • 余弦相似度 (Cosine Similarity)  :计算两个向量夹角的余弦值。值越接近 1,代表方向越一致,语义越相似。这是最常用的度量方式。
  • 点积 (Dot Product)  :计算两个向量的乘积和。在向量归一化后,点积等价于余弦相似度。
  • 欧氏距离 (Euclidean Distance)  :计算两个向量在空间中的直线距离。距离越小,语义越相似。

Embedding 在 RAG 中的作用

语义检索的基础

RAG 的“检索”环节通常以基于 Embedding 的语义搜索为核心,流程如下:

  1. 离线索引构建:文档切分后,使用 Embedding 模型将每个文档块(Chunk)转换为向量,存入专门的向量数据库中。
  2. 在线查询检索:当用户提出问题时,使用同一个 Embedding 模型将用户的问题也转换为一个向量。
  3. 相似度计算:在向量数据库中,计算“问题向量”与所有“文档块向量”的相似度。
  4. 召回上下文:选取相似度最高的 Top-K 个文档块,作为补充的上下文信息,与原始问题一同送给大语言模型(LLM)生成最终答案。

决定检索质量的关键

Embedding 的质量直接决定了 RAG 检索召回内容的准确性与相关性。一个优秀的 Embedding 模型能够精准捕捉问题和文档之间的深层语义联系,即使用户的提问和原文的表述不完全一致。

RAG 对嵌入技术的新要求

RAG 是为了解决大语言模型的两大问题:

  • 知识固化:内部知识难以更新
  • 幻觉:生成的内容可能不符合事实,且难以溯源

通过“检索-生成”范式,动态地为 LLM 注入外部知识。这一过程的核心是 语义检索,很大程度上依赖于高质量的向量嵌入。

后续 RAG 的兴起对嵌入技术提出了更高、更具体的要求:

  • 领域自适应能力:通用的嵌入模型在专业领域(如法律、医疗)往往表现不佳,这就要求嵌入模型具备领域自适应的能力,能够通过微调或使用指令(如 INSTRUCTOR 模型)来适应特定领域的术语和语义。
  • 多粒度与多模态支持:RAG 系统需要处理的不仅仅是短句,还可能包括长文档、代码,甚至是图像和表格。这就要求嵌入模型能够处理不同长度和类型的输入数据
  • 检索效率与混合检索:嵌入向量的维度和模型大小直接影响存储成本和检索速度。同时,为了结合语义相似性(密集检索)和关键词匹配(稀疏检索)的优点,支持混合检索的嵌入模型(如 BGE-M3)应运而生,在某些任务中成为提升召回率的关键。

嵌入模型训练原理

现代嵌入模型的核心通常是 Transformer 的编码器(Encoder)部分,BERT 就是其中的典型代表。它通过堆叠多个 Transformer Encoder 层来构建一个深度的双向表示学习网络。

基于自监督学习策略的主要训练任务

现代嵌入模型学习上下文的逻辑、语法和词汇搭配,理解懂得了词与词之间的关系。

掩码语言模型 (MLM)

  • 过程
    • 随机地将输入句子中 15% 的词元(Token)替换为一个特殊的 [MASK] 标记。
    • 让模型去预测这些被遮盖住的原始词元是什么。
  • 目标:通过这个任务,模型被迫学习每个词元与其上下文之间的关系,从而掌握深层次的语境语义。

下一句预测 (Next Sentence Prediction, NSP)

  • 过程
    • 构造训练样本,每个样本包含两个句子 A 和 B。
    • 其中 50% 的样本,B 是 A 的真实下一句(IsNext);另外 50% 的样本,B 是从语料库中随机抽取的句子(NotNext)。
    • 让模型判断 B 是否是 A 的下一句。
  • 目标:这个任务让模型学习句子与句子之间的逻辑关系、连贯性和主题相关性。
  • 重要说明:后续的研究(如 RoBERTa)发现2,NSP 任务可能过于简单,甚至会损害模型性能。因此,许多现代的预训练模型(如 RoBERTa、SBERT)在预训练阶段移除了 NSP。

效果增强策略

现代嵌入模型 需要能精准判断“用户提的问题”和“知识库里的哪篇文章”最匹配,在检索任务中表现更佳。

  • 度量学习 (Metric Learning)  :
    • 思想:直接以“相似度”作为优化目标。
    • 方法:收集大量相关的文本对(例如,(问题,答案)、(新闻标题,正文))。训练的目标是优化向量空间中的相对距离:让“正例对”的向量表示在空间中被“拉近”,而“负例对”的向量表示被“推远”。关键在于优化排序关系,而非追求绝对的相似度值(如 1 或 0),因为过度追求极端值可能导致模型过拟合。
  • 对比学习 (Contrastive Learning)  :
    • 思想:在向量空间中,将相似的样本“拉近”,将不相似的样本“推远”。
    • 方法:构建一个三元组(Anchor, Positive, Negative)。其中,Anchor 和 Positive 是相关的(例如,同一个问题的两种不同问法),Anchor 和 Negative 是不相关的。训练的目标是让 distance(Anchor, Positive) 尽可能小,同时让 distance(Anchor, Negative) 尽可能大。

多模态嵌入

现代 AI 的一项重要突破,是将简单的词向量发展成了能统一理解图文、音视频的复杂系统。

现实世界的信息是多模态的,包含图像、音频、视频等。多模态嵌入 (Multimodal Embedding) 的目标是将不同类型的数据(如图像和文本)映射到同一个共享的向量空间

CLIP 模型浅析

OpenAI 的 CLIP (Contrastive Language-Image Pre-training)  为多模态嵌入定义了一个有效的范式。CLIP 的架构采用双编码器架构 (Dual-Encoder Architecture),包含一个图像编码器和一个文本编码器,分别将图像和文本映射到同一个共享的向量空间中。

为了让这两个编码器学会“对齐”不同模态的语义,CLIP 在训练时采用了对比学习 (Contrastive Learning)  策略。在处理一批图文数据时,模型的目标是:最大化正确图文对的向量相似度,同时最小化所有错误配对的相似度。通过这种“拉近正例,推远负例”的方式,模型从海量数据中学会了将语义相关的图像和文本在向量空间中拉近。

image.png

常用多模态嵌入模型(以bge-visualized-m3为例)

bge-visualized-m3 会先用视觉编码器提取图像的 patch token,再将其映射到与文本同维度的“图像 token”,与文本 token 一起送入 BGE 的 Transformer 编码器进行联合建模,最终得到可用于图文检索的统一向量表示。

  • 模型架构:将图像token嵌入集成到BGE文本嵌入框架中构建的通用多模态嵌入模型。之前的 BGE 模型处理纯文本已经非常厉害了,没有必要从头再造一个模型,而是直接在这个优秀的“纯文本大脑”上,外接了一个“视觉模块”(图像 token 嵌入)。这样它就从单一的“文本处理机”变成了可以同时处理图片和文字的“多模态(Multimodal)”模型。、
  • 模型参数:
    • model_name_bge: 指定底层BGE文本嵌入模型,继承其强大的文本表示能力。
    • model_weight: Visual BGE的预训练权重文件,包含视觉编码器参数。
  • 多模态编码能力:可以把三种不同类型的东西,都变成电脑能懂的“数字向量”
    • 纯文本编码: 保持原始BGE模型的强大文本嵌入能力。
    • 纯图像编码: 使用基于EVA-CLIP的视觉编码器处理图像,提取图片的特征。
    • 图文联合编码: 将图像和文本特征融合到统一的向量空间。
  • 应用场景: 主要用于混合模态检索任务,包括多模态知识检索、组合图像检索、多模态查询的知识检索等。
  • 相似度计算: 使用矩阵乘法计算余弦相似度,所有嵌入向量都被标准化到单位长度,确保相似度值在合理范围内。
import os
os.environ["HF_ENDPOINT"] = "https://hf-mirror.com"
import torch
from visual_bge.visual_bge.modeling import Visualized_BGE

# 加载智源(BAAI)的 Visualized_BGE 模型。
model = Visualized_BGE(model_name_bge="BAAI/bge-base-en-v1.5",
                      model_weight="../../models/bge/Visualized_base_en_v1.5.pth")
# 现在进入 评估/推理模式
model.eval()

# 请把计算梯度的功能关掉,大幅节省显存并提高计算速度。
with torch.no_grad():
    # 文字 变成 文字向量
    text_emb = model.encode(text="datawhale开源组织的logo")
    # 图片 变成 图片向量
    img_emb_1 = model.encode(image="../../data/C3/imgs/datawhale01.png")
    # 同传图文 变成 图文融合向量
    multi_emb_1 = model.encode(image="../../data/C3/imgs/datawhale01.png", text="datawhale开源组织的logo")
    img_emb_2 = model.encode(image="../../data/C3/imgs/datawhale02.png")
    multi_emb_2 = model.encode(image="../../data/C3/imgs/datawhale02.png", text="datawhale开源组织的logo")

# 计算相似度
# 在 Python 的科学计算中,@ 代表矩阵乘法,.T 代表转置
# BGE 模型输出的向量通常在内部做过归一化(长度为 1)
# 所以向量之间的矩阵乘法(点积)在数学上就绝对等价于“余弦相似度” 。
sim_1 = img_emb_1 @ img_emb_2.T
sim_2 = img_emb_1 @ multi_emb_1.T
sim_3 = text_emb @ multi_emb_1.T
sim_4 = multi_emb_1 @ multi_emb_2.T

print("=== 相似度计算结果 ===")
print(f"纯图像 vs 纯图像: {sim_1}")
print(f"图文结合1 vs 纯图像: {sim_2}")
print(f"图文结合1 vs 纯文本: {sim_3}")
print(f"图文结合1 vs 图文结合2: {sim_4}")

运行结果:

=== 相似度计算结果 ===
纯图像 vs 纯图像: tensor([[0.8318]])
图文结合1 vs 纯图像: tensor([[0.8291]])
图文结合1 vs 纯文本: tensor([[0.7627]])
图文结合1 vs 图文结合2: tensor([[0.9058]])

向量数据库

向量数据库主要功能

向量数据库的核心价值在于其高效处理海量高维向量的能力。

  • 高效的相似性搜索:利用专门的索引技术(如 HNSW, IVF),能够在数十亿级别的向量中实现毫秒级的近似最近邻(ANN) 查询,快速找到与给定查询最相似的数据。
  • 高维数据存储与管理:专门为存储高维向量(通常维度成百上千)而优化,支持对向量数据进行增、删、改、查等基本操作。
  • 丰富的查询能力:除了基本的相似性搜索,还支持按标量字段过滤查询(例如,在搜索相似图片的同时,指定年份 > 2023)、范围查询和聚类分析等,满足复杂业务需求。
  • 可扩展与高可用:现代向量数据库通常采用分布式架构,具备良好的水平扩展能力和容错性,能够通过增加节点来应对数据量的增长,并确保服务的稳定可靠。
  • 数据与模型生态集成:与主流的 AI 框架(如 LangChain, LlamaIndex)和机器学习工作流无缝集成,简化了从模型训练到向量检索的应用开发流程。

image.png

工作原理

架构

向量数据库通常采用四层架构,通过存储层索引层查询层服务层的协同工作来实现高效相似性搜索。

  • 存储层:负责存储向量数据和元数据,优化存储效率并支持分布式存储。
  • 索引层:维护索引算法(HNSW、LSH、PQ等),负责索引的创建与优化,并支持索引调整。
  • 查询层:处理查询请求,支持混合查询并实现查询优化。
  • 服务层:管理客户端连接,提供监控和日志能力,并实现安全管理。

主要技术手段

  • 基于树的方法:如 Annoy 使用的随机投影树,通过树形结构实现对数复杂度的搜索
  • 基于哈希的方法:如 LSH(局部敏感哈希),通过哈希函数将相似向量映射到同一“桶”
  • 基于图的方法:如 HNSW(分层可导航小世界图),通过多层邻近图结构实现快速搜索
  • 基于量化的方法:如 Faiss 的 IVF 和 PQ,通过聚类和量化压缩向量

本地向量存储:以 FAISS 为例

FAISS (Facebook AI Similarity Search) 是一个由 Facebook AI Research 开发的高性能库,专门用于高效的相似性搜索和密集向量聚类。FAISS 本质上是一个算法库,它将索引直接保存为本地文件(一个 .faiss 索引文件和一个 .pkl 映射文件),而非运行一个数据库服务。这种方式轻量且高效。

from langchain_community.vectorstores import FAISS
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_core.documents import Document

# 1. 示例文本和嵌入模型
texts = [
    "张三是法外狂徒",
    "FAISS是一个用于高效相似性搜索和密集向量聚类的库。",
    "LangChain是一个用于开发由语言模型驱动的应用程序的框架。"
]
docs = [Document(page_content=t) for t in texts]
embeddings = HuggingFaceEmbeddings(model_name="BAAI/bge-small-zh-v1.5")

# 2. 创建向量存储并保存到本地
# 这 3 Document对象 翻译成向量,存入 FAISS 数据库
vectorstore = FAISS.from_documents(docs, embeddings)

# 把建好的数据库打包,以文件的形式保存在当前目录下的 faiss_index_store 文件夹里
local_faiss_path = "./faiss_index_store"
vectorstore.save_local(local_faiss_path)

print(f"FAISS index has been saved to {local_faiss_path}")

# 3. 加载索引并执行查询
# 加载时需指定相同的嵌入模型,并允许反序列化
loaded_vectorstore = FAISS.load_local(
    local_faiss_path,
    embeddings,
    # Python 在保存这种复杂对象到硬盘时,底层通常会用到一种叫 pickle 
    # LangChain 官方加了一道安全锁。它强迫你必须显式地写上 allow_dangerous_deserialization=True
    allow_dangerous_deserialization=True
)

# 相似性搜索
query = "FAISS是做什么的?"
results = loaded_vectorstore.similarity_search(query, k=1)

print(f"\n查询: '{query}'")
print("相似度最高的文档:")
for doc in results:
    print(f"- {doc.page_content}")

Milvus

Milvus 是一个开源的、专为大规模向量相似性搜索和分析而设计的向量数据库。Milvus 从设计之初就瞄准了生产环境。其采用云原生架构,具备高可用、高性能、易扩展的特性,能够处理十亿、百亿甚至更大规模的向量数据。

部署安装

  1. 安装 Docker 与 Docker Compose
  2. 下载官方的 docker-compose.yml 文件etcd 用于存储元数据,MinIO 用于对象存储。
  3. 启动 Milvus 服务:在 docker-compose.yml 文件所在的目录中,运行命令docker compose up -d以后台模式启动 Milvus。Docker 将会自动拉取所需的镜像并启动三个容器:milvus-standalonemilvus-minio, 和 milvus-etcd
  4. 查看 Docker 容器:在终端运行 docker ps 命令 (Linux),确认三个 Milvus 相关容器(milvus-standalonemilvus-miniomilvus-etcd)都处于 running 或 up 状态。

常用管理命令

  • 停止服务docker compose down
  • 彻底清理 (停止并删除数据)docker compose down -v

核心组件

Collection (集合)

Collection 是 Milvus 中最基本的数据组织单位,类似于关系型数据库中的一张表 (Table)所有的数据操作,如插入、删除、查询等,都是围绕 Collection 展开的。

  • Collection (集合) : 相当于一个图书馆,是所有数据的顶层容器。一个 Collection 可以包含多个 Partition,每个 Partition 可以包含多个 Entity
  • Partition (分区) : 相当于图书馆里的不同区域(如“小说区”、“科技区”),将数据物理隔离,指定特定的分区进行检索,让检索更高效。
  • Schema (模式) : 相当于图书馆的图书卡片规则,定义了每本书(数据)必须登记哪些信息(字段)。
  • Entity (实体) : 相当于一本具体的书,是数据本身。
  • Alias (别名) : 相当于一个动态的推荐书单(如“本周精选”),它可以指向某个具体的 Collection,方便应用层调用,实现数据更新时的无缝切换。
Schema (模式)

Schema 规定了 Collection 的数据结构,定义了其中包含的所有字段 (Field)  及其属性。一个设计良好的 Schema 是能够保证数据一致性并提升查询性能。

Schema 通常包含以下几类字段:

  • 主键字段 (Primary Key Field) : 每个 Collection 必须有且仅有一个主键字段,用于唯一标识每一条数据(实体)。它的值必须是唯一的,通常是整数或字符串类型。
  • 向量字段 (Vector Field) : 用于存储核心的向量数据。一个 Collection 可以有一个或多个向量字段,以满足多模态等复杂场景的需求。
  • 标量字段 (Scalar Field) : 用于存储除向量之外的元数据,如字符串、数字、布尔值、JSON 等。这些字段可以用于过滤查询,实现更精确的检索。

以一篇新闻文章为例,展示了一个典型的多模态、混合向量 Schema 设计。它将一篇文章拆解为:唯一的 Article (ID)、文本元数据(如 TitleAuthor Info)、图像信息(Image URL),并为图像和摘要内容分别生成了密集向量(Image EmbeddingSummary Embedding)和稀疏向量(Summary Sparse Embedding)。

Partition (分区)

PartitionCollection 内部的一个逻辑划分

每个 Collection 在创建时都会有一个名为 _default 的默认分区。我们可以根据业务需求创建更多的分区,将数据按特定规则(如类别、日期等)存入不同分区。一个 Collection 最多可以有 1024 个分区。

使用分区的好处:

  • 提升查询性能: 在查询时,指定只在一个或几个分区内进行搜索,大幅减少需要扫描的数据量,显著提升检索速度。
  • 数据管理: 便于对部分数据进行批量操作。
Alias (别名)

通过为一个 Collection 设置别名,我们可以在应用程序中使用这个别名来执行所有操作,而不是直接使用真实的 Collection 名称。这样做有如下好处:

  • 安全地更新数据:想象一下,你需要对一个在线服务的 Collection 进行大规模的数据更新或重建索引。直接在原 Collection 上操作风险很高。正确的做法是:
    1. 创建一个新的 Collection (collection_v2) 并导入、索引好所有新数据。
    2. 将指向旧 Collection (collection_v1) 的别名(例如 my_app_collection)原子性地切换到新 Collection (collection_v2) 上。
  • 代码解耦:整个切换过程对上层应用完全透明,无需修改任何代码或重启服务,实现了数据的平滑无缝升级。

索引 (Index)

索引本身就是一种为了加速查询而设计的复杂数据结构。对向量数据创建索引后,Milvus 可以极大地提升向量相似性搜索的速度,代价是会占用额外的存储和内存资源。

Milvus 支持对标量字段和向量字段分别创建索引:

  • 标量字段索引:主要用于加速元数据过滤,常用的有 INVERTEDBITMAP 等。通常使用推荐的索引类型即可。
  • 向量字段索引:这是 Milvus 的核心。选择合适的向量索引是在查询性能、召回率和内存占用之间做出权衡的艺术。
主要向量索引类型
  • FLAT(精确查找): 不建目录,暴力对比每一本书。极慢,极耗内存,但 100% 准确。 除非你的数据量特别小(比如只有几万条),否则千万别用。
  • IVF 系列(倒排索引): 首先通过聚类将所有向量分成多个“桶”(nlist),搜索时只看最相关的几个桶,缩小搜索范围,极大地提升了检索速度。性能和准确率平衡得很好,适合绝大多数常规场景。
  • HNSW(图索引): 构建一个多层的邻近图。查询时从最上层的稀疏图开始,快速定位到目标区域,然后在下层的密集图中进行精确搜索。速度最快,准确率极高,是现在的绝对主流。缺点就是极度消耗内存,构建索引的时间也较长。
  • DiskANN(磁盘索引): 如果你有十亿级的数据,HNSW 会把服务器内存撑爆。DiskANN 的黑科技在于,它能把索引存在相对便宜的 SSD 硬盘上,稍微牺牲一点点速度,换来能处理海量数据的能力。

检索

基础向量检索 (ANN Search)

从海量数据中高效地检索信息,是 Milvus 的核心功能之一。

近似最近邻 (Approximate Nearest Neighbor, ANN) 检索。与需要计算全部数据的暴力检索(Brute-force Search)不同,ANN 检索利用预先构建好的索引,能够极速地从海量数据中找到与查询向量最相似的 Top-K 个结果。

  • 主要参数:
    • anns_field: 指定要在哪个向量字段上进行检索。
    • data: 传入一个或多个查询向量。
    • limit (或 top_k): 指定需要返回的最相似结果的数量。
    • search_params: 指定检索时使用的参数,例如距离计算方式 (metric_type) 和索引相关的查询参数。
增强检索
过滤检索 (Filtered Search)

在实际应用中,我们很少只进行单纯的向量检索。更常见的需求是“在满足特定条件的向量中,查找最相似的结果”,这就是过滤检索。它将向量相似性检索与标量字段过滤结合在一起。

先根据提供的过滤表达式 (filter) 筛选出符合条件的实体,然后仅在这个子集内执行 ANN 检索。这极大地提高了查询的精准度

范围检索 (Range Search)

有时我们关心的不是最相似的 Top-K 个结果,而是“所有与查询向量的相似度在特定范围内的结果”。

范围检索允许定义一个距离(或相似度)的阈值范围。Milvus 会返回所有与查询向量的距离落在这个范围内的实体。

多向量混合检索 (Hybrid Search)

在真实的工程场景中,单一维度的向量(通常是密集向量 Dense Vector)擅长捕捉上下文的 泛化语义 ,但对专有名词、序列号等 精确关键词” 极其不敏感;而稀疏向量(Sparse Vector,类似于 BM25 算法产生的向量)则恰恰相反。混合检索的初衷,就是在系统底层将这两种(或多种)特征的检索能力结合起来。

  • 第一阶段:并行检索 (Parallel Retrieval):Milvus 会针对同一条查询指令,并发执行多个底层的 ANN 检索任务。

    • 数据存储结构:在 Milvus 的 Collection(集合)定义中,开发者为数据设定了多个向量字段。例如 field_dense:存储由 BERT 等模型生成的密集向量(维度固定,如 768 维,大部分值为非零浮点数);field_sparse:存储由 SPLADE 等模型生成的稀疏向量(维度极大,如 30000 维,但绝大多数值为 0,仅保留关键词权重)。
    • 查询向量生成:当用户发起查询时,应用层会将查询文本分别送入不同的 Embedding 模型,生成对应的一个密集向量和一个稀疏向量。
    • 并发执行索引查询: Milvus 接收到这两个查询向量后,会同时在对应的硬件线程上发起检索请求.线程 A 在 field_dense 的 HNSW 索引中执行近似最近邻搜索,基于余弦相似度 (Cosine Similarity) 返回最相似的 Top-K 个结果。线程 B 在 field_sparse倒排索引中执行搜索,基于内积 (Inner Product) 返回得分最高的 Top-K 个结果。
    • 阶段性输出:此阶段结束时,Milvus 内存中会产生多个独立的候选结果列表(List A 和 List B)。每个列表中包含了实体的 ID 以及它们在各自维度下的原始距离得分 (Raw Score)。
  • 第二阶段:结果融合 (Rerank)

    • 分数不可比性 (Score Incompatibility): 密集向量的余弦相似度得分可能分布在 [-1.0, 1.0] 之间,而稀疏向量的内积得分可能是 [0.0, 150.0] 这样绝对值极大的浮点数。由于量纲和分布区间完全不同,直接相加毫无数学意义,高分向量会完全吞噬低分向量的影响权重。
    • RRFRanker (倒数排名融合 Reciprocal Rank Fusion)Milvus): 引入了重排器(Reranker),RRF 通过以下数学公式为每个文档计算新的融合得分。Milvus 会对所有召回的文档计算这个 RRF_ScoreRRF\_Score,然后根据这个新分数进行降序排列,截取最终的 Top-K 结果返回给用户。
RRF_Score=i=1n1k+rankiRRF\_Score = \sum_{i=1}^{n} \frac{1}{k + rank_{i}}

基于LlamaIndex的高性能生产级RAG构建方案

句子窗口检索(Sentence Window Retrieval):上下文扩展

使用小块文本进行检索可以获得更高的精确度,但小块文本缺乏足够的上下文,可能导致大语言模型(LLM)无法生成高质量的答案;而使用大块文本虽然上下文丰富,却容易引入噪音,降低检索的相关性。

其工作流程如下:

  1. 索引阶段:在构建索引时,文档被分割成单个句子。每个句子都作为一个独立的“节点(Node)”存入向量数据库。同时,每个句子节点都会在元数据(metadata)中存储其上下文窗口,即该句子原文中的前N个和后N个句子。这个窗口内的文本不会被索引,仅仅是作为元数据存储。
  2. 检索阶段:当用户发起查询时,系统会在所有单一句子节点上执行相似度搜索。因为句子是表达完整语义的最小单位,所以这种方式可以非常精确地定位到与用户问题最相关的核心信息。
  3. 后处理阶段:在检索到最相关的句子节点后,系统会使用一个名为 MetadataReplacementPostProcessor 的后处理模块。该模块会读取到检索到的句子节点的元数据,并用元数据中存储的完整上下文窗口来替换节点中原来的单一句子内容。
  4. 生成阶段:最后,这些被替换了内容的、包含丰富上下文的节点被传递给LLM,用于生成最终的答案。

结构化索引

随着知识库的规模不断扩大(例如,包含数百个PDF文件),传统的RAG方法(即对所有文本块进行top-k相似度搜索)会遇到瓶颈。当一个查询可能只与其中一两个文档相关时,在整个文档库中进行无差别的向量搜索,不仅效率低下,还容易被不相关的文本块干扰,导致检索结果不精确。

为了解决这个问题,一个有效的方法是利用结构化索引。其原理是在索引文本块的同时,为其附加结构化的元数据(Metadata)。这些元数据可以是任何有助于筛选和定位信息的标签,例如:

  • 文件名
  • 文档创建日期
  • 章节标题
  • 作者
  • 任何自定义的分类标签

这种“先过滤,再搜索”的策略,能够极大地缩小检索范围,显著提升大规模知识库场景下RAG应用的检索效率和准确性。

参考

第三章 索引构建