源文件:chapter3/retrieval-pipeline/README.md 带神经重排的混合检索管线 一个用于教学的检索管线,结合稠密嵌入、稀疏检索与神经重排,展示不同检索方法的长处与短板。 教学目标 本项目演示: 稠密 vs 稀疏检索:各自擅长什么、为什么 混合检索:组合多种检索方法获得更好结果 神经重排:用 transformer 模型对检索结果重排序 并行处理:跨多个服务高效地索引与检索 真实工程模式:面向生产的 API 设计与错误处理 架构 关键概念 稠密嵌入(语义检索) 模型:BGE-M3(多语言,1024 维向量) 优点: 语义相似(能找到相关概念) 跨语言检索(跨语言工作) 概念理解(处理同义词) 上下文感知(理解含义) 短板: 可能漏掉精确字符串 对代码 / ID
源文件:chapter3/retrieval-pipeline/README.md
一个用于教学的检索管线,结合稠密嵌入、稀疏检索与神经重排,展示不同检索方法的长处与短板。
本项目演示:
┌──────────────────────────────────────────────┐ │ Client Application │ └────────────────────┬─────────────────────────┘ │ ▼ ┌──────────────────────────────────────────────┐ │ Retrieval Pipeline (Port 4242) │ │ │ │ ┌──────────────────────────────────────┐ │ │ │ Document Store (In-Memory) │ │ │ └──────────────────────────────────────┘ │ │ │ │ ┌──────────────────────────────────────┐ │ │ │ BGE-Reranker-v2 (Local Model) │ │ │ └──────────────────────────────────────┘ │ └────────┬──────────────────┬─────────────────┘ │ │ ▼ ▼ ┌─────────────────┐ ┌─────────────────┐ │ Dense Service │ │ Sparse Service │ │ (Port 4240) │ │ (Port 4241) │ │ │ │ │ │ BGE-M3 Model │ │ BM25 Engine │ └─────────────────┘ └─────────────────┘
fusion.pyscore(d) = Σ 1/(k + rank_r(d)),k=60。[0,1],再加权BAAI/bge-reranker-base(更轻量,evaluate.py 使用)# 克隆仓库 cd projects/week3/retrieval-pipeline # 安装依赖 pip install -r requirements.txt # 模型会在首次运行时自动下载: # - BGE-M3:约 2.3GB # - BGE-Reranker-v2-M3:约 1.1GB
./start_all_services.sh
会启动:
# 终端 1:稠密服务 cd ../dense-embedding python main.py --port 4240 # 终端 2:稀疏服务 cd ../sparse-embedding python server.py --port 4241 # 终端 3:管线 cd ../retrieval-pipeline python main.py --port 4242
python test_client.py
会运行一组全面的测试用例,展示稠密与稀疏各自擅长的场景。
python demo.py
用真实查询做演示并附带讲解。
http://localhost:4242/docs
evaluate.py)上面的 test_client.py / demo.py 需要三个微服务(端口
4240/4241/4242)在运行。evaluate.py 在单进程内跑完整条
管线,完全离线,因此你可以在没有服务的情况下、用本地模型复现"每个阶段都改善排序"的故事。
它在一个小型带标注评测集上走完整管线——切块 → 嵌入 → 检索 → 融合 → 重排——并打印一张分阶段对比表和
逐查询的明细。CLI 提供完整的中文 --help:
python evaluate.py --help # 中文帮助:语料/查询/阶段/top-k/模型/输出等 python evaluate.py # 内置评测集,完整对比表(默认) python evaluate.py --no-dense # 仅 BM25,纯离线、无需任何模型 python evaluate.py --no-rerank # 跳过重排阶段 python evaluate.py --query "XR-7003" # 单条查询逐阶段排名追踪 python evaluate.py --embed-model BAAI/bge-m3 --pooling cls # 换稠密模型 python evaluate.py --output result.json # 结果写入 JSON
本地组件(每个阶段都是真实模型 / 算法,非 mock):
| 阶段 | 组件(默认) | 是否离线? |
|---|---|---|
| chunk | 字符窗口切分器 | 是 纯 Python |
| sparse | BM25(rank_bm25) |
是 无需下载模型 |
| dense | sentence-transformers/all-MiniLM-L6-v2(约 90MB) |
是 经由 transformers(多语言:可换 Qwen/Qwen3-Embedding-0.6B / BAAI/bge-m3) |
| fuse | RRF + 加权(fusion.py) |
是 纯 Python |
| rerank | BAAI/bge-reranker-base(约 1.1GB,首次运行下载) |
是 缓存后即可 |
说明:
--no-dense完全不需要任何 ML 模型(仅 BM25)。稠密和
重排阶段需要本地模型;首次运行会从 HuggingFace 下载,
之后--offline可让一切走本地缓存。在 Apple Silicon 上,
某些transformers版本在 MPS 设备上会输出NaN——CLI 会检测到这一情况
并自动回退到 CPU,因此结果始终是有限的。
内置评测集故意包含两个难点簇:近似重复的代码
(XR-7001..XR-7006、HTTP-400..HTTP-500)会让稠密检索失效(向量几乎
完全相同,只有精确词项匹配能找到正确的那条),以及
零词面重叠的改写(查询"reclaiming unused heap space without
programmer effort" → 文档"Automatic memory management frees developers…")会让
BM25 失效。
Stage / Method Recall@3 MRR nDCG@3 ------------------------------------------------------------------------------ BM25 (sparse) 0.9000 0.8500 0.8631 Dense 1.0000 0.9000 0.9262 Hybrid-RRF 1.0000 1.0000 1.0000 Hybrid-Weighted 1.0000 0.9500 0.9631 Hybrid-RRF+Rerank 1.0000 0.9500 0.9631 逐条查询 MRR 明细(1.00=正确文档排在第 1 位) Query BM25 Dense RRF Wgt Rerank ------------------------------------------------------------------------------ XR-7003 1.00 0.50 1.00 1.00 1.00 XR-7005 1.00 0.50 1.00 1.00 1.00 HTTP-403 1.00 1.00 1.00 1.00 1.00 HTTP-400 1.00 1.00 1.00 1.00 0.50 a beginner friendly language with tidy... 1.00 1.00 1.00 1.00 1.00 reclaiming unused heap space without p... 0.00 1.00 1.00 1.00 1.00 how vegetation turns light into food 0.50 1.00 1.00 0.50 1.00 hiding a note so eavesdroppers cannot ... 1.00 1.00 1.00 1.00 1.00 how does water move between the ocean ... 1.00 1.00 1.00 1.00 1.00 how are volcanoes formed from molten rock 1.00 1.00 1.00 1.00 1.00
如何解读:
reclaiming…=0.00、vegetation…=0.50)。XR-7003/XR-7005=0.50——它把一个兄弟代码排到了第一)。vegetationHTTP-400)。它的价值在更大的单查询追踪让融合机制变得直观:
$ python evaluate.py --query "XR-7003" [BM25 (sparse)] 1. xr_7003 score= 3.2260 Product model XR-7003 is a smartphone available now. [Dense] 1. xr_7001 score= 0.5247 Product model XR-7001 ... # 稠密把错误的兄弟排到第一 2. xr_7003 score= 0.5195 Product model XR-7003 ... [Hybrid-RRF] 1. xr_7003 score= 0.0325 Product model XR-7003 ... # 融合把精确匹配提升到第一
# 查询:"kitty behavior" # 文档包含:"feline"、"cat" # 稠密找到语义匹配,稀疏漏掉
# 查询:"Alexander Humphrey" # 稀疏找到精确名称匹配 # 稠密可能返回其他人
# 查询:"人工智能" # 稠密能找到任意语言的 AI 文档 # 稀疏只能找到中文文本
# 查询:"HTTP-403" # 稀疏找到精确错误码 # 稠密可能返回其他错误
# 查询:"happiness and excitement" # 文档包含:"joy"、"elation" # 稠密理解情绪概念
POST /index { "text": "Document content", "doc_id": "optional_id", "metadata": {"category": "example"} }
POST /search { "query": "search terms", "mode": "hybrid", # 或 "dense" 或 "sparse" "top_k": 20, "rerank_top_k": 10 }
响应包含:
GET /stats
GET /documents?limit=10&offset=0
检索响应提供了教学性的洞察:
{ "dense_results": [...], // 语义检索的顶部结果 "sparse_results": [...], // BM25 的顶部结果 "reranked_results": [ // 最终重排结果 { "rank": 1, "doc_id": "doc_1", "rerank_score": 0.95, "original_ranks": { "dense": 3, // 在稠密中原排第 3 "sparse": 5 // 在稀疏中原排第 5 }, "rank_changes": [ "dense: +2", // 上升 2 位 "sparse: +4" // 上升 4 位 ] } ], "statistics": { "overlap_percentage": 30.0, // 稠密/稀疏一致的程度 "avg_dense_rank_change": 1.5, "avg_sparse_rank_change": 2.1 } }
尝试不同查询:
修改参数:
top_k 检索更多 / 更少候选分析模式:
编辑 config.py 调整:
retrieval-pipeline/ ├── config.py # 配置设置(含 fusion_method / rrf_k) ├── document_store.py # 内存文档存储 ├── retrieval_client.py # 稠密/稀疏服务客户端 ├── reranker.py # BGE-Reranker 实现 ├── fusion.py # 结果融合:RRF + 加权分数融合 ├── retrieval_pipeline.py # 主管线编排(使用 fusion.py) ├── evaluate.py # 离线单进程评测 CLI(中文 --help) ├── main.py # FastAPI 服务 ├── test_client.py # 教学测试用例 ├── demo.py # 交互式演示 ├── requirements.txt # Python 依赖 ├── start_all_services.sh # 启动脚本 ├── stop_all_services.sh # 停止脚本 └── README.md # 本文件
本项目为学习用途的教学项目。