- Sprint 执行计划 v1.1:§1.1 补第一批交付实据;T9 SPIKE 结论回写 Sprint 1 表 (T-P0-16 通过 2.8ms;T-P0-12 有条件;T-P0-10 原型 6/6 + 真实照片重验前置); Sprint 2 表标注 T-P2-01~06 交付;新增 §9 两批交付记录 - 开发任务拆解 v1.5:交付回写 + 新风险登记(RTMPose 域差异、BiRefNet 异步化) - 新增 开发任务包/:三批多 agent 并行的协作规程与 14 份任务契约 - 新增 前置开通清单-云资源与资质.md - 清理已用毕的评审报告(文档评审 2026-08-13、项目整体评审 2026-08-14), README 中相应引用改为留痕表述
84 lines
6.7 KiB
Markdown
84 lines
6.7 KiB
Markdown
# T8 · 知识库·向量与混合检索
|
||
|
||
> 对应任务:`T-P2-03`(向量化可重跑/模型可切换)、`T-P2-04`(检索、硬过滤零漏)、`T-P2-06`(无结果 query 记录)| 需求:`FR-KB-03/04`、架构 §8.3、`TD-08` | 分支:`task/t8-retrieval` | worktree:`/home/xsl/meida_backend-t8`
|
||
> 先读《README.md(协作规程)》,遵守全部纪律。
|
||
|
||
## 1. 目标
|
||
|
||
pgvector 上的混合检索管线骨架:**硬过滤 → 向量召回 → 关键词召回 → RRF 融合重排**,加 `kb_chunk` 写入通道(向量 + tsvector 双索引)与 `kb_gap` 知识缺口埋点。检索是 Agent(M4-b)和报告(M3)的共同前置。
|
||
|
||
## 2. 已就绪的契约(协调提交已提供,禁改 models/migrations)
|
||
|
||
### 表(迁移 `0005` 已建,ORM `KbChunk`/`KbAsset`/`KbGap` 已加,先读 models.py 核对)
|
||
|
||
- `kb_chunk`:`chunk_id` PK、`entity_id`、`chunk_type` varchar(32)、`text` text、`embedding` **vector(1024)**、`embedding_model` varchar(64)、`embedding_version` varchar(16)、`tsv` tsvector(GENERATED ALWAYS AS to_tsvector('simple', text) STORED——**写入时不要手工填 tsv**);HNSW 索引(cosine)+ GIN(tsv)
|
||
- `kb_asset`:`asset_id` PK、`entity_id`、`asset_type`、`cos_key` varchar(255)、`spec_check` JSONB、`tryon_ready` bool、`review_status`、`license`;Index(entity_id)、partial Index(tryon_ready)
|
||
- `kb_gap`:`gap_id` PK、`query_text`、`filter_ctx` JSONB、`hit_count` int、`first_seen_at`;Index(query_text)
|
||
|
||
### 配置(config.py 已加,.env.example 已示例)
|
||
|
||
```python
|
||
kb_vector_dimensions: int = 1024 # KB_VECTOR_DIMENSIONS
|
||
kb_embedding_model: str = "deterministic-v1" # KB_EMBEDDING_MODEL(当前默认=确定性假实现)
|
||
kb_embedding_device: str = "cpu" # KB_EMBEDDING_DEVICE
|
||
retrieval_top_k: int = 50 # RETRIEVAL_TOP_K
|
||
retrieval_rerank_weights: dict = {"vector":0.45,"keyword":0.2,"rule":0.25,"freshness":0.1} # RETRIEVAL_RERANK_WEIGHTS_JSON
|
||
```
|
||
⚠️ 上批经验:带 alias 的 Settings 字段在**测试里按字段名构造会被静默忽略**——测试用 `monkeypatch.setenv` + `get_settings.cache_clear()`。
|
||
|
||
### 重排公式(架构 §8.3 唯一口径)
|
||
|
||
`最终分 = 0.45×向量 + 0.2×关键词 + 0.25×规则权重 + 0.1×新鲜度`(权重可配,就是上面的 rerank_weights)。规则权重项本批的输入是 `rule_boost: dict[chunk_id, float]`(由调用方传入,规则引擎联动属后续任务,管线只认接口)。
|
||
|
||
### 分层位置
|
||
|
||
`packages/retrieval` 在 import-linter 第 4 层(与 rules 同层)。只准 import `packages.shared`/`packages.domain` 与已装依赖(pgvector 的 sqlalchemy 类型、numpy)。
|
||
|
||
## 3. 实现要点
|
||
|
||
允许改动:`packages/retrieval/**`、测试文件。**不建 HTTP 路由**(检索是内部能力,被 agent/report 的 service 调用)。
|
||
|
||
1. **`packages/retrieval/embedding.py`**:
|
||
- `EmbeddingProvider` Protocol:`async def embed(texts: list[str]) -> list[list[float]]`、属性 `model_name/version/dimensions`;
|
||
- `DeterministicEmbedder`:把文本 sha256 种子映射到**归一化**的 1024 维向量(同文本同向量、不同文本近似正交——用 seed 生成伪随机向量后 L2 归一化)。注释写明:这是测试/dev 的假实现,**真实 BGE-M3 ONNX 加载是后续任务**(配置里的 `kb_embedding_device` 是为它预留的);
|
||
- `build_embedding_provider(settings)` 工厂:按 `kb_embedding_model` 选择实现(当前只认 `deterministic-v1`,其它值抛 `AppError(CONFIG_ERROR)`)。
|
||
2. **`packages/retrieval/repo.py`**:
|
||
- `upsert_chunks(session, entity_id, chunks, embedding)`:写 kb_chunk(embedding 用 `pgvector.sqlalchemy.Vector`;`embedding_model/version` 必填——**没有版本号的向量不可重跑**,这是 T-P2-03 的验收);
|
||
- `hybrid_search(session, query_vec, query_text, *, entity_types, occasion, body_shape, rule_boost, top_k)`:按架构 §8.3 的 CTE 结构实现(可用 `sqlalchemy.text()` 原生 SQL + 绑定参数,或 SQLAlchemy core;`embedding <=> :query_vec` 余弦距离、`ts_rank`、RRF:`1/(60+rank_vec) + 1/(60+rank_kw)`,再叠加权重与新鲜度 `(now-updated_at)` 归一化衰减);
|
||
- **硬过滤是第一公民**:`e.status='published'` + entity_types + occasion(`attrs->'occasions' ? :occasion`)+ 禁忌排除(`NOT (attrs->'avoid_body_shapes' ? :body_shape)`)——**硬过滤零漏**是 T-P2-04 红线,集成测试必须证明"禁忌实体即使向量最近也绝不出现";
|
||
- `record_gap(session, query_text, filter_ctx)`:无结果时 upsert kb_gap(同 query_text + filter_ctx 则 `hit_count += 1`)。
|
||
3. **`packages/retrieval/service.py`**:`RetrievalService.search(query_text, *, user_ctx) -> SearchOutcome`(编排 embed → hybrid_search →(结果为空时)record_gap);`SearchOutcome` 含 `results: list[ScoredChunk]`(chunk_id/entity_id/text/vec_score/kw_score/final_score)与 `degraded: bool`(无结果走了 gap 记录)。
|
||
|
||
## 4. 测试要求
|
||
|
||
- 单测 `tests/unit/test_embedding.py`:确定性(同文本同向量)、维度、L2 归一化、工厂的未知模型名拒绝。
|
||
- 单测 `tests/unit/test_rerank.py`:RRF 融合 + 权重 + 新鲜度的数学正确性(构造已知分数断言最终序)。
|
||
- 集成 `tests/integration/test_hybrid_retrieval.py`(新文件,用 DeterministicEmbedder,**不需要真实模型**):
|
||
1. 建 3 个实体(1 个 published 带禁忌属性、1 个 draft、1 个 published 正常)+ 各挂 chunk → 检索命中正常实体、**draft 不出现、禁忌实体零出现**(T-P2-04 硬过滤零漏);
|
||
2. 关键词命中路径(text 里放英文关键词,tsv 'simple' 分词);
|
||
3. 无结果 query → kb_gap 出现一行、再查同 query → hit_count=2(T-P2-06);
|
||
4. `tryon_ready=true` 的 kb_asset 可按 entity 查询(顺手验证 partial 索引可用)。
|
||
|
||
## 5. 禁改清单
|
||
|
||
通用禁改之外:`packages/kb/**`、`packages/rules/**`、`packages/identity/**`、所有路由文件。
|
||
|
||
## 6. 验收标准(T-P2-03/04/06)
|
||
|
||
1. 向量写入带 `embedding_model/version`,换模型名即视为不同版本(可重跑判据);
|
||
2. 硬过滤零漏(集成断言禁忌/draft 实体绝不出现在结果);
|
||
3. RRF + 权重重排数学正确(单测);
|
||
4. 无结果 query 全量落 kb_gap 且可聚合排序(hit_count);
|
||
5. 全部检查绿(mypy 范围同 T6 列表 + `packages/retrieval`)。
|
||
|
||
## 7. 提交信息
|
||
|
||
```
|
||
feat(retrieval): hybrid search over pgvector with hard filters and gap tracking
|
||
|
||
DeterministicEmbedder(可换 BGE-M3 的接口位)+ kb_chunk 双索引写入
|
||
+ 硬过滤→向量→关键词→RRF 重排管线 + kb_gap 缺口埋点(T-P2-03/04/06,TD-08)。
|
||
|
||
偏差说明:<如有写在这里,无则写"无">
|
||
```
|