在 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 生成
用 LLM 将原始查询扩展为 3-5 个子查询,覆盖不同语义视角。
Prompt 设计:
你是一个搜索查询优化助手。请将以下用户问题改写为 {N} 个不同角度的搜索查询,
用于从海事法规知识库中检索相关文档。每个查询应覆盖不同的关键词和语义角度。
要求:
1. 保持原始问题的核心意图
2. 使用不同的关键词和表达方式
3. 分别侧重不同方面(定义、条款、对比、实例等)
4. 包含原始查询本身作为第一个
用户问题: {query}
请输出 JSON 数组,每个元素是一个查询字符串:
示例:
输入: "海上交通安全法与水污染防治法在船舶管理方面的规定有何异同?"
扩展为:
[
"海上交通安全法与水污染防治法在船舶管理方面的规定有何异同",
"海上交通安全法 船舶航行安全 管理规定 具体条款",
"水污染防治法 船舶排放 防污染 管理要求",
"船舶管理 安全法 环保法 法律对比",
"海事法律 船舶监管 安全与环保 异同分析"
]
每个子查询调用 RAG v2 的 search() (BM25 + Vector 混合检索),获取 Top-K 结果。
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),但需注意:
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 的优势:
与 RAG v2 相同,调 qwen-rerank-8B 对 RRF Top-20 做精排,过滤 threshold=0.5。
根据查询复杂度动态调整:
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
| 文件 | 用途 |
|---|---|
qaagent67/scripts/search_engine_fusion.py |
RAG Fusion 引擎,包装 v2 的 search |
| 文件 | 改动 |
|---|---|
qaagent67/agent-config.yml |
新增 rag.fusion 配置段 |
qademo/app.py |
import 改为 from search_engine_fusion import search |
"""
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]
rag:
# ... 现有 v2 配置 ...
fusion:
enabled: true
defaultExpansions: 4 # 默认扩展查询数
maxExpansions: 6 # 最大扩展数
topKPerQuery: 10 # 每路检索的 Top-K
expansionModel: local # local (用 vLLM) 或 siliconflow
# Query Expansion 用本地 vLLM (免费),不用 SiliconFlow
expansionPrompt: |
你是一个搜索查询优化助手。请将以下问题改写为 {n} 个不同角度的搜索查询...
| 阶段 | 耗时 | 说明 |
|---|---|---|
| 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年船员注册人数" | 精确匹配 | 同样精确 | 数据查找不需要多视角 |
| "船员适任证书申请流程" | 找到相关条款 | 覆盖申请条件+流程+所需材料 | 扩展覆盖不同方面 |
| "琼州海峡定线制" | 找到最新版 | 找到新旧版本+变更说明 | 扩展到"修订""实施""变化" |
search_engine_fusion.pyFusion 可用? ─── Yes → 用 Fusion
│
No
│
v2 可用? ─── Yes → 用 v2 (Hybrid)
│
No
│
用 v1 (BM25 only)
每一层都是 try/except 保护,任何组件失败不影响基本功能。