RAG 向量检索:从“查字“到“查意“

LLM 很聪明,但它有一个硬伤:知识截止到训练那天 。你今天问它"公司上周更新的报销流程",它只能瞎编。RAG(Retrieval-Augmented Generation,检索增强生成)就是为了解决这个问题:先检索外部知识,再让 LLM 基于检索结果生成答案。而 RAG 的检索环节,核心靠的就是向量检索

这篇文章从零讲清楚:什么是向量检索、Embedding 干了什么、相似度怎么算、大规模检索怎么加速,以及它在 Agent 里到底长什么样。

一、传统搜索 vs 向量检索:为什么"同义词"是个大麻烦

传统搜索引擎(比如数据库里 LIKE '%关键词%' 或倒排索引的 BM25)的核心逻辑是字面匹配。你用"调薪"去搜,文档里写的是"薪酬调整流程"------一个词对不上,直接搜不到。

这叫词汇鸿沟(lexical gap):你说的话和文档里写的话用的是不同词,但意思一样。传统搜索对此毫无办法------它没有"语义理解"能力,只有"字符串匹配"能力。

向量检索换了一个思路:不找"字",找"意思"。它把查询和文档都转成一串数字(向量),然后在数学空间里算距离。语义相近 → 距离近 → 命中。你说"怎么调薪",文档里有"薪酬调整流程",它们在向量空间里紧挨着------哪怕字面上完全不同。

对比维度 传统关键词搜索 向量检索
匹配依据 字面命中(TF-IDF/BM25) 语义距离(余弦相似度等)
"调薪"搜"薪酬调整" 搜不到 能搜到,向量距离近
搜人名、型号、编号 精准 可能漂移
搜"大概意思" 无能为力 强项

一句话:传统搜索像在找 ,向量检索像在找 。成熟的系统都会做混合检索(Hybrid Search)------两路结果融合,互补短板。

二、Embedding:把文字变成"语义坐标"

为什么需要 Embedding

计算机只认识数字。想让计算机判断"猫在沙发上睡觉"和"猫咪在沙发上打盹"意思相近,得先把这两句话都变成数字。Embedding 就是这个转换过程------把文本映射到一个高维向量空间里的一个点

输入一段文字,Embedding 模型输出一个固定长度的浮点数数组,比如 OpenAI 的 text-embedding-3-small 吐出 1536 个浮点数:

复制代码
[0.012, -0.453, 0.891, -0.023, ..., 0.147]   # 共 1536 维

你没法想象 1536 维空间长什么样(人类大脑超过三维就抓瞎了),但数学上完全合理。Embedding 模型在训练时就是靠海量语料学到了一个规律:把语义相近的文字放在高维空间中相邻的位置

经典类比:向量算术

Embedding 的妙处还不止于此。经典实验:"国王" - "男人" + "女人" ≈ "女王"。向量可以做加减法,而且结果还符合人类直觉------这说明向量确实编码了语义信息,不只是随机数字。

主流 Embedding 模型一览

模型 维度 特点
OpenAI text-embedding-3-small 1536 通用性强,性价比高,中文支持不错
OpenAI text-embedding-3-large 3072 精度更高,维度更大,适合高要求场景
BGE (BAAI) 768/1024 开源,中文优化,自部署免 API 费用
Jina Embeddings v3 1024 多语言,支持 8192 token 长文本输入
Cohere Embed v3 1024 支持多种输入类型(搜索文档/搜索查询等)

选模型的核心原则:入库和检索必须用同一个 Embedding 模型。不同模型产出的向量空间不一致,换模型 = 推倒重来。

三、相似度:怎么判断两个向量"靠得近"

向量检索最后一步,就是拿用户 query 的向量,去向量库里找距离最近的 K 个文档向量。这里"距离"有三种常用度量:

余弦相似度(Cosine Similarity)

最通用。计算两个向量夹角的余弦值,范围 [-1, 1]只看方向不看长度,所以长的文档和短的 query 也能公平比较。

复制代码
余弦相似度 = (A · B) / (||A|| × ||B||)

欧氏距离(Euclidean Distance)

两点之间直线距离。当向量已经 L2 归一化后,欧氏距离和余弦相似度等价。

点积(Dot Product)

两个向量逐元素相乘再求和。向量归一化后 = 余弦相似度。适合需要加权排序的场景。

实际工程里 90% 的场景用余弦相似度就对了。推理开销小,语义表达力足够。

四、ANN 加速:百万向量怎么毫秒级搜完

为什么要近似

