diff --git a/_AI_USAGE_LOG.md b/_AI_USAGE_LOG.md index a03880e..950d773 100644 --- a/_AI_USAGE_LOG.md +++ b/_AI_USAGE_LOG.md @@ -77,3 +77,4 @@ | 2026-08-11 | Agent 实现 | T8(架构审查整改):LLM 客户端全异步化(Issue9)。client.py 由同步 httpx.Client 全异步化:LLMClient Protocol chat → async def;HttpLLMClient 用 httpx.AsyncClient + asyncio.sleep 退避(消除 time.sleep 阻塞 asyncio 任务池);__enter__/__exit__ → __aenter__/__aexit__(async with 生命周期闭环);engine.py chat/chat_structured/_call 全部 async + await;FakeLLMClient.chat → async;测试基建用 anyio pytest 插件(@pytest.mark.anyio);test_inference_engine.py 32 用例脚本批量转换 async + NotConfiguredClient 同步 client 转 async;test_inference_client.py 10 用例转 async;同步 inference-engine-design spec 与 milestone3-inference-review httpx 描述(防文档漂移);TDD 验证 RED(async 接口缺失 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_char(CJK 统一表意/扩展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.2:TaskQueue 标注 v1 仅 InMemory、Redis/Valkey 为 v2 预留;rag-layer §9:Storage Adapter 仅 ChromaAdapter、移除切换流程/工厂 qdrant 分支;agent-runtime §3.1/§3.5;design §5.5/§8.4.1;config-design env/app.yaml/rag.yaml/docker compose;web-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 新增 RerankConfig(enabled=True/model=BAAI/bge-reranker-v2-m3/device=cpu)挂入 RagConfig;EmbeddingConfig.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-m3;config-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 | diff --git a/docs/config-design.md b/docs/config-design.md index a2f5eea..1752082 100644 --- a/docs/config-design.md +++ b/docs/config-design.md @@ -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 仅 chroma(qdrant 为 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.3,v1 新增 I6/T6) + enabled: true # false 时跳过精排,直接取 RRF 前 top_k + model: BAAI/bge-reranker-v2-m3 # 多语言 Cross-Encoder + device: cpu # cpu | cuda ``` --- diff --git a/docs/design.md b/docs/design.md index 06c0deb..dcecbbe 100644 --- a/docs/design.md +++ b/docs/design.md @@ -708,10 +708,10 @@ Parser 处理时分为: | 设计点 | 决策 | |--------|------| | 向量数据库 | Chroma(v1 唯一;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 对比,只重建变化文档)| | 版本路由 | 会话开始锁版本 | diff --git a/docs/implementation-plan.md b/docs/implementation-plan.md index 9cf37b5..c682f5c 100644 --- a/docs/implementation-plan.md +++ b/docs/implementation-plan.md @@ -122,7 +122,7 @@ Wordテンプレート・ルール文書・PPTルール文書・現行システ |---|-------|------| | 4.1 | ルール文書のインデックス化 | 分類済みルールの分割(Word/Excel/PPT フォーマット適応)→ Embedding → ベクトルストア構築(詳細: docs/rag-layer-design.md §3, §4) | | 4.2 | StorageAdapter 実装 | VectorStoreAdapter 抽象+ChromaAdapter+MockAdapter(詳細: 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 からの更新トリガー → 再インデックス化 | diff --git a/docs/rag-layer-design.md b/docs/rag-layer-design.md index 3fc702c..c7f3276 100644 --- a/docs/rag-layer-design.md +++ b/docs/rag-layer-design.md @@ -50,7 +50,8 @@ RAG 层存储三类内容,分类在 Parser 解析时完成: | 选型点 | 决策 | 说明 | |--------|------|------| | 向量数据库 | **Chroma(v1 唯一)** | 初期用 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-Encoder,query×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-m3(Cross-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 时,保留结构信息: diff --git a/src/genesis/config.py b/src/genesis/config.py index 72f469d..1401ffb 100644 --- a/src/genesis/config.py +++ b/src/genesis/config.py @@ -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/T6:v1 引入 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) # ---------- 加载辅助 ---------- diff --git a/tests/fixtures/rag.yaml b/tests/fixtures/rag.yaml index bb58690..b7f996c 100644 --- a/tests/fixtures/rag.yaml +++ b/tests/fixtures/rag.yaml @@ -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 \ No newline at end of file + rrf_k: 42 +rerank: + enabled: true + model: BAAI/bge-reranker-v2-m3 \ No newline at end of file diff --git a/tests/test_config.py b/tests/test_config.py index 72b722d..af7ba57 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -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" \ No newline at end of file + assert out["models"][1] == "plain" + + +# ---------- T6/T11: rerank 精排 + bge-m3 多语言(I6/OV2) ---------- + +def test_embedding_default_model_is_bge_m3(): + """OV2:bge-small-zh 面向中文、实际语料为日文 → 默认切换多语言 bge-m3。""" + s = Settings() + assert s.rag.embedding.model == "BAAI/bge-m3" + + +def test_rerank_config_defaults(): + """I6:v1 引入 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" \ No newline at end of file diff --git a/tests/test_rag_design_consistency.py b/tests/test_rag_design_consistency.py new file mode 100644 index 0000000..b473466 --- /dev/null +++ b/tests/test_rag_design_consistency.py @@ -0,0 +1,84 @@ +"""RAG 模型选型一致性门禁:T6/T11 整改(I6/OV2)的机器契约。 + +- T6(I6):v1 引入 rerank 精排 bge-reranker-v2-m3 +- T11(OV2):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}"