Skip to content

Agent 进阶(三)进阶 RAG(下):重排序、切分与选型

🕒 Published at:

上一篇我们用查询改写和混合检索,把"该找到的资料尽量都找回来"(提升召回)。但还剩一个关键问题:找回来一大堆,最相关的那条不一定排在最前面。这一篇讲怎么把最相关的精准顶到最前——重排序(rerank),再聊两件影响 RAG 效果的"地基工程":文档怎么切向量库怎么选。仍以 Python 为主,关键处标注 Node。

一个容易被忽略的真相:排在前面≠最相关

RAG 通常只把检索到的前几条(比如前 3 条)喂给大模型,因为塞太多又贵又干扰。问题来了:检索器给的排序,未必真的按"最相关"排

举个例子。用户问「扫地机器人能不能拖地」,检索器可能返回:

text
第1条:……扫地机器人的清扫模式介绍……(沾点边,但没答到点)
第2条:……充电与续航说明……(不相关,只是都提到"机器人")
第3条:本机支持扫拖一体,可同时扫地和拖地……(← 真正的答案!)

真正的答案排在第 3 条。如果你只取前 2 条喂给模型,模型手里根本没有正确资料,只能瞎编或说不知道。检索器擅长"海选",但不擅长"精挑"——它用的相似度打分比较粗糙,容易被"字面沾边但实际不相关"的内容干扰。

重排序:请一位「精挑」的专家

重排序的思路很直白:先让检索器海选出较多的候选(比如前 20 条),再请一个更懂"相关性"的模型逐条重新打分、重新排序,最后只留最好的前 3 条。

检索器海选粗筛出前 20 条重排序精挑逐条重新打分留最好的前 3 条海选保召回、精挑保精度,各司其职

打个比方:检索器像 HR 筛简历,从几百份里粗筛出 20 份"看着还行"的;重排序像用人部门的专家,把这 20 份逐一细读,挑出真正最合适的 3 个。海选图快、精挑图准,两步配合。

用什么来"精挑"?常见有两类:一类是专门的重排序模型(rerank model,需要额外接入);另一类更省事——直接让大模型来重排,我们已经有 qwen,不用额外下载任何东西。LangChain 里用 LLMListwiseRerank 配合 ContextualCompressionRetriever 即可:

python
import os
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_core.vectorstores import InMemoryVectorStore
from langchain_core.documents import Document
from langchain_classic.retrievers.document_compressors import LLMListwiseRerank
from langchain_classic.retrievers.contextual_compression import ContextualCompressionRetriever