如果暴力搜索(kNN)------拿 query 向量和库里每个向量都算一遍距离再排序------复杂度是 O(N)。N = 十万的时候还行,N = 百万、千万就崩了:每次查询要跑几百万次浮点运算,延迟秒级起步。

所以实际用的不是精确搜索,而是 ANN(Approximate Nearest Neighbor,近似最近邻)------牺牲一丁点精度(比如从"100% 召回"降到 "99.5% 召回"),换回几十上百倍的加速。

两大主流算法

IVF(Inverted File,倒排文件):先把所有向量用 K-Means 聚成 N 个桶(簇)。查询时只搜离 query 最近的几个桶,桶内做暴力搜索。核心思想是"先粗筛再细查"。

关键参数:

  • nlist:分多少个桶。越大越细,但建索引越慢。
  • nprobe:查几个桶。越大召回越高,但越慢。

HNSW(Hierarchical Navigable Small World,分层可导航小世界图):目前精度和速度的标杆。构建一个分层图结构------上层稀疏用于"导航到大致区域",下层稠密用于"精确定位"。搜索时从顶层逐层下降,类似在多层停车场里从顶层俯瞰逐步找到车位。

关键参数:

  • M:每个节点的最大连接数(16~64)。越大越精确,但内存也越大。
  • efSearch:搜索时考察的候选数(50~200)。越大召回越高,但越慢。
对比维度 IVF HNSW
召回精度 高,但依赖 nprobe 调参 极高,通常优于 IVF
检索速度 极快,接近 O(log N)
构建速度 快(只聚类) 慢(逐点建图)
内存占用 高(存全部原始向量) 高(存图结构 + 向量)
适合场景 平衡速度与精度的通用场景 高要求场景(RAG、推荐系统)

代表向量数据库

数据库 核心算法 特点
FAISS (Meta) IVF, HNSW, PQ 库而非数据库,极高性能,GPU 加速
Milvus IVF, HNSW 分布式架构,支持十亿级
Qdrant HNSW 过滤功能丰富,Rust 实现性能好
Chroma HNSW 轻量易用,适合原型和中小规模
pgvector IVF, HNSW PostgreSQL 扩展,和业务库同存

五、分块策略:切多大才合适

Embedding 模型再好,分块没搞对,检索照样烂。这是工程中最容易被低估的环节。

核心矛盾

  • 切太大(比如 2048 token):一段里塞了三个不同话题,Embedding 向量变成一个"杂糅语义点",搜什么都能命中一点,但精准度极差。
  • 切太小(比如 128 token):一句完整的话被腰斩,上下文丢失,LLM 拿到碎片答不出完整答案。

工程上的经验值

  • 通用场景:512 token 为一块,相邻块重叠 10%~15%(overlap)。这样关键句落在边界处也不被切开。
  • 技术文档/代码:按自然边界切------函数级、段落级、章节级。不要机械按字符数切。
  • QA 场景:每个 Q&A 对独立成块。
python 复制代码
# chunking_config.py --- 典型分块配置
from langchain.text_splitter import RecursiveCharacterTextSplitter

splitter = RecursiveCharacterTextSplitter(
    chunk_size=500,        # 每块 500 字符
    chunk_overlap=50,      # 相邻块重叠 50 字符(10%)
    separators=["\n\n", "\n", "。", ".", " "]  # 优先按自然边界切
)
chunks = splitter.create_documents([document])

分块同时要带上元数据:来源文档名、章节标题、时间戳、版本号。检索时可以用元数据做过滤("只搜 2024 年的文档"),精度大幅提升。

六、Rerank:少花钱、多办事

向量检索拉回 top-20 个候选块,直接全塞给 LLM?token 浪费不说,后面十几条可能是噪音。

两阶段检索是性价比最优解:

  1. 粗筛(向量检索):从全库拉 top-20 ~ top-50
  2. 精排(Rerank 模型):用一个专门的重排序模型对候选集打分,取 top-3 ~ top-5 喂 LLM

Rerank 模型比 Embedding 模型更"贵"但更准------它会把 query 和候选文档逐对精细比对,而不是只算一次向量距离。Cohere Rerank、BGE-Reranker 是常用选择。

python 复制代码
# retrieval.py --- 两阶段检索伪代码
def retrieve_and_rerank(query: str, top_k: int = 20, final_k: int = 3) -> list[str]:
    # 阶段1:向量粗筛
    query_vec = embed(query)
    candidates = vector_db.search(query_vec, top_k=top_k)

    # 阶段2:Rerank 精排
    scores = reranker.compute_scores(query, [c.text for c in candidates])
    ranked = sorted(zip(candidates, scores), key=lambda x: x[1], reverse=True)

    return [c.text for c, _ in ranked[:final_k]]

