端到端 RAG 系统:六节课的部件,一条流水线 本节摘要:六节部件单独成立什么也不证明——分块器对语料的 recall@5 赢,系统的 recall@5 可能输,因为检索器排不了分块器吐出的东西。本节把分块器、混合检索器、查询重写器、交叉编码器重排器、答案生成器组装成一条端到端流水线,实现一个按块锚点引用每条主张、低置信则拒绝的生成器,用第 68 节评估验收,并证明分段构建在全指标上碾压单段。配套一个自终止 CLI demo:摄入固定语料、跑固定查询集、所有指标过阈值则退出码 0,这正是 CI 冒烟测试的形状。读完本节,你交付的不是一个 demo,而是一个可上线、可回归、可回归到段的系统。 对应原课程:Phase 19 · Lesson 69 · (原英文 )。
本节摘要:六节部件单独成立什么也不证明——分块器对语料的 recall@5 赢,系统的 recall@5 可能输,因为检索器排不了分块器吐出的东西。本节把分块器、混合检索器、查询重写器、交叉编码器重排器、答案生成器组装成一条端到端流水线,实现一个按块锚点引用每条主张、低置信则拒绝的生成器,用第 68 节评估验收,并证明分段构建在全指标上碾压单段。配套一个自终止 CLI demo:摄入固定语料、跑固定查询集、所有指标过阈值则退出码 0,这正是 CI 冒烟测试的形状。读完本节,你交付的不是一个 demo,而是一个可上线、可回归、可回归到段的系统。
对应原课程:Phase 19 · Lesson 69 ·
end-to-end-rag-system(原英文phases/19-capstone-projects/69-end-to-end-rag-system/docs/en.md)。本节属第 20 章「毕业项目」的进阶 RAG 赛道,是本赛道的收官。
阅读完本节,你应当能够:
六个部件单独成立什么也不证明。分块器对语料 recall@5 赢,系统 recall@5 可能输,因为检索器排不了它吐出的块;重排器在合成候选池上抬 MRR,在真实双塔候选上失败,因为双塔在重排预算处的召回太低;查询重写器在某查询上把黄金文档提上来,在下一条上崩,因为 LLM mock 返回退化的假想。
集成测试才是整条流水线端到端跑在同一固定 qrels 上、用同一指标、由一个编排文件把一切接起来。指标在集成流水线上击败每段孤立 demo 的指标,你才算证明了这套系统。
流水线是一张小图,每段是一个签名清晰的函数。
| 阶段 | 输入 | 输出 |
|---|---|---|
| 分块器 | 文档文本 | Chunk 记录列表 |
| 检索器 | 查询串 | Top-N Chunk 记录 |
| 重写器(可选) | 查询串 | 改写列表 + 假想 |
| 重排器 | 查询、候选 | 带 cross 分数的 Top-K Chunk 记录 |
| 生成器 | 查询、Top-K Chunk 记录 | 带引用的答案串 |
每段签名稳定时组合很直接。本节的 Pipeline 类持有五段与一个按序运行它们的 query 方法。每段可换:传不同分块器/检索器/重写器/重排器/生成器,流水线照跑。
生成器是末段也最易坏。本节随附确定性 mock 生成器:取 top-K 重排块;选至多两块,其文本与查询的内容 token 重叠最高;输出答案为「每块取一句」的拼接,每句后跟 [doc_id:chunk_index] 锚点;若无块重叠高于拒绝阈值,输出「我不知道」且无引用。生产换真实 LLM 调用,提示模板:
你只用下列片段回答问题。每条主张用括号里的锚点引用。 若片段不能回答问题,说「我不知道」。 问题:{query} 片段: {带锚点的枚举块} 答案:
💡 低置信拒绝路径正是要记录交叉编码器 rank-1 分数的全部理由。低于语料阈值,生成器拒绝——这是防幻觉答案的安全阀。
demo 端到端跑一切:打印一条查询的逐段分解、对四条固定 qrels 跑评估、打印指标表;若第 68 节所有指标达 demo 设的阈值,退出码 0;否则非零并点名失败指标。这正是 CI 冒烟测试的形状:离线、快、确定性;阈值在固定语料上故意收紧,六节中任一回归都让 demo 失败。
code/main.py 实现:
Chunk —— 贯穿所有段的记录(在第 64 节形状上加 chunk_index 与源 doc_id)。Chunker —— 从第 64 节选策略(默认递归分割)。HybridIndex —— 打包第 65 节的 BM25 + 稠密 + RRF。Rewriter(可选)—— 按查询长度与连词存在性从第 67 节选 HyDE、多查询、分解之一。Reranker —— 第 66 节训好的交叉编码器,配更小固定训练集以秒级收敛。Generator —— 带引用与低置信拒绝的确定性 mock 生成器。Pipeline —— 组合五段,带 query(question) 返回 Result(answer, top_k, latency_ms_per_stage)。run_demo() —— 摄入语料、跑三条固定查询、跑评估、打印结果、按阈值设退出码。class Pipeline: def __init__(self, chunker, index, rewriter, reranker, generator): self.chunker, self.index = chunker, index self.rewriter, self.reranker, self.generator = rewriter, reranker, generator def ingest(self, docs): for doc in docs: for chunk in self.chunker.split(doc.text): self.index.add(chunk, doc_id=doc.id) def query(self, question, top_n=50, top_k=5): t0 = time.perf_counter() q = self.rewriter.rewrite(question) if self.rewriter else [question] candidates = self.index.search(q, k=top_n) topk = self.reranker.rerank(question, candidates, k=top_k) score_top1 = topk[0].cross_score answer = self.generator.generate(question, topk, refuse=score_top1 < REFUSE_THRESHOLD) return Result(answer=answer, top_k=topk, latency_ms_per_stage=stage_timings)
运行:
python3 code/main.py
输出是一条打印的查询轨迹、完整评估表、最终通过/失败状态,在固定语料上返回退出码 0。
业界主流端到端 RAG 框架(LangChain RetrievalQA、LlamaIndex QueryEngine、Haystack Pipeline、Anthropic Contextual Retrieval 参考架构)都是同一形状:分块→索引→检索→重排→生成→评估。差别在于:LangChain/LlamaIndex 偏「快速接线」,把段选择交给默认;Haystack 偏「显式 DAG」,每段是节点;本节的 Pipeline 偏后者——显式段接口、每段可换、评估作门。它们的共识与本节一致:
分块器边界漂移。 若在评估 qrels 标注轮与 demo 间换了分块策略,黄金文档 id 不再对齐。在 qrels 文件里锁死分块策略,demo 含一个点名分块器的头。
重排器训练集漏进评估。 第 66 节的 14 条训练三元组含与评估查询相似的查询。生产里严格留出评估查询,demo 的评估查询故意与重排训练集不相交。
Mock 生成器掩盖幻觉风险。 Mock 不会幻觉,因为它只输出检索块里的文本。本节点出这一点,并指向生产换真实模型的路径。
无流式。 流水线在每段末返回完整答案;生产系统会流式输出生成器结果。流式超出本节范围,答案级指标对最终串工作不变。
延迟是离线的。 Mock LLM 调用是常数时间,真实 LLM 调用主导。在请求域规划延迟预算;本节逐段计时只测 CPU 工作。
Pipeline + run_demo():本节的流水线文件是第 20 章 Track F 其余课程假设的形状;后续课程会在此之上加摄入自动化、增量重索引、遥测、服务层。检索、重排、重写、评估四半在此完成。Result(answer, top_k, latency_ms_per_stage):每查询的结构化结果,既是评估输入也是可观测性事件。生产模式:
--corpus path 标志:加载真实语料,重跑评估与阈值检查。--strategy 标志:测每种策略对端到端召回的贡献。Pipeline.query() 按序运行。下一节,我们将切换到评估赛道(Track G),从「任务规格格式」开始——用一份结构化的任务规格文件,把「评什么、怎么评、阈值多少」变成可版本、可回归的工件。