索引建好之后,生成模型还看不到任何私有段落。缺的是一步:query → 带分数的块。LlamaIndex 把这一步收成 Retriever:retrieve(query) 返回 list[NodeWithScore]。向量路擅长同义改写(「实验对象」→「小鼠」);货号、基因名走 BM25 更稳。两路再用倒数排名融合(reciprocal rank fusion,RRF)合成一份名单——此时还没有调用生成模型。
段末注释:Retriever =
str → list[NodeWithScore];RRF 按各路排名加 $\sum_i 1/(k+\mathrm{rank}_i)$,不要求两路分数同量纲。

1. 一句话定位
| 维度 | 内容 |
|---|---|
| 角色 | 知识层的召回:按 query 取出候选 Node,附相似度/词项分数 |
| 输入 → 输出 | str 或 QueryBundle → list[NodeWithScore] |
| 典型调用入口 | index.as_retriever().retrieve()、BM25Retriever.from_defaults()、QueryFusionRetriever |
| 与 LangChain / LangGraph | 近邻是 BaseRetriever.invoke;LangGraph 节点里只 retrieve,不要在图里 from_documents |
出现背景:as_query_engine() 把检索和生成焊死。要调 top-k、加 metadata 过滤、或把检索当 Agent 工具,必须先把 Retriever 拆出来。
混合检索的评测口径(Recall@k / nDCG)见 RAG 目录;本篇只讲 LlamaIndex 怎么把两路拼上。
2. 前置依赖与环境
1 | pip install -U llama-index-core llama-index-llms-ollama llama-index-embeddings-ollama llama-index-retrievers-bm25 |
- Python 3.10+;
llama-index-core0.14.x - 向量路必须设
Settings.embed_model;纯retrieve不必设 LLM(QueryFusionRetriever(num_queries>1)才会为改写 query 调 LLM) - 异步:
await retriever.aretrieve(q),参数与retrieve一致
3. 实现逻辑
1 | 1. 建好 VectorStoreIndex(Node 已有 embedding) |
字段级变形:
1 | "实验对象是什么物种?" |
4. 原理说明
主轴是:Retriever 只负责候选名单;分数不可跨后端比较;混合靠排名不是靠把余弦和 BM25 加在一起。
1 | 1. retrieve(str) 包成 QueryBundle(query_str=...) |
index.as_retriever(**kwargs) 出现在步骤 2。对向量索引默认构造 VectorIndexRetriever。similarity_top_k 默认常为 2(过小)。
NodeWithScore(数据类)出现在步骤 3。字段:node: BaseNode,score: float | None。引用读 node.metadata,不要 str(hit)。
MetadataFilters 出现在步骤 4。MetadataFilter(key, value, operator=FilterOperator.EQ);多条件用 FilterCondition.AND/OR。
BM25Retriever.from_defaults 出现在步骤 5。三者只传一个:index / nodes / docstore。默认 language="english" 的停用词/词干对中文帮助有限,拉丁符号(GAPDH、C57BL/6)仍有效。
QueryFusionRetriever 出现在步骤 6~7。num_queries=1 关闭 query 生成;多路同一语料才叫混合,多路不同索引见 Router 专篇。
VectorStoreQueryMode.HYBRID 要求向量库本身支持稀疏+稠密(Weaviate / OpenSearch 等)。SimpleVectorStore 没有这一档,单机用 BM25 + Fusion。
5. 最小可运行示例
1 | pip install -U llama-index-core llama-index-embeddings-ollama llama-index-retrievers-bm25 |
1 | from llama_index.core import Document, Settings, VectorStoreIndex |
生产异步用 await hybrid.aretrieve(q)。
6. 重要配置参数
| 参数(API 名) | 类型 / 默认值 | 功能说明 | 作用与影响 | 参考起点 / 常用范围 | 配置指导 |
|---|---|---|---|---|---|
similarity_top_k |
int,常默认 2 | 本路最多返回几条 | 过小漏召回;过大噪声进合成 | 向量 5~10;融合截断同量级 | 后面有 rerank 可先放大 |
filters |
MetadataFilters,可选 | 按 Node.metadata 在库侧硬过滤 | 键不存在或类型不匹配 → 空结果 | section=methods |
入库时 metadata 必须是扁平标量 |
num_queries |
int,Fusion 默认 4 | 除原 query 外再生成几条改写 | >1 要 LLM;本地延迟倍增 |
混合基线用 1 | 改写策略另开 Query Rewrite,不要默认 4 |
mode |
str,Fusion 默认多为 simple | 多路名单如何合成 | reciprocal_rerank 不比绝对分;simple 易被量纲带偏 |
reciprocal_rerank |
向量+BM25 用 RRF |
BM25Retriever.language |
str,默认英语停用词 | 词干/停用词语言 | 中文正文收益小;不影响拉丁符号 | 货号/基因名场景可忽略 | 纯中文再换中文分词方案 |
vector_store_query_mode |
枚举,默认 DEFAULT | 后端是否走 hybrid/sparse | Simple 店无 HYBRID,传了也没用 | 有 OS/Weaviate 再用 | 单机走 BM25+Fusion |
7. 适用 / 不适用
| 维度 | 适用 | 不适用 |
|---|---|---|
| 任务形态 | 语义问 + 术语/货号并存 | 只要生成、不需要引用——不必拆 Retriever |
| 集成约束 | 单机 Simple 店 + BM25 | 向量库已提供原生 hybrid——用后端 hybrid,少一层 Python Fusion |
| 工程阶段 | 调 top-k / 过滤 / 当工具 | 多知识库「先选库再搜」——那是 Router,不是本篇 Fusion |
环、审批仍交给 LangGraph;本对象只返回名单。
8. 易踩坑
- 把
NodeWithScore当 str 塞 prompt:模型读到对象 repr。用hit.node.text。 similarity_top_k=2不改:方法节证据经常排在第 3。- Fusion
num_queries=4却没准备 LLM:改写失败或偷偷打 OpenAI。 - 比较 vector.score 与 bm25.score:量纲不同;要比名次或只看融合后的 RRF 分。
小结
retrieve停在 NodeWithScore,不生成答案。- filters 在库侧截断;BM25 补拉丁符号;RRF 融合两路排名。
- 混合基线:
num_queries=1+mode="reciprocal_rerank"。 - 要把检索接到问答,用 QueryEngine 把本对象塞进去。