一个纯 CPU、不依赖 GPU、不绑定向量库、不绑定大模型的 RAG 检索管道库。它做了一件事——让检索的每一步计算都透明可见。前言我和 RAG 黑盒的斗争做过 RAG 的人大概都经历过这种时刻你搭好了混合检索BM25 向量接上了 Cross-Encoder Rerank加了 RRF 融合再套一层 Position-Aware Blend。跑起来效果还行但当你想调参的时候——你完全不知道该调哪里。文档 A 排在第一是 BM25 拉上去的还是 Rerank 的功劳如果你把 RRF 的k从 60 改成 40排名会怎么变Blend 权重 0.75:0.25 是最优的吗你去翻 LangChain 的文档只有set_debug(True)打印的 API 调用日志。你去翻 LlamaIndexResponseSynthesizer有 trace但粒度是我调了 Rerank 这个服务不是文档 A 在 Rerank 这一步的 logit 是 3.21sigmoid 之后是 0.961。Elasticsearch 的_explainAPI 能告诉你 BM25 评分细节但只限 BM25 一个阶段看不到融合后、重排后、混合后分数的完整流转。没有一个工具能在一次查询中展示每个文档在管道每个阶段的精确分数。这是我做 RAGForge 的初衷。RAGForge 是什么一句话只做 RAG 检索管道的组装与调优不做其他。它不处理文档解析有 unstructured、docling不做文本切块有 LangChain 的 text splitter不绑定向量数据库Elasticsearch、Milvus 各有适用场景不生成最终回答那是 LLM 的事。它只关心一件事查询进来 → 最相关的文档出去。中间每一步怎么算的让你看得一清二楚。硬件要求一块 CPU 就够了Embedding 模型 multilingual-e5-small~400MBFP16Reranker 模型 bge-reranker-v2-m3~500MBint8 量化全部 ONNX Runtime CPU 推理。不需要 CUDA不需要 GPU 驱动不需要 8GB 显存。查询理解改写、分解、HyDE目前通过 LLM API 实现——这是整个管道中唯一需要网络的部分后续会支持本地小模型如 Qwen2.5-1.5B实现完全离线。X-Ray Tracing把检索管道放进 X 光机这是 RAGForge 最核心的功能也是我认为最有价值的创新。传统 trace 工具告诉你发生了什么Step: Retrieval Time: 120ms Output: 30 candidates Step: Rerank Time: 850ms Output: 30 docs rerankedX-Ray Tracing 告诉你为什么Query: 苹果手机多少钱 Total: 3544.3ms Step Time Output -------------------------------------------------------------------------------- Retrieval 1943.4ms 3 candidates Rerank 1600.8ms 3 docs reranked Blend 0.0ms 3 results blended Final Results: Top1 0.3087 苹果手机官方定价 Top2 0.2821 iPhone 15售价多少 Top3 0.0640 华为手机报价 Query: 苹果手机多少钱 Total: 3544.3ms ┌─ BM25 Retrieval ───────────────────────────────────────── │ 苹果手机官方定价 BM25 0.6 rank1 │ iPhone 15售价多少 BM25 0.5 rank2 │ 华为手机报价 BM25 0.1 rank3 └──────────────────────────────────────────────────────────── ┌─ Vector Retrieval ───────────────────────────────────────── │ 苹果手机官方定价 cos_sim0.9609 rank1 │ iPhone 15售价多少 cos_sim0.9362 rank2 │ 华为手机报价 cos_sim0.9250 rank3 └──────────────────────────────────────────────────────────── ┌─ RRF Fusion ───────────────────────────────────────── │ 苹果手机官方定价 rrf0.0645 bonus_rank10.05 → 0.1145 │ iPhone 15售价多少 rrf0.0635 bonus_rank2_30.02 → 0.0835 │ 华为手机报价 rrf0.0625 bonus_rank2_30.02 → 0.0825 └──────────────────────────────────────────────────────────── ┌─ Rerank ──────────────────────────────────────────── │ 苹果手机官方定价 logit 2.1 sigmoid0.891 Highly relevant │ iPhone 15售价多少 logit 2.0 sigmoid0.878 Highly relevant │ 华为手机报价 logit -4.7 sigmoid0.009 Low relevance └──────────────────────────────────────────────────────────── ┌─ Blend ───────────────────────────────────────────── │ 苹果手机官方定价 0.1145×0.75 0.891×0.25 0.3087 │ iPhone 15售价多少 0.0835×0.75 0.878×0.25 0.2821 │ 华为手机报价 0.0825×0.75 0.009×0.25 0.0640 └──────────────────────────────────────────────────────────── ════════════════════════════════════════════════════════════ FINAL RESULTS: #1 0.3087 苹果手机官方定价 ← FINAL #1 #2 0.2821 iPhone 15售价多少 ← FINAL #2 #3 0.0640 华为手机报价 ← FINAL #3这不是我编造的例子这是pipeline.search(query, docs, traceTrue)后调用trace.xray的真实输出。实现方式X-Ray 不是在 pipeline 外面包一层 decorator 或 middleware 能做到的。它需要在每个组件内部提供一个带详细分数的并行方法组件标准方法X-Ray 方法返回的额外信息BM25retrieve()retrieve_with_scores()每个文档的 BM25 分数Vectorretrieve()retrieve_with_scores()每个文档的 cos_simHybridretrieve_dual()retrieve_dual_with_scores()两路并行分数RRFfuse()fuse_with_details()rrf_base bonus_type bonus → finalRerankrerank()rerank_normalized()logit sigmoid relevance levelBlendblend()blend_with_details()rrf_score × w_r rerank_score × w_rr finalPipeline 层在 trace 模式下自动调用这些*_with_*方法收集所有详细分数后交给PipelineTrace.xray属性格式化输出。这意味着关闭 trace 时完全不走 X-Ray 路径零性能损耗打开 trace 时每个组件多做一次带分数的检索Rerank 复用原始结果性能损耗在可接受范围新增组件不需要改 Pipeline 代码只要实现标准方法即可X-Ray 方法是可选的Protocol 接口不继承只实现Python 生态里做可插拔组件传统做法是继承classMyRetriever(BaseRetriever):# 必须继承defretrieve(self,query,documents):...RAGForge 用typing.Protocol这是 Python 3.11 的结构化子类型structural subtypingruntime_checkableclassRetriever(Protocol):defretrieve(self,query:str,documents:list[str])-list[tuple[str,int]]:...# 你的实现 — 不需要 import 任何基类classMyRetriever:defretrieve(self,query:str,documents:list[str])-list[tuple[str,int]]:return[(doc,rank)forrank,docinenumerate(sorted_docs)]# 直接塞进管道pipelineSearchPipeline(retrieverMyRetriever())为什么这很重要零耦合你的代码不 import RAGForge 的任何东西只实现一个方法签名。想换框架你的组件零修改。mypy 友好Protocol 是静态类型系统的一部分mypy能检查你的实现是否符合接口。isinstance可用runtime_checkable让你能在运行时做isinstance(obj, Retriever)检查。Go/Rust 的理念这和 Go 的 interface、Rust 的 trait 是同一个设计哲学——“如果它能做什么它就是什么”而不是它必须是什么的子类。6 个 Protocol 精确对应检索管道中的 6 个可替换抽象Embedder → 文本转向量 Retriever → 查询 文档 → 排序列表 Reranker → 查询 文档 → 精排分数 FusionStrategy → 多个排序列表 → 融合排序列表 QueryTransform → 查询 → 改写查询/子查询列表 Judge → 查询 文档 → 相关性判断不多不少每个都对应一个明确的职责边界。Multi-Query Retrieval不是替换是加法传统的查询改写是这样的原始查询 苹果手机多少钱 → 改写为 苹果 iPhone 系列手机 价格 报价 官方售价 → 用改写后的查询去检索问题在于你丢掉了原始查询。用户的口语化表达虽然检索效果不好但它包含了用户的真实意图。改写后的查询检索效果好但它是对意图的一种解释可能丢失了某些维度。RAGForge 的 Multi-Query Retrieval 做的是加法而非替换原始查询 苹果手机多少钱 → 改写为 苹果 iPhone 系列手机 价格 报价 官方售价 → 用原始查询检索 → 结果列表 A → 用改写查询检索 → 结果列表 B → RRF 融合 A 和 B → 最终候选一行配置切换pipe_cfgPipelineConfig(query_transform_strategyQueryTransformStrategy.RETRIEVE_AND_FUSE,)也支持decompose()——多意图查询拆成多个子查询原始 N 个子查询 → (N1) 路并行检索全部 RRF 融合。一些不那么创新但很实用的东西RRF Top-Position Bonus标准 RRF 公式是1/(krank)所有排名一视同仁。但现实中排名第一的文档通常质量远高于第二。RAGForge 在 RRF 基础上加了位置奖励rank 10.05 bonusrank 2-30.02 bonus这不是学术论文级别的创新但在实际业务中这 0.05 的 bonus 经常能让真正相关的文档从第 3 名跳到第 1 名。Position-Aware Blend当 RRF 分数和 Rerank 分数需要合并时不同排名区间应该用不同的权重比排名区间检索分权重Rerank 分权重理由Top 1-30.750.25检索阶段已经筛选出了高质量候选Top 4-100.500.50中间区间两个分数都不可靠Top 110.300.70低排名位置更信任 Rerank 的精细判断这也算不上创新但它解决了一个实际问题你不可能用一组权重打天下。Adaptive Fusion给几组 (query, relevant_documents) 的反馈数据自动搜索最优的 RRF 参数k和query_weight。本质是 grid search但省去了手动试参的麻烦。和 LangChain / LlamaIndex 的关系我不用替代这个词。RAGForge 不是要替代 LangChain 或 LlamaIndex。它们是全栈框架——从文档加载到最终生成什么都管。RAGForge 是专业工具——只管检索管道这一段。你可以这样用# LangChain 负责文档加载、切块、LLM 生成# RAGForge 负责检索管道fromlangchain.document_loadersimportPyPDFLoaderfromlangchain.text_splitterimportRecursiveCharacterTextSplitterfromrag_forgeimportSearchPipeline,FastembedEmbedder,...# 用 LangChain 加载和切块docsPyPDFLoader(paper.pdf).load()chunksRecursiveCharacterTextSplitter().split_documents(docs)texts[chunk.page_contentforchunkinchunks]# 用 RAGForge 做检索带 X-Ray 调试results,tracepipeline.search(query,texts,traceTrue)print(trace.xray)# 调参利器# 用 LangChain 的 LLM 生成最终回答# ...各管各的互相补充。架构一览query ──► [Query Transform] ──► [Retriever] ──► [Fusion] ──► [Rerank] ──► [Blend] ──► results optional always if hybrid optional optionalMulti-Query 模式query ──► [Transform] ─┬──► [检索: 原始查询] ──┐ ├──► [检索: 改写查询] ──┼──► [RRF 融合] ──► [Rerank] ──► [Blend] ──► results └──► [检索: 子查询3] ──┘rag_forge/ ├── protocols.py # 6 个 Protocol 接口 ├── pipeline.py # SearchPipeline组装一切 ├── types.py # 数据类型 X-Ray 格式化 ├── config/ # 配置 ├── models/ # ONNX CPU 推理Embedding Reranker ├── retrieval/ # BM25 / Vector / Hybrid ├── fusion/ # RRF / Blend / Adaptive ├── query/ # QueryPlanner改写/分解/HyDE/扩展 ├── evaluation/ # Judge EvaluatorNDCG/Recall/MRR ├── cache/ # 语义缓存 ├── dedup/ # 文档去重 ├── tracing/ # 追踪器 └── profiler.py # 性能分析30 秒上手安装pip install ragforge-sdkfromrag_forgeimportSearchPipeline,BM25Retriever pipelineSearchPipeline(retrieverBM25Retriever())resultspipeline.search(苹果手机,[iPhone 15 价格,华为手机])完整——Hybrid Rerank X-Rayfromrag_forgeimportSearchPipeline,FastembedEmbedder,FastembedReranker,ModelConfig pipelineSearchPipeline(embedderFastembedEmbedder(ModelConfig()),rerankerFastembedReranker(ModelConfig()),)results,tracepipeline.search(苹果手机多少钱,documents,traceTrue)print(trace.xray)写在最后RAG 领域不缺框架。LangChain、LlamaIndex、Haystack、Semantic Kernel……每一个都能做 RAG。但 RAG 检索管道的调参体验几乎没有框架认真对待过。你调了k60改成k40效果变好了还是变差了你加了 RerankTop-10 的变化是什么Blend 权重从 0.7:0.3 改成 0.5:0.5哪些文档受益了、哪些文档受害了这些问题X-Ray Tracing 能回答。而在此之前你只能猜。RAGForge 的目标是成为你在调检索管道参数时第一个打开的工具。 安装pip install ragforge-sdk 项目地址https://github.com/jiangnanboy/RAGForge