model = ChatOpenAI(model="qwen-plus", api_key=os.getenv("DASHSCOPE_API_KEY"),
                   base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", temperature=0)
embeddings = OpenAIEmbeddings(model="text-embedding-v3", api_key=os.getenv("DASHSCOPE_API_KEY"),
                              base_url="https://dashscope.aliyuncs.com/compatible-mode/v1")

docs = [Document(page_content="扫地机器人的清扫模式介绍……"),
        Document(page_content="充电与续航说明……"),
        Document(page_content="本机支持扫拖一体,可同时扫地和拖地。")]

# 第一步:海选,多捞一些候选(k 调大,比如 20)
base_retriever = InMemoryVectorStore.from_documents(docs, embeddings).as_retriever(
    search_kwargs={"k": 20}
)

# 第二步:用大模型重排,只留最相关的前 3 条
reranker = LLMListwiseRerank.from_llm(llm=model, top_n=3)
retriever = ContextualCompressionRetriever(
    base_compressor=reranker,
    base_retriever=base_retriever,
)

for d in retriever.invoke("扫地机器人能不能拖地"):
    print(d.page_content)

经过重排,「支持扫拖一体」那条会被顶到最前——因为大模型真正读懂了它和问题最相关。注意代价:重排要对每条候选再过一遍模型,会增加延迟和成本,所以海选数量别开太大(20 左右够了),并只在"排序质量很重要"的场景用。

Node 侧:LangChain.js 同样有 ContextualCompressionRetriever;重排常用 Cohere Rerank、Jina Reranker 这类云服务,或用 LLM 重排的等价实现。思路完全一致:先海选、再精挑。

地基工程一:文档到底该怎么切

检索的上限,很大程度上在"切分"这一步就定了。切得不好,后面再怎么优化都事倍功半。常见两个极端:

切太大(比如一块 2000 字):一块里混了好几个主题,检索时"命中"了这一块,但真正有用的可能只有其中两句,其余全是噪音,既稀释相关性又浪费 token。

切太小(比如一块 50 字):一句被切得七零八落,上下文断裂。比如「重新安装尘盒即可解决」被单独切出来,检索到了却不知道是解决"哪个问题"的。

实践中的几条经验:按语义边界切(优先在段落、标题、句号处断开,别把一句话拦腰砍断,这也是我们一直用 RecursiveCharacterTextSplitter 的原因);留一点重叠(相邻块重叠几十字,避免边界信息丢失);结合文档结构切(如果文档有清晰的标题层级,按"标题 + 其下内容"成块,语义最完整)。一个常见起点是 300~500 字一块、重叠 50 字左右,再根据你的文档类型调。

举个直观对比:一份「产品 FAQ」,最好按"一问一答"成块(每个 Q&A 一块,语义天然完整);而一份「连续的操作手册」,则更适合按段落带重叠地切。没有万能参数,切分要看文档长什么样。

地基工程二:向量库怎么选

入门用的内存向量库,重启就没、也扛不了大数据量。真实项目怎么选?一句话原则:按"数据量 + 是否要持久化 + 团队运维能力"来定。

简单说三档:

  • 内存向量库(如 InMemoryVectorStore):零配置,适合开发、测试、小 demo。缺点是不持久化、数据量一大就撑不住。
  • 轻量本地库(如 Chroma):能落盘持久化,单机就能跑,适合中小项目、几万到几十万条的知识库。上手简单,是很多团队的第一选择。
  • 专业向量数据库(如 Milvus、Qdrant,或给 Postgres 装 pgvector 插件):为海量数据和高并发设计,支持分布式、过滤、高可用,适合数据量大、要上生产扛流量的场景。代价是要专门部署和运维。

选型别一步到位求"最强"。大多数项目从 Chroma 起步完全够用,等数据量和访问量真的顶不住了,再迁移到专业向量库——而且因为 LangChain 把向量库接口统一了,迁移时业务代码基本不用改,这正是框架的价值。

怎么判断"扛不住"真的是向量库的锅

这里要小心一个误区:系统一变慢,就以为是向量库不行,急着换。 很多时候慢在别处(模型生成慢、网络慢、重排太多),换了向量库白忙一场。升级前先确认瓶颈真的在检索这一环。判断方法就一句话:把每一步的耗时拆开看。

用第八篇要讲的可观测手段(或者简单点,在检索前后各打一个时间戳),把一次问答拆成"检索用了多久、模型生成用了多久"。如果发现检索这一步的耗时明显偏高、而且随数据量增长越来越慢,那才轮到怀疑向量库。反过来,如果检索只花几十毫秒、慢的是模型那一秒多,那换向量库纯属做无用功。

确认是检索环节后,再看是不是向量库本身扛不住——几个典型信号:

  • 数据一多就变慢或崩:内存向量库随数据量涨,检索线性变慢、内存吃紧,甚至直接 OOM(内存溢出)。这是最明显的"该升级了"。
  • 并发一高就超时:单个用户查很快,一旦多人同时用就排队、超时、连接打满——说明它扛不住并发,需要为高并发设计的专业库。
  • 写入/建索引越来越慢:知识库频繁更新时,入库和建索引卡顿。
  • 需要按条件过滤但很吃力:比如要"只在某部门、某时间段的文档里检索",而当前方案要么不支持元数据过滤、要么一过滤就很慢。

一个稳妥的做法:单独给检索这一步做个小压测——不走大模型,纯粹反复调 retriever.invoke(),逐步加大数据量和并发,看它从多少量级开始明显变慢或报错。这个"拐点"就是你该升级向量库的信号,而不是凭感觉。

小结

这一篇补齐了 RAG 的"精度"和"地基":重排序让最相关的资料排到最前(海选保召回、精挑保精度);切分决定检索的上限(按语义切、留重叠、看文档结构);向量库选型从 Chroma 起步、按需升级。

加上上一篇的召回优化,你的 RAG 就从"能用"迈向了"好用"。下一篇我们换个主题:当一个 Agent 什么都想干、反而干不好时,该怎么把它拆成一个"分工协作的团队"——多智能体(上)