- 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 中相应引用改为留痕表述
6.7 KiB
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_idPK、entity_id、chunk_typevarchar(32)、texttext、embeddingvector(1024)、embedding_modelvarchar(64)、embedding_versionvarchar(16)、tsvtsvector(GENERATED ALWAYS AS to_tsvector('simple', text) STORED——写入时不要手工填 tsv);HNSW 索引(cosine)+ GIN(tsv)kb_asset:asset_idPK、entity_id、asset_type、cos_keyvarchar(255)、spec_checkJSONB、tryon_readybool、review_status、license;Index(entity_id)、partial Index(tryon_ready)kb_gap:gap_idPK、query_text、filter_ctxJSONB、hit_countint、first_seen_at;Index(query_text)
配置(config.py 已加,.env.example 已示例)
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 调用)。
packages/retrieval/embedding.py:EmbeddingProviderProtocol: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))。
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)。
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,不需要真实模型):- 建 3 个实体(1 个 published 带禁忌属性、1 个 draft、1 个 published 正常)+ 各挂 chunk → 检索命中正常实体、draft 不出现、禁忌实体零出现(T-P2-04 硬过滤零漏);
- 关键词命中路径(text 里放英文关键词,tsv 'simple' 分词);
- 无结果 query → kb_gap 出现一行、再查同 query → hit_count=2(T-P2-06);
tryon_ready=true的 kb_asset 可按 entity 查询(顺手验证 partial 索引可用)。
5. 禁改清单
通用禁改之外:packages/kb/**、packages/rules/**、packages/identity/**、所有路由文件。
6. 验收标准(T-P2-03/04/06)
- 向量写入带
embedding_model/version,换模型名即视为不同版本(可重跑判据); - 硬过滤零漏(集成断言禁忌/draft 实体绝不出现在结果);
- RRF + 权重重排数学正确(单测);
- 无结果 query 全量落 kb_gap 且可聚合排序(hit_count);
- 全部检查绿(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)。
偏差说明:<如有写在这里,无则写"无">