# RAG v2: BM25 + Embedding + Rerank 混合检索方案 ## Context 当前 RAG 系统使用 BM25 关键词检索,存在核心缺陷:搜索"海上交通安全法与水污染防治法"时检索到行政检查清单而非法律原文,因为 BM25 只看关键词共现频率,无法理解语义。用户提议引入 qwen-embedding-v4 + qwen-rerank-8B + RRF 混合检索,预期显著提升检索准确性。 ## 方案概览 ``` 查询 ├─► BM25 检索 (现有 search_engine.py) → 排名列表 A ├─► 向量检索 (qwen-embedding-v4) → 排名列表 B └─► RRF 融合 (k=60) → top-20 候选 └─► Rerank (qwen-rerank-8B) → 过滤 threshold=0.5 → top-5 结果 ``` ## 新增/修改文件清单 ### 新增 3 个文件 | 文件 | 用途 | |------|------| | `qaagent67/scripts/search_engine_v2.py` | 混合检索引擎 (BM25+向量+RRF+Rerank) | | `qaagent67/scripts/build_vector_index.py` | 向量索引构建 (重新分块+调 embedding API) | | `qaagent67/scripts/rebuild_index_v2.py` | BM25 索引重建 (适配新分块参数) | ### 修改 2 个文件 | 文件 | 改动 | |------|------| | `qaagent67/agent-config.yml` | 新增 `rag.hybrid/embedding/rerank` 配置段 | | `qademo/app.py` | 改 1 行 import: `from search_engine` → `from search_engine_v2` | ### 不动的文件 (保留) | 文件 | 说明 | |------|------| | `qaagent67/scripts/search_engine.py` | 现有 BM25 引擎,被 v2 内部调用 | | `qaagent67/scripts/rebuild_index.py` | 现有索引构建,保留为参考 | | `knowledge/maritime/rag_index.pkl` | 现有 BM25 索引,保留为 fallback | | `knowledge/maritime/rag_keywords.pkl` | 现有关键词索引,保留为 fallback | ## 详细设计 ### 1. build_vector_index.py — 向量索引构建 **分块参数:** - 分段符: `\n\n` - 最大长度: 1024 token (≈1024 中文字符) - 重叠: 200 token - 保留表格完整性 (`[TABLE_START]...[TABLE_END]`) **流程:** 1. 读 processed/*.txt → 用新参数分块 → ~50-60K chunks 2. 每 16 个 chunk 一批调 SiliconFlow embedding API 3. 保存: `rag_vectors_v2.npy` (numpy float32 [N, dim]) + `rag_vectors_meta_v2.pkl` 4. 同时构建新 BM25 索引: `rag_index_v2.pkl` + `rag_keywords_v2.pkl` 5. 支持断点续传 (checkpoint 每 500 批) **API 调用:** ``` POST https://api.siliconflow.cn/v1/embeddings {"model": "Pro/Qwen/Qwen3-Embedding-0.6B", "input": ["text1", ...], "encoding_format": "float"} ``` **预估:** - ~55K chunks × 500 tokens = 27.5M tokens ≈ $0.30 一次性成本 - 3,437 批 × 0.1s = ~6 分钟 ### 2. search_engine_v2.py — 混合检索引擎 **核心函数:** ```python def search(query, top_k=5): # 1. BM25 检索 (调现有 search_engine.search) bm25_results = bm25_search(query, top_k=20) # 2. 向量检索 (embed query → cosine similarity) query_vec = _embed_query(query) vector_results = _vector_search(query_vec, top_k=20) # 3. RRF 融合 fused = _rrf_fuse(bm25_results, vector_results, k=60) # top-20 # 4. Rerank reranked = _rerank(query, fused[:20]) # 5. 过滤 threshold + 返回 top-k return [r for r in reranked if r['score'] >= 0.5][:top_k] ``` **降级策略:** - 向量索引不存在 → 纯 BM25 - Embedding API 超时 → 纯 BM25 - Rerank API 超时 → 跳过 rerank,直接用 RRF 结果 **返回格式:** 与现有 `search()` 完全一致: `{source, chunk_id, score, text}` ### 3. agent-config.yml 新增配置 ```yaml rag: enabled: true collection: maritime_rag chunkSize: 1024 chunkOverlap: 200 chunkSeparator: "\n\n" topK: 5 hybrid: enabled: true rrfK: 60 rerankCandidates: 20 similarityThreshold: 0.5 embedding: model: Pro/Qwen/Qwen3-Embedding-0.6B baseUrl: https://api.siliconflow.cn/v1 apiKeyEnv: SILICONFLOW_API_KEY batchSize: 16 rerank: model: Pro/Qwen/Qwen3-Reranker-0.6B baseUrl: https://api.siliconflow.cn/v1 apiKeyEnv: SILICONFLOW_API_KEY topN: 5 ``` ### 4. app.py 改动 (1 行) ```python # Before: from search_engine import search # After: try: from search_engine_v2 import search except ImportError: from search_engine import search ``` ### 5. 存储格式 | 文件 | 格式 | 大小估算 | |------|------|---------| | `rag_vectors_v2.npy` | numpy float32 [N, dim] | ~225 MB (55K×1024×4) | | `rag_vectors_meta_v2.pkl` | pickle list of (source, chunk_id, text) | ~50 MB | | `rag_index_v2.pkl` | pickle BM25 索引 | ~30 MB | | `rag_keywords_v2.pkl` | pickle 关键词倒排 | ~20 MB | **不依赖向量数据库** — numpy brute-force cosine similarity 对 55K chunks 只需 <100ms。 ### 6. 依赖 仅新增 `numpy`,已在 Docker 镜像的 pip install 中。无需 chromadb/faiss 等。 ### 7. Docker 部署 ```bash # 构建索引 (一次性) docker exec qademo python3 /app/agentexample/qaagent67/scripts/build_vector_index.py # 重启 Web 服务 (自动使用 v2) docker restart qademo ``` 环境变量: `SILICONFLOW_API_KEY=` (申请: https://cloud.siliconflow.cn/account/ak ) ## 影响分析 | 方面 | 影响 | |------|------| | 现有 BM25 代码 | 不动,被 v2 内部调用 | | Web 前端 | 无改动 | | Wiki 模式 | 不受影响 | | 查询延迟 | +200-500ms (embed+rerank API) | | 首次加载 | ~2s (numpy mmap) vs 6s (pickle BM25) | | 索引构建 | 一次性 ~6 分钟 + ~$0.30 | | 每次查询成本 | ~$0.001 (1次embed + 1次rerank) | ## 验证方式 1. **单元测试:** `python search_engine_v2.py "海上交通安全法与水污染防治法"` — 应检索到法律原文 2. **对比测试:** 同一问题 v1 vs v2 结果对比 3. **Web 测试:** 在 http://qa.lambdagent.cn:8080 提问验证 4. **降级测试:** 停掉 SiliconFlow API,验证 fallback 到 BM25 ## 实施顺序 1. 先验证 SiliconFlow embedding/rerank API 可用性 (curl 测试) 2. 更新 agent-config.yml 3. 编写 build_vector_index.py + 构建索引 4. 编写 search_engine_v2.py 5. 修改 app.py (1 行) 6. 部署到 Docker + 验证