从零构建学术RAG系统:三种检索策略的演进
最近我在做一个”学术文献 AI 助手”把一堆论文 PDF 扔进去能直接用自然语言问”哪篇论文提出了 X”“Y 方法的优缺点是什么”并且答案必须给出具体出处。技术栈是典型的 RAGRetrieval-Augmented Generation但真上手才发现检索模块才是这个系统的天花板——检索不到对的段落LLM 再强也只能一本正经地编。这篇文章记录我把检索模块从”能用”改到”敢用”的三次演进V1 BM25 稀疏检索 → V2 向量检索 → V3 混合检索 RRF 融合 Cross-Encoder 重排。这不是教科书式的罗列而是项目里真实的踩坑顺序。三段核心代码全部可运行写文章当天我实跑过文中输出都是真实粘贴的外加一张选型对比表和一份调优清单。引言为什么”给论文做检索”比普通 RAG 更难做这个项目的动机很朴素我电脑里躺着两百多篇 PDF每次想引用”那篇提出某某方法的论文”都要靠文件名记忆术配合阅读器的全文搜索效率感人。这个助手要解决的就是这件事——用自然语言提问系统给出答案并附上出处哪篇论文、哪一节、哪一页。RAG 的道理一句话能讲完先从语料里检索出相关段落再把段落塞给 LLM 生成答案。但”生成”只是面子“检索”才是里子。检索不到生成就只能编而且编得理直气壮。所以这个项目里我花时间最多的不是调 prompt而是检索模块。给论文做检索比给新闻、FAQ 做检索难在四个地方•长文档。一篇论文 8k–20k tokens必须分块chunk。但论文不是一锅粥它有 Abstract / Intro / Method / Experiments 的骨架随手按 500 tokens 一刀切很容易把”方法描述”和”实验结论”缝进同一个块embedding 出来四不像。•专业术语与中英混杂。学术查询里全是 BM25、DPO、LoRA 这种缩写和模型名还经常中英混排。比如查”DPO 与 PPO 的区别”既要求检索器精确命中术语缩写又要求它理解”区别”是在问两个方法的对比——分词级的精确匹配和向量级的语义理解缺一不可。•公式与图表。LaTeX 公式、表格、图注都是信息载体分块和索引时怎么处理它们直接决定”第 3 节那个损失函数”这类查询能不能命中。•引用溯源。答案必须能回指到具体论文、章节、页码否则在学术场景就是耍流氓。这要求每个 chunk 从一开始就背着完整元数据走。我的演进路线是 V1 → V2 → V3顺序完全由踩坑决定先上 BM25 让系统跑起来被口语化查询打脸后加向量检索又发现术语查询变”飘”了最后把两路合并、再加一道重排兜底。读完本文你能带走三样东西三套可直接运行的代码同一份语料、同一组查询三版效果差异肉眼可见、一张选型对比表、一份我在项目里实际使用的调优清单。系统整体架构先把地图画出来整个系统分离线和在线两个阶段。离线侧负责把论文变成”可检索”的形态PDF 解析PyMuPDF / GROBID→ 清洗 → 章节感知分块 → 同时建两个索引BM25 倒排索引 FAISS 向量索引。在线侧负责应答用户查询 → 稀疏、稠密双路召回 → RRF 融合 → Cross-Encoder 重排 → Top-5 结果塞给 LLM 生成答案并附引用。flowchart LRsubgraph 离线索引A[论文 PDF 库] -- B[解析与清洗PyMuPDF / GROBID]B -- C[章节感知分块 元数据 title/doi/section/page]C -- D[(倒排索引BM25)]C -- E[Embedding 模型bge-m3]E -- F[(向量索引FAISS)]endsubgraph 在线检索生成Q[用户查询] -- R1[稀疏召回 Top-50]Q -- R2[稠密召回 Top-50]D -- R1F -- R2R1 -- M[RRF 融合]R2 -- MM -- N[Cross-Encoder 重排 Top-5]N -- G[LLM 生成附引用溯源]end本文只聚焦图里的检索模块双索引、双路召回、融合、重排PDF 解析和 LLM 生成只交代接口不展开。解析侧的接口约定是”进 PDF、出带元数据的 chunk”PyMuPDF 负责抽正文文本和版面GROBID 负责提取标题、作者、参考文献等结构化字段生成侧的接口约定是”进 Top-K chunk、出带引用的答案”把 chunk 文本连元数据一起拼进 prompt要求 LLM 按编号引用来源最后由代码把编号翻译回具体的论文、章节、页码。检索模块夹在中间上下游都以 chunk 为”最小货币”打交道——这也是为什么后面三版策略改来改去接口从来没变过。可能有读者会问V1 阶段明明只用 BM25为什么架构图上第一天就画了双索引这是踩坑换来的习惯索引是离线资产重建一次就要全量跑一遍所以哪怕 V1 只用稀疏路向量索引也该同步建着”养”在那里等到 V2 上线时直接切换不用回炉重造。先交代一个贯穿全文的设计元数据。每个 chunk 除文本外还背着一个字典——title / authors / year / doi 或 arXiv id / section / page。这是”引用溯源”的地基后面三版策略检索出来的不只是”一段文字”而是”一段知道出处的文字”生成阶段才能给出规范引用。第 7 节还会看到把”标题 章节名”拼进 chunk 文本本身顺手还能提升检索质量。技术选型与版本Python 3.10rank_bm25 做稀疏索引sentence-transformers 负责 embedding 与重排模型faiss-cpu 做向量索引jieba 负责中文分词。全部 pip 可装演示规模下不需要 GPU文末附 requirements。V1BM25 稀疏检索——先让系统”能用”第一版的目标只有一个跑通。我选了最经典的 BM25。它的原理一句话查询和文档共享的词越多、这些词在整个语料里越稀有文档得分越高。可以理解为 TF-IDF 的概率化改良外加两个工程修正词频饱和一个词出现 10 次不会让得分翻 10 倍参数 k1 控制饱和速度和长度归一长文档不因词多而占便宜参数 b 控制归一力度BM25 的数学公式如下score(D,Q)∑iIDF(qi)⋅f(qi,D)⋅(k11)f(qi,D)k1⋅(1−bb⋅∣D∣avgdl) \text{score}(D, Q) \sum_{i} \text{IDF}(q_i) \cdot \frac{f(q_i, D) \cdot (k_1 1)}{f(q_i, D) k_1 \cdot \left(1 - b b \cdot \frac{|D|}{\text{avgdl}}\right)}score(D,Q)i∑​IDF(qi​)⋅f(qi​,D)k1​⋅(1−bb⋅avgdl∣D∣​)f(qi​,D)⋅(k1​1)​其中DDD表示文档QQQ表示查询qiq_iqi​是查询QQQ中的第iii个词项f(qi,D)f(q_i, D)f(qi​,D)是词项qiq_iqi​在文档DDD中的词频IDF(qi)\text{IDF}(q_i)IDF(qi​)是词项qiq_iqi​的逆文档频率衡量该词在整个语料库中的稀有程度∣D∣|D|∣D∣是文档DDD的长度词数avgdl\text{avgdl}avgdl是整个语料库的平均文档长度k1k_1k1​是词频饱和参数控制词频对得分的影响程度通常取值 1.2-2.0bbb是长度归一化参数控制文档长度对得分的影响通常取值 0.75这个公式的核心思想是词频饱和通过(k11)(k_1 1)(k1​1)项防止高频词过度影响得分长度归一化通过1−bb⋅∣D∣avgdl1 - b b \cdot \frac{|D|}{\text{avgdl}}1−bb⋅avgdl∣D∣​项避免长文档仅因词多而占优。当b1b1b1时长度归一化完全生效当b0b0b0时不考虑文档长度影响。公式看看就好不用推导。BM25 是信息检索的经典强基线Robertson Zaragoza, 2009直到今天在 BEIR 这类零样本检索基准上它 依然是让很多新模型头疼的基线Thakur et al., 2021——这也是我敢拿它打底的原因。为了让全文自洽后面三段代码共用同一份”迷你语料”6 条一句话版论文摘要模拟分块后的 chunk以及两个精心设计的查询——q_exact 含稀有术语检验精确匹配能力q_semantic 是语义改写检验语义泛化能力。pip install rank_bm25 jiebaimport jiebaimport numpy as npfrom rank_bm25 import BM25OkapiDOCS [“Attention Is All You Need提出 Transformer 架构完全基于自注意力self-attention机制摒弃循环结构”, # doc0“BERT基于 Transformer 编码器的双向预训练语言模型通过掩码语言模型MLM学习上下文表示”, # doc1“BM25 是经典的信息检索排序函数基于词频 TF 与逆文档频率 IDF 的概率模型是稀疏检索基线”, # doc2“Retrieval-Augmented Generation (RAG)将参数化语言模型与非参数化的外部知识检索相结合用于知识密集型任务”, # doc3“Dense Passage Retrieval (DPR)用双塔编码器把问题和段落编码为稠密向量用于开放域问答的段落检索”, # doc4“FAISSMeta 开源的高效向量相似度搜索库支持十亿级向量的近似最近邻检索”, # doc5]QUERIES {“q_exact”: “BM25 的打分公式是什么”, # 含稀有术语 → 检验精确匹配“q_semantic”: “不用循环结构的序列建模方法”, # 语义改写 → 检验语义泛化}tok lambda s: list(jieba.lcut(s)) # 演示用默认词典真实项目要加学术自定义词典bm25 BM25Okapi([tok(d) for d in DOCS])for tag, q in QUERIES.items():scores bm25.get_scores(tok(q))order np.argsort(-scores) # 按得分从高到低排序print(f[{tag}] {q}“)for r, i in enumerate(order[:3]):print(f” Top{r1}: doc{i} score{scores[i]:.3f} {DOCS[i][:24]}…)我机器上的真实输出[q_exact] BM25 的打分公式是什么Top1: doc2 score3.980 BM25 是经典的信息检索排序函数基于词频 T…Top2: doc5 score0.687 FAISSMeta 开源的高效向量相似度搜索库…Top3: doc4 score0.686 Dense Passage Retrieval …[q_semantic] 不用循环结构的序列建模方法Top1: doc0 score2.468 Attention Is All You Nee…Top2: doc5 score0.400 FAISSMeta 开源的高效向量相似度搜索库…Top3: doc2 score0.372 BM25 是经典的信息检索排序函数基于词频 T…两组结果都值得掰开看•q_exact 是 BM25 的主场doc2 以 3.980 对 0.687 的悬殊比分碾压术语型查询稳得毫无悬念。•q_semantic 要诚实交代写文章前我本以为这个查询会让 BM25 当场翻车实际跑出来 doc0 居然是第一。翻看分词结果才发现原因——jieba 把查询切成[‘不用’, ‘循环’, ‘结构’, ‘的’, ‘序列’, ‘建模’, ‘方法’]而 doc0 也被切出了”循环”“结构”两个词这是字面巧合送的命中不是语义理解。巧合有多脆弱把同一个意思换得更口语化一点立刻现原形[q_spoken] 那个不用 RNN 的模型是怎么做的Top1: doc2 score3.581 BM25 是经典的信息检索排序函数基于词频 T…Top2: doc1 score2.203 BERT基于 Transformer 编码器的…Top3: doc3 score1.837 Retrieval-Augmented Gene…doc0 实际得分 1.037排在第 6/6 名正确答案 doc0 直接垫底Top1 反而是那篇讲 BM25 的论文。“RNN”和”循环结构”在字面层面是两个世界BM25 没有桥。学术场景下的优点零训练、毫秒级示意量级下同、结果可解释——每个得分都能对到具体的词调试时能直接看出”这篇为什么被召回”这在排查 bad case 时非常救命对术语缩写、模型名、作者名、年份这类”精确字符串”天然友好——而这恰是学术查询的高频模式。缺点同义词、改写、跨语言查询基本无能为力对分词质量敏感公式符号基本失效。分词这一点值得展开两句它是中文 BM25 最容易被低估的坑。jieba 默认词典不认识学术圈的黑话像 “LoRA”、“DPO” 这类缩写一定要 jieba.add_word() 加进自定义词典否则可能被切碎英文术语建议同时保留原词 token别把 “Self-Attention” 拆成 “self” 和 “attention” 就完事我的做法是中文走 jieba、连续英数串整体保留两路 token 混合进索引。另外 BM25Okapi 的 k1 和 b 参数我用的是默认值k11.5, b0.75这也是大多数系统的常用起点小语料上调参意义不大。工程小贴士rank_bm25 是纯 Python 实现适合原型验证语料上到百万级 chunk 后请直接换 Elasticsearch / OpenSearch这里不展开。V1 小结术语查询稳口语化提问翻车——这就是上 V2 的直接动机。4. V2稠密向量检索——补上语义理解V2 的思路换了个物种用预训练模型把查询和文档都编码成向量语义相近则向量距离相近再用 ANN近似最近邻索引快速找最近邻。三个关键概念三句话讲清•双塔编码器query 和 doc 用同一个模型但独立编码。好处是文档向量可以在离线阶段全部算好存进索引在线只需编码查询检索就是一次向量近邻查询极快。•余弦相似度 归一化后的内积代码里 normalize_embeddingsTrue 配上 FAISS 的 IndexFlatIPIP 即内积算出来的就是余弦相似度。•ANN 索引的取舍IndexFlatIP 是暴力全扫描结果精确、几万条内毫无压力数据量大了再换 IVF / HNSW用一点精度换数量级的速度。模型选择上演示代码用 BAAI/bge-small-zh-v1.5约 100MBCPU 秒加载生产环境建议直接上 bge-m3第 7 节展开。另外如实交代一句我写文章的环境里 huggingface.co 不可达模型是通过 ModelScope 镜像下载的同一个模型AI-ModelScope/bge-small-zh-v1.5网络正常的读者直接 SentenceTransformer(“BAAI/bge-small-zh-v1.5”) 即可网络受限就用 ModelScope 下载到本地再传路径行为完全一致。pip install sentence-transformers faiss-cpuimport faissimport numpy as npfrom sentence_transformers import SentenceTransformermodel SentenceTransformer(“BAAI/bge-small-zh-v1.5”) # 演示用小模型生产可换 bge-m3emb model.encode(DOCS, normalize_embeddingsTrue) # 归一化后内积 余弦相似度index faiss.IndexFlatIP(emb.shape[1]) # 原型期暴力检索即可index.add(np.asarray(emb, dtype“float32”))for tag, q in QUERIES.items():qv model.encode([q], normalize_embeddingsTrue).astype(“float32”)D, I index.search(qv, 3) # D相似度I文档下标print(f[{tag}] {q}“)for r, (d, i) in enumerate(zip(D[0], I[0])):print(f” Top{r1}: doc{i} cos{d:.3f} {DOCS[i][:24]}…)真实输出[q_exact] BM25 的打分公式是什么Top1: doc2 cos0.601 BM25 是经典的信息检索排序函数基于词频 T…Top2: doc1 cos0.397 BERT基于 Transformer 编码器的…Top3: doc4 cos0.365 Dense Passage Retrieval …[q_semantic] 不用循环结构的序列建模方法Top1: doc0 cos0.507 Attention Is All You Nee…Top2: doc4 cos0.465 Dense Passage Retrieval …Top3: doc1 cos0.463 BERT基于 Transformer 编码器的…两组结果同样掰开看•q_semantic 被救活了doc0 以 0.507 拿第一。但注意第二名是 doc4DPR0.465差距只有 0.04——“检索”主题的论文在语义空间里和”序列建模”查询靠得也不远语义干扰项开始贴身。•q_exact 虽然 doc2 仍是第一但对比一下优势幅度BM25 是 3.980 对 0.687近 6 倍差向量路是 0.601 对 0.397约 1.5 倍。术语查询的”稳赢感”没了BERT、DPR 这些语义近邻全挤进了 Top3。稀有术语、编号、人名在 embedding 里容易被”语义稀释”——模型对高频共现的语义敏感对字符串本身并不敏感。真正的意外在口语化查询上。我原本的预期是”向量检索总该行了吧”实跑结果当场教育了我[q_spoken] 那个不用 RNN 的模型是怎么做的Top1: doc1 cos0.542 BERT基于 Transformer 编码器的…Top2: doc2 cos0.461 BM25 是经典的信息检索排序函数基于词频 T…Top3: doc3 cos0.435 Retrieval-Augmented Gene…doc0 实际 cos0.392排在第 5/6 名doc0 排第 5Top1 是 BERT——BERT 确实是 Transformer 家族的但答非所问。演示用的小模型对”RNN ↔ 循环结构”这种跨表述映射并不牢靠。这个真实结果比预想的更有价值向量检索不是银弹效果同时受模型能力和查询表述影响。这个坑给第 5 节的重排埋下了伏笔也提醒演示模型和生产模型的差距——别拿 small 级模型的行为当最终结论。学术场景下的优点语义泛化强对改写、跨语言查询鲁棒特别适合”我知道意思但叫不出术语”的探索式查询对长摘要块的整体语义把握好于词匹配。缺点稀有术语/编号/人名可能被语义稀释chunk 过长会进一步稀释语义第 7 节谈分块结果不可解释效果依赖 embedding 模型的领域适配并且引入了模型推理成本和向量索引的运维。工程小贴士原型期 IndexFlatIP 完全够用chunk 上到十万级再考虑 HNSW / IVF或者干脆上 Chroma / Qdrant 这类现成向量库。V2 小结语义查询救活了但精确查询的优势变薄、近义干扰开始插队——V3 的动机齐了。5. V3混合检索 RRF 融合 Cross-Encoder 重排——生产级方案V1 保精确、V2 保语义两者的错误模式恰好互补把它们并起来召回就更全再花一点算力把候选精排一遍排序就更准。混合检索通常优于任何单一检索这在 BEIR 后续研究如 Luan et al., TACL 2021 对稀疏稠密混合表示的系统分析和工业界实践里都是通行结论。V3 由两个机制组成•RRF 融合Reciprocal Rank Fusion只用”排名”不用”原始分数”做融合score Σ 1/(krank)k 常取 60。为什么要绕开原始分数因为 BM25 的分0 到十几和余弦相似度0 到 1尺度根本不可比加权求和就得调权重而 RRF 免调参且效果稳Cormack, Clarke Büttcher, SIGIR 2009。•Cross-Encoder 重排双塔检索是”查询和文档各自编码、再比向量”信息在编码阶段就被压缩掉了Cross-Encoder 则把 query 和候选文档拼接后一起过模型让两者在注意力层面充分交互打分明显更准但慢——所以它只用于对双路召回的 Top-N比如 50候选做精排而不是全库扫描Nogueira Cho, 2019。整条流水线长这样flowchart TBQ[查询] -- S[BM25 召回 Top-50] T[向量召回 Top-50]S -- R[“RRF 融合score Σ 1/(60rank)”]T -- RR -- C[取并集候选 Top-50]C -- X[Cross-Encoder 逐对打分]X -- O[重排后 Top-5 进入生成]代码上注意一个”演进感”V3 不重写检索器而是把前两节的 bm25、index、model 当组件直接复用复用前两节的 bm25 / index / model体现演进而非重写import numpy as npfrom sentence_transformers import CrossEncoderRRF_K, RECALL_N, FINAL_N 60, 50, 3reranker CrossEncoder(“BAAI/bge-reranker-base”) # 只加载一次千万别写进查询函数里def hybrid_search(query: str):# 1) 双路召回各取 Top-RECALL_Nbm25_rank np.argsort(-bm25.get_scores(tok(query)))[:RECALL_N]qv model.encode([query], normalize_embeddingsTrue).astype(“float32”)vec_rank index.search(qv, RECALL_N)[1][0]vec_rank vec_rank[vec_rank 0] # 语料不足 RECALL_N 时 FAISS 用 -1 填充必须过滤print(f BM25 路 Top3: {[int(i) for i in bm25_rank[:3]]}f 向量路 Top3: {[int(i) for i in vec_rank[:3]]}“)# 2) RRF 融合只用排名不碰两路的原始分数尺度不可比fused {}for ranks in (bm25_rank, vec_rank):for r, doc_id in enumerate(ranks):fused[doc_id] fused.get(doc_id, 0) 1 / (RRF_K r 1)cands sorted(fused, keyfused.get, reverseTrue)[:RECALL_N]print(f” RRF 融合后 Top3: {[int(i) for i in cands[:3]]})# 3) Cross-Encoder 精排query 与候选拼接后逐对打分慢但准pairs [(query, DOCS[i]) for i in cands]ce np.asarray(reranker.predict(pairs))order np.argsort(-ce)return [(int(cands[i]), float(ce[i])) for i in order[:FINAL_N]]for tag, q in [(“q_exact”, QUERIES[“q_exact”]),(“q_semantic”, QUERIES[“q_semantic”]),(“q_spoken”, “那个不用 RNN 的模型是怎么做的”)]:print(f[{tag}] {q}“)for r, (i, s) in enumerate(hybrid_search(q)):print(f” 最终Top{r1}: doc{i} ce_score{s:.3f} {DOCS[i][:24]}…)print()真实输出这是全文我最喜欢的一组数字[q_exact] BM25 的打分公式是什么BM25 路 Top3: [2, 5, 4] 向量路 Top3: [2, 1, 4]RRF 融合后 Top3: [2, 1, 4]最终Top1: doc2 ce_score0.997 BM25 是经典的信息检索排序函数基于词频 T…最终Top2: doc3 ce_score0.005 Retrieval-Augmented Gene…最终Top3: doc4 ce_score0.003 Dense Passage Retrieval …[q_semantic] 不用循环结构的序列建模方法BM25 路 Top3: [0, 5, 2] 向量路 Top3: [0, 4, 1]RRF 融合后 Top3: [0, 2, 1]最终Top1: doc0 ce_score0.977 Attention Is All You Nee…最终Top2: doc2 ce_score0.021 BM25 是经典的信息检索排序函数基于词频 T…最终Top3: doc4 ce_score0.014 Dense Passage Retrieval …[q_spoken] 那个不用 RNN 的模型是怎么做的BM25 路 Top3: [2, 1, 3] 向量路 Top3: [1, 2, 3]RRF 融合后 Top3: [2, 1, 3]最终Top1: doc0 ce_score0.003 Attention Is All You Nee…最终Top2: doc2 ce_score0.001 BM25 是经典的信息检索排序函数基于词频 T…最终Top3: doc1 ce_score0.000 BERT基于 Transformer 编码器的…逐个解读•q_exact两路召回的 Top1 都是 doc2RRF 后 doc2 居首Cross-Encoder 给出 0.997 的高分断层第一第二名只有 0.005。•q_semantic两路 Top1 都是 doc0CE 0.977 稳定第一V2 里贴身紧逼的 DPRdoc4被压到了第三。•q_spoken最有戏剧性的一条。前两版里 doc0 一个垫底一个第五这里 BM25 路和向量路的 Top3 也都没有它RRF 融合后它依然不在前三——但因为 50 的召回深度把它兜进了候选集Cross-Encoder 逐对看完”那个不用 RNN 的模型”和”摒弃循环结构”之后直接把 doc0 捞到了第一。不过要诚实0.003 的绝对分并不高说明 base 级 reranker 在这个查询上只是”矮子里拔将军”生产环境换更大的 reranker如 bge-reranker-v2-m3会稳得多。这组对照把 V3 两个机制的分工讲清楚了融合负责”召回更全”一路漏掉的另一路补上重排负责”排得更准”候选集里的正确答案往前顶。在我的项目语料上也观察到同样的典型现象两路召回列表里各自漏掉的文档常被另一路补上重排后正确答案在 Top-3 的稳定性肉眼可见地提升。踩坑实录两条第一FAISS 在语料不足 k 条时会用 -1 填充返回结果不过滤的话 doc(-1) 会被当成正常候选送进 RRF——我第一版就踩了输出 Top3 里混进一个 -1读者照抄时引以为戒。第二reranker 模型要在函数外加载一次写进查询函数里每次查询都重新加载模型延迟直接爆炸。代价与边界两路索引加一次重排推理端到端延迟从毫秒级涨到百毫秒级示意量级与硬件和数据规模相关运维对象从一个库变成”双索引 重排服务”。还有一条硬约束重排只能重排召回到的候选召回截断就是重排的天花板——先保证 Recall50再谈精排本演示只有 6 条语料RECALL_N50 等于全库覆盖天花板咬不到人真实规模的语料上它才会露出獠牙。三个主要调优旋钮RRF 的 k、双路召回深度、重排候选数。补一句调参直觉RRF 的 k 对结果并不敏感原论文扫过多个取值60 附近都行不用花精力细调真正影响大的是召回深度和重排候选数这两个才是要拿评估集说话的地方。学术场景下的优点术语精确与语义泛化兼得重排对”字面相近但答非所问”的干扰块比如同领域、不同方法的论文段落纠偏明显进入生成环节的 Top-K 质量直接提升引用溯源的可靠性跟着水涨船高。6. 实战调优经验全文干货密度最高的一节策略选型只是上半场下面这些才是我项目里真正磨出来的经验。6.1 分块策略对学术文献最关键的一步分块质量对最终效果的影响比换模型更大。chunk 过长会导致 embedding 语义稀释过短又会丢失上下文我的做法•优先”章节感知”分块按 Abstract / Intro / Method / Experiments 的 section 边界切超长 section 再按 300–500 tokens 二次切分相邻块重叠 10–15%约 50 tokens避免关键句被拦腰截断。•chunk 头部拼”标题路径”把文本组织成论文标题 章节名 正文。同一个词在标题路径里出现BM25 更容易命中文题匹配的查询embedding 也能拿到”这段话出自哪篇论文哪一节”的上下文。•公式保留 LaTeX 源码进 chunk术语型查询能命中公式里的符号但别指望 embedding 理解公式语义图表的 caption 单独成块并关联正文中引用该图的句子“图 3 说明什么”这类查询才有救。•元数据必须齐全title / authors / year / doi / section / page 一个都不能少否则生成阶段给不出规范引用学术场景直接不及格。分块之前还有一关容易被忽略PDF 解析本身就是个坑。双栏排版抽出来的文本顺序经常是错的页眉页脚、页码、水印会混进正文直接拿去分块等于给索引投毒。我的经验是解析后先做一轮清洗去页眉页脚、修复断行、合并被拆散的段落元数据交给 GROBID 这类专门工具提取别自己写正则硬啃。6.2 Embedding 模型选择•中英混排 长文本首选 BAAI/bge-m3dense 和 sparse 一体还能直接当一路稀疏检索用和第 5 节的架构天然契合。纯英文学术语料可以试 SPECTER2它用引文关系训练对论文语义的表征更贴合学术场景。•快速验证流程固定分块方案和查询集只换模型比 Recall10别凭榜单拍脑袋——榜单上的赢家不一定懂你的语料第 4 节那个 small 模型的翻车现场就是提醒。6.3 重排模型•开源首选 bge-reranker 系列中文场景 bge-reranker-v2-m3英文场景 bge-reranker-large。重排候选数 20–50 是延迟与收益的甜点区再多收益递减明显。•什么情况不值得上重排语料小一万块以内、查询模式简单、延迟敏感——这时候 V1V2 的 RRF 融合就够了别为了架构图好看而加组件。6.4 评估先行•造 50–100 条”问题 → 应命中的 chunk”标注集从自己的真实提问里积累即可难例优先——那种”换个说法就搜不到”的查询最该进集子。指标用两个就够RecallK前 K 条里有没有命中正确 chunk和 MRR正确答案排得越靠前分越高第一名记 1 分、第二名记 1/2依此类推取平均。•每改一个变量——分块、模型、重排——都跑一遍这个小评估避免”感觉变好了”式调优。评估脚本二十行就能写完核心就是循环查询集、记录正确 chunk 的名次篇幅所限不贴正文放在文末仓库里。•说个反直觉的体会本文这份 6 条语料、3 个查询的演示其实就是”评估先行”的最小实践——没有这组固定查询V1 到 V3 的差异就只能靠嘴描述。小评估集不贵先造起来再说。7. 总结与后续方向回顾三版演进的一句话画像V1 让系统能用V2 让它听得懂人话V3 让它稳。核心教训只有一条检索质量决定 RAG 的上限而且没有单一策略通吃——BM25 的精确、向量检索的语义、重排的精排能力是在真实查询的毒打下一件件补齐的。那个口语化查询在三个版本下的命运BM25 垫底、小向量模型第五、加重排后回到第一就是这条教训最浓缩的演示。如果只能记住一件事我希望是第 6 节那句先 BM25 打底语义不够加向量质量不够上重排。别在第一天就追全套架构也别指望单一路径走到黑。这个系统还远远没到终点后续方向列出来附关键词方便大家自查•查询侧HyDE让 LLM 先生成假想答案再去检索、query 改写/扩展、多轮对话里的指代消解。•索引侧引用图谱增强GraphRAG 的思路论文天然带引用网络、用 bge-m3 的 densesparse 双输出做”单模型混合检索”省掉一路索引。•评估侧RAGAS 这类端到端评估框架把”答案对不对、引用准不准”也纳入指标。•多模态论文图表的图像 embedding让”图 3 的趋势说明什么”成为一等公民查询。文中三段代码拼起来就是一个可独立运行的完整脚本连同 requirements 和最小评估脚本一起放在我的 GitHub 仓库地址见评论区置顶。如果你也在学术语料上搞过检索欢迎在评论区分享你的踩坑——尤其是公式和图表分块这块我至今没有特别满意的方案。参考文献1.Robertson, S., Zaragoza, H. (2009). The Probabilistic Relevance Framework: BM25 and Beyond. Foundations and Trends in Information Retrieval.BM25 经典出处2.Cormack, G. V., Clarke, C. L. A., Büttcher, S. (2009). Reciprocal Rank Fusion Outperforms Condorcet and Individual Rank Learning Methods. SIGIR 2009.RRF 出处3.Nogueira, R., Cho, K. (2019). Passage Re-ranking with BERT. arXiv:1901.04085.Cross-Encoder 重排代表作4.Thakur, N., et al. (2021). BEIR: A Heterogenous Benchmark for Zero-shot Evaluation of Information Retrieval Models. NeurIPS 2021 Datasets and Benchmarks.“BM25 仍是强基线、混合检索更优”的基准依据5.Lewis, P., et al. (2020). Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks. NeurIPS 2020Karpukhin, V., et al. (2020). Dense Passage Retrieval for Open-Domain Question Answering. EMNLP 2020.演示语料中 doc3/doc4 的原始论文顺带致敬6.Luan, Y., Eisenstein, J., Toutanova, K., Collins, M. (2021). Sparse, Dense, and Attentional Representations for Text Retrieval. TACL 2021.稀疏稠密混合表示的系统分析“混合优于单路”的文献锚点

相关新闻