feat(rag): v1 rerank 精排 + bge-m3 多语言切换(T6/T11 架构审查整改)

- T6 (Issue6): RerankConfig(enabled/model=BAAI/bge-reranker-v2-m3/device)
  - rag-layer-design §6.3 Rerank 精排:窗口=RRF top-10、候选≤top_k 跳过、
    故障降级 RRF 原序;原 §6.3-6.6 顺延 6.4-6.7
  - config-design §5 新增 rerank 段;design §5.5 检索策略加 rerank
- T11 (OV2): EmbeddingConfig.model 默认 bge-small-zh-v1.5 → BAAI/bge-m3(日文语料)
  - rag-layer-design 选型表/依赖表/manifest/流程图同步 + 新增 §2.3 日文样本验证
  - design.md / implementation-plan 4.3 / config-design embedding 同步
- 新增 test_rag_design_consistency.py 一致性门禁(6 用例防文档漂移)
- TDD: RED(默认模型仍旧 + rerank 字段不存在)→ GREEN → 全量 198 passed / 100.00%(996 stmts/252 br)
This commit is contained in:
lhl
2026-08-12 11:32:28 +08:00
parent c170ec2edd
commit 3decdfc31c
9 changed files with 186 additions and 24 deletions
+1
View File
@@ -77,3 +77,4 @@
| 2026-08-11 | Agent 实现 | T8(架构审查整改):LLM 客户端全异步化(Issue9)。client.py 由同步 httpx.Client 全异步化:LLMClient Protocol chat → async defHttpLLMClient 用 httpx.AsyncClient + asyncio.sleep 退避(消除 time.sleep 阻塞 asyncio 任务池);__enter__/__exit__ → __aenter__/__aexit__async with 生命周期闭环);engine.py chat/chat_structured/_call 全部 async + awaitFakeLLMClient.chat → async;测试基建用 anyio pytest 插件(@pytest.mark.anyio);test_inference_engine.py 32 用例脚本批量转换 async + NotConfiguredClient 同步 client 转 asynctest_inference_client.py 10 用例转 async;同步 inference-engine-design spec 与 milestone3-inference-review httpx 描述(防文档漂移);TDD 验证 REDasync 接口缺失 TypeError)→ GREEN(聚焦 67 passed)→ 全量 182 passed 覆盖 100.00%987 stmts/252 br),fail_under=99 达标 | src/genesis/inference/client.py, src/genesis/inference/engine.py, tests/test_inference_client.py, tests/test_inference_engine.py, tests/inference_helpers.py, docs/superpowers/specs/2026-08-09-inference-engine-design.md, docs/milestone3-inference-review.md, _AI_USAGE_LOG.md | deepseek-v4-flash-free |
| 2026-08-11 | Agent 实现 | T9(架构审查整改):CJK 保守 token 估算(Issue11)。token.py approximate_token_count 重写:新增 _is_cjk_charCJK 统一表意/扩展A/假名/韩文/兼容/全角六大 Unicode 范围)+ _CJK_TOKENS_PER_CHAR=1.5(旧逻辑 4 字符 1 token 对中文/日文严重低估,裁剪失效致 API 超限);CJK 字符按 1.5 token/字符,其余仍 4 字符 1 token,最少 1 token;同步 config-design.md token_estimation 注释;新增 4 用例(纯 CJK 保守/ASCII 不回归/混合文本/全角符号);TDD 验证 RED((設計)4 字符仅 1 token)→ GREEN(聚焦 11 passed)→ 全量 186 passed 覆盖 100.00%995 stmts/252 br),fail_under=99 达标 | src/genesis/inference/token.py, tests/test_inference_token.py, docs/config-design.md, _AI_USAGE_LOG.md | deepseek-v4-flash-free |
| 2026-08-11 | Agent 实现 | T5(架构审查整改):删死配置 + 同步文档(Issue5 + OV1)。config.py 删除 QdrantStoreConfig 类与 VectorStoreConfig.qdrant 字段、AppConfig.task_queue.redis_url;同步更新 6 处文档(api-design §1/§4.3/§5.2/§5.3/§6.2TaskQueue 标注 v1 仅 InMemory、Redis/Valkey 为 v2 预留;rag-layer §9Storage Adapter 仅 ChromaAdapter、移除切换流程/工厂 qdrant 分支;agent-runtime §3.1/§3.5design §5.5/§8.4.1config-design env/app.yaml/rag.yaml/docker composeweb-ui §4.1+ tests/fixtures/rag.yaml 去 qdrant 段;历史评审记录(design-review/web-ui-review/phase1 plan)保留原样不改写;新增 3 用例(QdrantStoreConfig 已删/vector_store 无 qdrant 字段/task_queue 无 redis_url+ 同步 2 个既有 qdrant 依赖用例;TDD 验证 RED(三处死配置存在)→ GREEN(聚焦 10 passed)→ 全量 189 passed 覆盖 100.00%991 stmts/252 br),fail_under=99 达标 | src/genesis/config.py, tests/test_config.py, tests/fixtures/rag.yaml, docs/api-design.md, docs/rag-layer-design.md, docs/agent-runtime-design.md, docs/design.md, docs/config-design.md, docs/web-ui-design.md, _AI_USAGE_LOG.md | deepseek-v4-flash-free |
| 2026-08-11 | Agent 实现 | T6+T11(架构审查整改,Lane B):v1 rerank 精排 + bge-m3 多语言切换(Issue6 + OV2)。config.py 新增 RerankConfigenabled=True/model=BAAI/bge-reranker-v2-m3/device=cpu)挂入 RagConfigEmbeddingConfig.model 默认 bge-small-zh-v1.5 → BAAI/bge-m3(实际语料日文);rag-layer-design.md 新增 §2.3 多语言与日文样本验证、§6.3 Rerank 精排(窗口=RRF top-10、候选≤top_k 跳过、故障降级 RRF 原序),原 §6.3-6.6 顺延 6.4-6.7;选型表/依赖表/manifest/流程图 bge-small-zh → bge-m3config-design.md embedding 默认 + 新增 rerank 段;design.md §5.5 与 implementation-plan 4.3 同步;新增 tests/test_rag_design_consistency.py 一致性门禁(6 用例:代码默认/fixture 同步/4 文档用 bge-m3+reranker/无 legacy 引用);TDD 验证 RED(默认模型仍旧+rerank 字段不存在)→ GREEN(聚焦 13 passed)→ 全量 198 passed 覆盖 100.00%996 stmts/252 br),fail_under=99 达标 | src/genesis/config.py, tests/test_config.py, tests/test_rag_design_consistency.py, tests/fixtures/rag.yaml, docs/rag-layer-design.md, docs/config-design.md, docs/design.md, docs/implementation-plan.md, _AI_USAGE_LOG.md | deepseek-v4-flash-free |
+10 -5
View File
@@ -156,10 +156,10 @@ prompt_registry:
```yaml
# config/rag.yaml
embedding:
model: BAAI/bge-small-zh-v1.5 # 可切 BAAI/bge-m3
device: cpu # cpu | cuda
max_batch_size: 32 # 编码批大小
cache_dir: /data/shared/models # 模型缓存目录
model: BAAI/bge-m3 # 多语言(中/日/英),适配日文语料(OV2/T11)
device: cpu # cpu | cuda
max_batch_size: 32 # 编码批大小
cache_dir: /data/shared/models # 模型缓存目录
vector_store: # StorageAdapter 配置(rag-layer §9.4
adapter: chroma # v1 仅 chromaqdrant 为 v2 预留)
@@ -176,7 +176,12 @@ retrieval:
channel_top_k: 10 # 双通道各取 top-10
rrf_k: 60 # RRF 融合常数(rag-layer §6.2
default_top_k: 5 # 融合后默认返回数
contextual_enrichment: true # 上下文增强开关(§6.3
contextual_enrichment: true # 上下文增强开关(§6.4
rerank: # Rerank 精排(rag-layer §6.3v1 新增 I6/T6
enabled: true # false 时跳过精排,直接取 RRF 前 top_k
model: BAAI/bge-reranker-v2-m3 # 多语言 Cross-Encoder
device: cpu # cpu | cuda
```
---
+2 -2
View File
@@ -708,10 +708,10 @@ Parser 处理时分为:
| 设计点 | 决策 |
|--------|------|
| 向量数据库 | Chromav1 唯一;Qdrant 切换为 v2 预留,Scope 缩减裁定)|
| Embedding | bge-small-zh-v1.5(本地),可切换 bge-m3 |
| Embedding | bge-m3(多语言,适配日文语料;OV2/T11)|
| 实现方式 | 手写(chromadb + rank_bm25 + sentence-transformers|
| 分割策略 | 按格式适配(Word 标题层级 / Excel 规则块 / PPT 1-2 页)|
| 检索策略 | 双通道(向量 top-10 + BM25 top-10+ RRF 融合 |
| 检索策略 | 双通道(向量 top-10 + BM25 top-10+ RRF 融合 + rerank 精排(bge-reranker-v2-m3|
| 分类存储 | 分 Collection 隔离(rules-write / rules-design / ref-docs|
| 版本管理 | 文档级增量 + 版本组合(hash 对比,只重建变化文档)|
| 版本路由 | 会话开始锁版本 |
+1 -1
View File
@@ -122,7 +122,7 @@ Wordテンプレート・ルール文書・PPTルール文書・現行システ
|---|-------|------|
| 4.1 | ルール文書のインデックス化 | 分類済みルールの分割(Word/Excel/PPT フォーマット適応)→ Embedding → ベクトルストア構築(詳細: docs/rag-layer-design.md §3, §4 |
| 4.2 | StorageAdapter 実装 | VectorStoreAdapter 抽象+ChromaAdapterMockAdapter(詳細: docs/rag-layer-design.md §9 |
| 4.3 | ハイブリッド検索API | ベクトル(bge-small-zh-v1.5)+BM25 双チャネル+RRF 融合の実装(詳細: docs/rag-layer-design.md §6 |
| 4.3 | ハイブリッド検索API | ベクトル(bge-m3,日文対応)+BM25 双チャネル+RRF 融合rerank 精排(bge-reranker-v2-m3の実装(詳細: docs/rag-layer-design.md §6 |
| 4.4 | ルールハンドブックのバージョン管理 | ドキュメント級インクリメンタル更新(hash比較)+manifest+セッションロックバージョン(詳細: docs/rag-layer-design.md §5 |
| 4.5 | ルール衝突検出・ユーザー確認 | ConflictDetector+ユーザー確認フロー+意思決定記録(詳細: docs/rag-layer-design.md §7 |
| 4.6 | 「ルールを更新」UI連携 | Web UI からの更新トリガー → 再インデックス化 |
+47 -10
View File
@@ -50,7 +50,8 @@ RAG 层存储三类内容,分类在 Parser 解析时完成:
| 选型点 | 决策 | 说明 |
|--------|------|------|
| 向量数据库 | **Chromav1 唯一)** | 初期用 Chroma(轻量本地,嵌入进程,Docker 部署最简);通过 Storage Adapter 抽象保留接口,Qdrant 切换为 v2 预留(Scope 缩减裁定,T5 整改)|
| Embedding 模型 | **bge-small-zh-v1.5**(本地运行) | 中文小模型(1024维,~100MB),CPU 可跑;统一配置 `config/rag.yaml``embedding.model`(见 docs/config-design.md §5可切换为 `BAAI/bge-m3`(多语言更好) |
| Embedding 模型 | **bge-m3**(本地运行) | 多语言模型(中/日/英),适配实际日文语料(OV2/T11);统一配置 `config/rag.yaml``embedding.model`(见 docs/config-design.md §5;稠密向量 1024 维,CPU 可跑 |
| Rerank 模型 | **bge-reranker-v2-m3**(v1 引入,I6/T6) | 精排阶段使用;统一配置 `config/rag.yaml``rerank.model`;默认启用,可通过 `rerank.enabled=false` 关闭降级为纯 RRF 结果 |
| 实现方式 | **手写实现**(不引入 LangChain/LlamaIndex | chromadb + rank_bm25 + sentence-transformers 直接实现;依赖轻、可控性强、与现有轻量技术栈一致 |
### 2.1 为什么手写而非框架
@@ -68,11 +69,22 @@ RAG 层存储三类内容,分类在 Parser 解析时完成:
```
chromadb # 向量存储(Chroma
sentence-transformers # Embedding 编码(bge-small-zh-v1.5
sentence-transformers # Embedding 编码(bge-m3+ Rerank 精排(bge-reranker-v2-m3
rank_bm25 # BM25 关键词检索
pydantic # 数据模型(RuleChunk 等)
```
### 2.3 多语言与日文样本验证(OV2/T11)
实际规则语料为**日文**(要件定义/概要设计/记入规则),因此:
- Embedding 采用多语言模型 **bge-m3**(支持中/日/英,稠密向量 1024 维)
- Rerank 采用多语言精排模型 **bge-reranker-v2-m3**Cross-Encoderquery×chunk 打分)
- **日文样本验证**(Phase4 实施时执行):用真实日文规则片段(如「見出しは「1.1」形式で記述する」)构造检索用例,断言:
1. 语义近义日文 query 能召回对应规则(top-5 命中)
2. rerank 后相关片段排位不低于 RRF 原始排位(不劣化)
3. 日英混合/片假名术语 query 不空召回
---
## 3. 文档分割策略
@@ -181,8 +193,8 @@ Chunking Pipeline
{"name": "设计方针.docx", "hash": "sha256:...", "type": "design", "built": "reuse_from_v2"}
],
"collections": {
"rules-write": {"chunk_count": 130, "embedding_model": "bge-small-zh-v1.5"},
"rules-design": {"chunk_count": 45, "embedding_model": "bge-small-zh-v1.5"}
"rules-write": {"chunk_count": 130, "embedding_model": "bge-m3", "rerank_model": "bge-reranker-v2-m3"},
"rules-design": {"chunk_count": 45, "embedding_model": "bge-m3", "rerank_model": "bge-reranker-v2-m3"}
},
"active_previous": "v2"
}
@@ -296,12 +308,15 @@ v3 会复制未变化文档的 embeddings。对规则文档几 MB 的量级,
│ │
│ 双通道检索: │
│ ├── VectorRetriever(向量语义检索 top-10
│ │ 使用 bge-small-zh 编码查询
│ │ 使用 bge-m3 编码查询
│ └── BM25Retriever(关键词检索 top-10
│ │
│ RRF Fusion(结果融合) │
│ └── 合并两通道结果,按 RRF 公式排序 │
└── 输出 top-N 个 RuleChunk
│ Rerank 精排(v1 新增,I6/T6
│ └── bge-reranker-v2-m3 对候选重打分 │
│ └── 输出 top-N 个 RuleChunk │
└──────────────────────────────────────────┘
```
@@ -320,7 +335,29 @@ RRF_score(d) = Σ 1 / (k + rank(d)) # k=60 常用值
融合后 top-k = 调用方传参(默认 5)
```
### 6.3 上下文增强(Contextual Enrichment
### 6.3 Rerank 精排(v1 新增,I6/T6
RRF 融合后、注入 LLM prompt 前,对候选做 **Cross-Encoder 精排**2026 主流实践:向量→rerank→精排):
```
RRF 候选(默认 top-10 窗口)
bge-reranker-v2-m3Cross-Encoder
│ 对每对 (query, chunk) 打分,输出相关性分数
按分数降序重排 → 截取最终 top_k(默认 5)
```
| 项目 | 决策 |
|------|------|
| 模型 | `BAAI/bge-reranker-v2-m3`(多语言 Cross-Encoder,与日文语料匹配) |
| 开关 | `rerank.enabled`(默认 true);false 时跳过精排,直接取 RRF 前 top_k |
| 打分窗口 | RRF 融合后 top-10 候选(与通道 top-k 一致,避免重排成本随全文增长) |
| 候选过少 | 候选 ≤ top_k 时跳过 rerank(无重排必要,节省推理) |
| 故障降级 | rerank 推理异常 → 降级为 RRF 原始顺序,不阻断检索(同 §10 容错策略) |
### 6.4 上下文增强(Contextual Enrichment
检索时不仅用 query 文本,还拼入当前上下文:
@@ -330,7 +367,7 @@ RRF_score(d) = Σ 1 / (k + rank(d)) # k=60 常用值
| Impact | 「要素 F001 与 TB001 的关系」推理 → query + 要素类型 + 要素描述 |
| QA | 「校验某段内容的规则遵守」→ query + 待校验段落 |
### 6.4 各 Agent 查询构造规范
### 6.5 各 Agent 查询构造规范
| 调用方 | query 构造方式 |
|--------|--------------|
@@ -338,7 +375,7 @@ RRF_score(d) = Σ 1 / (k + rank(d)) # k=60 常用值
| Impact | query = 要素类型 + 要素描述 + 待推论的关联方向 |
| QA | query = 待校验段落内容 + 所属章 |
### 6.5 top_k 配置
### 6.6 top_k 配置
`top_k` 由调用方传参,允许按章差异配置:
@@ -348,7 +385,7 @@ RRF_score(d) = Σ 1 / (k + rank(d)) # k=60 常用值
| DB 设计章 | 8 |
| 其余章 | 5 |
### 6.6 检索结果上下文组装
### 6.7 检索结果上下文组装
检索出的 chunk 注入 LLM prompt 时,保留结构信息:
+10 -1
View File
@@ -81,7 +81,8 @@ class InferenceConfig(BaseModel):
class EmbeddingConfig(BaseModel):
model: str = "BAAI/bge-small-zh-v1.5"
# OV2/T11:实际语料为日文,bge-small-zh 面向中文 → 默认多语言 bge-m3(中/日/英)
model: str = "BAAI/bge-m3"
device: str = "cpu"
max_batch_size: int = 32
cache_dir: str = "/data/shared/models"
@@ -110,11 +111,19 @@ class RetrievalConfig(BaseModel):
contextual_enrichment: bool = True
class RerankConfig(BaseModel):
# I6/T6v1 引入 rerank 精排(2026 主流实践:向量→rerank→精排)
enabled: bool = True
model: str = "BAAI/bge-reranker-v2-m3"
device: str = "cpu"
class RagConfig(BaseModel):
embedding: EmbeddingConfig = Field(default_factory=EmbeddingConfig)
vector_store: VectorStoreConfig = Field(default_factory=VectorStoreConfig)
chunking: ChunkingConfig = Field(default_factory=ChunkingConfig)
retrieval: RetrievalConfig = Field(default_factory=RetrievalConfig)
rerank: RerankConfig = Field(default_factory=RerankConfig)
# ---------- 加载辅助 ----------
+5 -2
View File
@@ -1,8 +1,11 @@
embedding:
model: BAAI/bge-small-zh-v1.5
model: BAAI/bge-m3
vector_store:
adapter: chroma
chroma:
persist_dir: /data/shared/rules-handbook/chroma
retrieval:
rrf_k: 42
rrf_k: 42
rerank:
enabled: true
model: BAAI/bge-reranker-v2-m3
+26 -3
View File
@@ -35,7 +35,7 @@ def test_from_dir_maps_yaml_fields():
assert s.inference.models.primary.name == "deepseek-chat"
assert s.inference.llm_calls.max_context_tokens == 16000
assert s.inference.structured_output.max_parse_retry == 3
assert s.rag.embedding.model == "BAAI/bge-small-zh-v1.5"
assert s.rag.embedding.model == "BAAI/bge-m3"
assert s.rag.retrieval.rrf_k == 42
@@ -45,7 +45,7 @@ def test_defaults_when_dir_empty(tmp_path):
assert s.app.server.max_upload_mb == 100
assert s.app.task_queue["backend"] == "memory"
assert s.inference.models.primary.name == "deepseek-chat"
assert s.rag.embedding.model == "BAAI/bge-small-zh-v1.5"
assert s.rag.embedding.model == "BAAI/bge-m3"
assert s.rag.retrieval.rrf_k == 60
@@ -82,4 +82,27 @@ def test_expand_env_list_branch(monkeypatch):
data = {"models": [{"name": "a", "key": "${SOME_API_KEY}"}, "plain"]}
out = _expand_env(data)
assert out["models"][0]["key"] == "sk-list-xyz"
assert out["models"][1] == "plain"
assert out["models"][1] == "plain"
# ---------- T6/T11: rerank 精排 + bge-m3 多语言(I6/OV2 ----------
def test_embedding_default_model_is_bge_m3():
"""OV2bge-small-zh 面向中文、实际语料为日文 → 默认切换多语言 bge-m3。"""
s = Settings()
assert s.rag.embedding.model == "BAAI/bge-m3"
def test_rerank_config_defaults():
"""I6v1 引入 rerank 精排(bge-reranker-v2-m3),默认启用、device=cpu。"""
s = Settings()
assert s.rag.rerank.enabled is True
assert s.rag.rerank.model == "BAAI/bge-reranker-v2-m3"
assert s.rag.rerank.device == "cpu"
def test_rerank_env_override(monkeypatch):
monkeypatch.setenv("GENESIS_RAG__RERANK__ENABLED", "false")
s = Settings.from_dir(FIXTURES)
assert s.rag.rerank.enabled is False
assert s.rag.rerank.model == "BAAI/bge-reranker-v2-m3"
+84
View File
@@ -0,0 +1,84 @@
"""RAG 模型选型一致性门禁:T6/T11 整改(I6/OV2)的机器契约。
- T6I6):v1 引入 rerank 精排 bge-reranker-v2-m3
- T11OV2):embedding 默认切换多语言 bge-m3(实际语料为日文)
防文档漂移:代码默认值、fixture、当前设计文档四处必须一致;
当前设计文档不得残留 bge-small-zh-v1.5(历史评审记录/计划快照不改写,故不扫描)。
"""
from __future__ import annotations
from pathlib import Path
import yaml
from genesis.config import Settings
ROOT = Path(__file__).resolve().parents[1]
FIXTURES = Path(__file__).parent / "fixtures"
# 当前设计文档(排除历史评审记录 docs/design-review.md、docs/*-review*.md、
# docs/superpowers/plans/* 等历史快照)
_CURRENT_DOCS = [
"docs/rag-layer-design.md",
"docs/config-design.md",
"docs/design.md",
"docs/implementation-plan.md",
]
EMBEDDING_MODEL = "BAAI/bge-m3"
RERANK_MODEL = "BAAI/bge-reranker-v2-m3"
# 设计文档用短名(bge-m3);全名含短名子串,故 token 宽松匹配
_EMBEDDING_TOKEN = "bge-m3"
_RERANK_TOKEN = "bge-reranker-v2-m3"
def _doc_text(name: str) -> str:
return (ROOT / name).read_text(encoding="utf-8")
def _fixture_rag() -> dict:
with (FIXTURES / "rag.yaml").open("r", encoding="utf-8") as f:
return yaml.safe_load(f) or {}
# ---------- 代码默认值(T11 ----------
def test_code_default_embedding_is_bge_m3():
s = Settings()
assert s.rag.embedding.model == EMBEDDING_MODEL
# ---------- 代码默认值(T6 ----------
def test_code_rerank_config_defaults():
s = Settings()
assert s.rag.rerank.enabled is True
assert s.rag.rerank.model == RERANK_MODEL
# ---------- fixture 与代码一致 ----------
def test_fixture_synced_with_code_defaults():
rag = _fixture_rag()
assert rag["embedding"]["model"] == EMBEDDING_MODEL
assert rag["rerank"]["enabled"] is True
assert rag["rerank"]["model"] == RERANK_MODEL
# ---------- 文档与选型一致(T6/T11 ----------
def test_docs_use_bge_m3():
missing = [name for name in _CURRENT_DOCS if _EMBEDDING_TOKEN not in _doc_text(name)]
assert not missing, f"以下当前设计文档缺失 {_EMBEDDING_TOKEN}{missing}"
def test_docs_use_rerank_model():
missing = [name for name in _CURRENT_DOCS if _RERANK_TOKEN not in _doc_text(name)]
assert not missing, f"以下当前设计文档缺失 {_RERANK_TOKEN}{missing}"
def test_docs_no_legacy_bge_small_zh():
legacy = [name for name in _CURRENT_DOCS if "bge-small-zh-v1.5" in _doc_text(name)]
assert not legacy, f"当前设计文档残留旧模型 bge-small-zh-v1.5(请同步为 bge-m3):{legacy}"