七、在 Agent 中长什么样

Agent 用 RAG 不需要把每个细节都自己手写,成熟的框架已经封装好了。以你正在学的 LangChain 为例:

python 复制代码
# agent_rag_pipeline.py --- Agent 中 RAG 的最小闭环
from langchain.document_loaders import TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.embeddings import OpenAIEmbeddings
from langchain.vectorstores import Chroma

# 1. 加载文档 + 分块
loader = TextLoader("company_policy.txt")
docs = loader.load()
chunks = RecursiveCharacterTextSplitter(
    chunk_size=500, chunk_overlap=50
).split_documents(docs)

# 2. 向量化 + 存入向量库
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
vectorstore = Chroma.from_documents(chunks, embeddings)

# 3. 作为检索器接入 Agent
retriever = vectorstore.as_retriever(search_kwargs={"k": 5})

# 4. Agent 调用时,retriever 自动搜相关文档注入上下文

完整链路:用户问 → Agent 决策 → 调 retriever 搜向量库 → 拿 top-k 文档 → 拼 prompt → LLM 生成 → 返回答案

关键设计点:

  • retriever 对 Agent 来说是一个工具(Tool),和其他工具(API 调用、数据库查询、代码执行)并列。Agent 自己决定什么时候该用检索、什么时候不该。
  • 检索到的内容注入到 LLM 的 system prompt 或上下文里,而不是替换用户原始问题。

八、常见坑

现象 真相
向量检索结果完全不对 入库和检索用了不同的 Embedding 模型,向量空间不统一
换了个相似问法就搜不到了 Embedding 模型对领域术语没训练过,考虑微调或换领域模型
明明文档有答案,检索就是命中不了 分块太大,语义被稀释;或分块太小,关键上下文被切断
向量检索快但内存爆炸 没做向量压缩(PQ),1536 维 × 百万条 = 几个 GB 起
Rerank 加了反而变慢 Rerank 模型和 Embedding 模型有"排序偏好冲突"------粗筛选出的候选集本身不行,精排救不回来
混合检索分数无法对齐 向量余弦值(-1~1)和 BM25 得分量纲不同,直接加权无效;用 RRF(倒数排名融合)代替

九、总结

向量检索用一句话概括:把文本映射到高维向量的语义空间,靠距离度量"意思有多近",用 ANN 算法在大规模数据中毫秒级找出最相关的文档。它在 RAG 链路里扮演"外部大脑的记忆索引"------不是取代 LLM,而是让 LLM 有据可依。

工程上的优先级:分块策略 > Embedding 模型选择 > 向量数据库选型。很多项目在向量数据库上纠结半天,结果分块一塌糊涂------方向跑偏了。

最后记住一个核心判断:向量检索擅长"语义模糊匹配",不擅长"精确条件查找"。人名、型号、编号、日期范围、法条编号------这些老老实实用过滤条件或关键词搜索,别硬往向量上套。

相关推荐
这个DBA有点耶8 小时前
MySQL主从延迟的“最后一公里”:如何把延迟压到极限
数据库·程序员·架构
Wang's Blog8 小时前
Vibe Coding一人即团队系列8: Claude Code 模型选型指南
人工智能
我叫孙一鸣-专注电子元器件8 小时前
自动驾驶的眼睛正在换代:激光雷达的“小镜子”正在改变未来
人工智能·机器学习·自动驾驶·卫星通讯·压电驱动器
后端小肥肠8 小时前
开营 10 天变现率 20%:我用一套 Skill,把小红书虚拟资料跑成了可复制流程
人工智能·aigc·agent
格林威9 小时前
C#图像快速剪切:使用OpenCvSharp和Halcon优化图像剪切和CPU占用
开发语言·人工智能·数码相机·计算机视觉·c#·视觉检测·工业相机
远航计算机9 小时前
GEO自诊断实操手册:你的内容为什么AI搜不到?
人工智能·矩阵·aigc·媒体
DianSan_ERP9 小时前
WMS接入电商平台自动化履约实战:一张订单从平台到出库的接口时序设计
java·前端·网络·数据库·安全·架构·自动化
richard_first9 小时前
Transformer 与大语言模型:第6章 Self-Attention自注意
人工智能·深度学习·transformer
深念Y9 小时前
约束工程:如何让 AI 没法跑偏
人工智能·ai·软件工程·codex·opencode·ccsiwtch
赛逸会展s9 小时前
2027赛逸展机器人展新闻发布会在京举行
人工智能·机器人