# RAG Fusion 方案设计 ## Context 在 RAG v2 (BM25 + Embedding + Rerank) 基础上,进一步引入 RAG Fusion 机制:用 LLM 将用户原始 Query 扩展为多个语义等价/互补的子查询,并行检索后通过 RRF 融合排序,显著提升召回覆盖度和鲁棒性。 与 RAG v2 的关系:RAG Fusion 是 RAG v2 的**上层增强**,不替换 v2,而是在 v2 的混合检索之上叠加 Query Expansion。 ## 架构对比 ``` RAG v1 (当前): Query → BM25 → Top-5 → LLM RAG v2 (混合检索): Query → BM25 + Vector → RRF → Rerank → Top-5 → LLM RAG Fusion (本方案): Query → LLM Expansion → N 个子查询 ├─► Q1 → BM25 + Vector → Top-K1 ├─► Q2 → BM25 + Vector → Top-K2 ├─► ... └─► QN → BM25 + Vector → Top-KN └─► RRF 融合 N 路结果 → Top-20 └─► Rerank → Top-5 → LLM 生成 ``` ## 核心组件 ### 1. Query Expansion (LLM 查询扩展) 用 LLM 将原始查询扩展为 3-5 个子查询,覆盖不同语义视角。 **Prompt 设计:** ``` 你是一个搜索查询优化助手。请将以下用户问题改写为 {N} 个不同角度的搜索查询, 用于从海事法规知识库中检索相关文档。每个查询应覆盖不同的关键词和语义角度。 要求: 1. 保持原始问题的核心意图 2. 使用不同的关键词和表达方式 3. 分别侧重不同方面(定义、条款、对比、实例等) 4. 包含原始查询本身作为第一个 用户问题: {query} 请输出 JSON 数组,每个元素是一个查询字符串: ``` **示例:** ``` 输入: "海上交通安全法与水污染防治法在船舶管理方面的规定有何异同?" 扩展为: [ "海上交通安全法与水污染防治法在船舶管理方面的规定有何异同", "海上交通安全法 船舶航行安全 管理规定 具体条款", "水污染防治法 船舶排放 防污染 管理要求", "船舶管理 安全法 环保法 法律对比", "海事法律 船舶监管 安全与环保 异同分析" ] ``` ### 2. 多路并行检索 每个子查询调用 RAG v2 的 `search()` (BM25 + Vector 混合检索),获取 Top-K 结果。 ```python def multi_search(queries, top_k_per_query=10): all_results = [] for q in queries: results = hybrid_search(q, top_k=top_k_per_query) # RAG v2 的 search all_results.append(results) return all_results ``` **优化**: 可以并行化(ThreadPoolExecutor),但需注意: - BM25 是本地计算,可并行 - Vector 检索是本地 numpy 计算,可并行 - Embedding API 调用,可批量合并(N 个 query 一次 API 调用) ### 3. RRF 融合 (多路结果合并) ```python def rrf_fuse_multi(all_results, k=60): scores = defaultdict(float) chunk_map = {} for results in all_results: for rank, r in enumerate(results): doc_id = f"{r['source']}#{r['chunk_id']}" scores[doc_id] += 1.0 / (k + rank + 1) chunk_map[doc_id] = r # 保留 chunk 数据 # 按融合分数排序 sorted_ids = sorted(scores.items(), key=lambda x: -x[1]) return [chunk_map[did] | {'fusion_score': score} for did, score in sorted_ids] ``` **RRF 的优势**: - "多次出现的文档"得分更高 → 高置信度 - 不依赖原始分数的可比性(BM25 分数 vs 余弦相似度) - 只用排名,天然归一化 ### 4. Rerank (精排) 与 RAG v2 相同,调 qwen-rerank-8B 对 RRF Top-20 做精排,过滤 threshold=0.5。 ### 5. 动态策略 根据查询复杂度动态调整: ```python def determine_expansion_count(query): """简单问题少扩展,复杂问题多扩展""" # 简单启发式: # - 包含对比词(异同、比较、区别)→ 5 个 # - 包含多个实体 → 4 个 # - 普通事实查询 → 3 个 compare_words = ['异同', '比较', '区别', '对比', '不同', '相同'] if any(w in query for w in compare_words): return 5 if len(query) > 30: return 4 return 3 ``` ## 新增/修改文件 ### 新增 1 个文件 | 文件 | 用途 | |------|------| | `qaagent67/scripts/search_engine_fusion.py` | RAG Fusion 引擎,包装 v2 的 search | ### 修改 2 个文件 | 文件 | 改动 | |------|------| | `qaagent67/agent-config.yml` | 新增 `rag.fusion` 配置段 | | `qademo/app.py` | import 改为 `from search_engine_fusion import search` | ## search_engine_fusion.py 核心设计 ```python """ RAG Fusion: Multi-query expansion + parallel hybrid retrieval + RRF fusion + rerank Wraps search_engine_v2.search() as the base retrieval unit. """ # 降级链: fusion → v2 (hybrid) → v1 (BM25) try: from search_engine_v2 import search as hybrid_search except ImportError: from search_engine import search as hybrid_search def _expand_query(query, n=4): """用 LLM 扩展查询为 N 个子查询""" prompt = f"""...""" # 调 vLLM 本地 (免费) 或 SiliconFlow result = llm_call(prompt) queries = json.loads(result) return [query] + queries[:n-1] # 原始查询始终保留 def _rrf_fuse_multi(all_results, k=60): """RRF 融合多路检索结果""" ... def search(query, top_k=5): """RAG Fusion 主入口 (drop-in replacement)""" # 1. Query Expansion n = determine_expansion_count(query) queries = _expand_query(query, n=n) # 2. 多路检索 all_results = [hybrid_search(q, top_k=10) for q in queries] # 3. RRF 融合 fused = _rrf_fuse_multi(all_results, k=60) # 4. 去重 + 取 Top-20 seen = set() unique = [] for r in fused: key = f"{r['source']}#{r['chunk_id']}" if key not in seen: seen.add(key) unique.append(r) # 5. Rerank (复用 v2 的 rerank) reranked = _rerank(query, unique[:20]) # 用原始 query rerank # 6. 过滤 + 返回 return [r for r in reranked if r['score'] >= threshold][:top_k] ``` ## agent-config.yml 新增配置 ```yaml rag: # ... 现有 v2 配置 ... fusion: enabled: true defaultExpansions: 4 # 默认扩展查询数 maxExpansions: 6 # 最大扩展数 topKPerQuery: 10 # 每路检索的 Top-K expansionModel: local # local (用 vLLM) 或 siliconflow # Query Expansion 用本地 vLLM (免费),不用 SiliconFlow expansionPrompt: | 你是一个搜索查询优化助手。请将以下问题改写为 {n} 个不同角度的搜索查询... ``` ## 性能与成本分析 ### 延迟分解 (以 4 路查询为例) | 阶段 | 耗时 | 说明 | |------|------|------| | Query Expansion | ~2s | 本地 vLLM 生成 4 个子查询 | | BM25 × 4 | <100ms | 本地计算,可并行 | | Embedding × 4 | ~200ms | 批量调 API (4 个 query 一次) | | Vector Search × 4 | <100ms | numpy 计算 | | RRF Fusion | <10ms | 纯排名合并 | | Rerank | ~200ms | API 调用 | | **总检索** | **~2.5s** | | | LLM 生成 | ~10s | vLLM NPU | | **端到端** | **~12.5s** | vs 当前 ~12s,增加 <1s | ### 成本 | 项目 | 每次查询 | |------|---------| | Query Expansion | 免费 (本地 vLLM) | | Embedding (4 queries) | ~$0.0001 | | Rerank | ~$0.0005 | | **总计** | ~$0.0006/查询 | ### 与各方案对比 | 方案 | 检索准确性 | 延迟 | 成本/查询 | 复杂度 | |------|-----------|------|----------|--------| | v1 BM25 | ⭐⭐ | 12s | $0 | 低 | | v2 Hybrid | ⭐⭐⭐⭐ | 12.3s | $0.001 | 中 | | **Fusion** | **⭐⭐⭐⭐⭐** | **12.5s** | **$0.0006** | **中高** | **关键发现**: Query Expansion 用本地 vLLM (NPU,~2s),几乎不增加总延迟,但显著提升召回。 ## 针对海事知识库的预期提升 | 查询类型 | v2 Hybrid | Fusion | 原因 | |---------|-----------|--------|------| | "海上交通安全法与水污染防治法异同" | 检索到法律原文 | 更全面覆盖两部法律的多个章节 | 扩展为分别搜索两部法律 | | "2019年船员注册人数" | 精确匹配 | 同样精确 | 数据查找不需要多视角 | | "船员适任证书申请流程" | 找到相关条款 | 覆盖申请条件+流程+所需材料 | 扩展覆盖不同方面 | | "琼州海峡定线制" | 找到最新版 | 找到新旧版本+变更说明 | 扩展到"修订""实施""变化" | ## 实施顺序 1. 先完成 RAG v2 (Embedding + Rerank) 部署 2. 在 v2 基础上叠加 Fusion: - 编写 `search_engine_fusion.py` - 更新 config - 改 app.py import (1 行) 3. A/B 测试: v1 vs v2 vs Fusion 三方对比 ## 降级策略 ``` Fusion 可用? ─── Yes → 用 Fusion │ No │ v2 可用? ─── Yes → 用 v2 (Hybrid) │ No │ 用 v1 (BM25 only) ``` 每一层都是 try/except 保护,任何组件失败不影响基本功能。