- 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 中相应引用改为留痕表述
5.6 KiB
T7 · 知识库·规则引擎
对应任务:
T-P2-02(规则求值 p95 < 50ms;硬约束可拦截)| 需求:FR-KB-02、架构 §8.2 rules-engine、TD-10| 分支:task/t7-rules-engine| worktree:/home/xsl/meida_backend-t7先读《README.md(协作规程)》,遵守全部纪律。
1. 目标
自研数据驱动决策表(TD-10):kb_rule 的 JSON DSL 解析 + 纯域求值引擎。核心结论由规则引擎产出,LLM 只负责表达(防幻觉红线)——这是整个产品"专业可信"的根基,代码质量按"钱与正确性"标准写。
2. 已就绪的契约(协调提交已提供,禁改 models/migrations)
表 kb_rule(ORM KbRule 已加)
rule_id PK 雪花、rule_key varchar(64) UNIQUE(如 R-BODY-A-002)、category varchar(32)、version int、status draft/published/archived、priority int、conditions JSONB、effects JSONB、is_hard bool、confidence Numeric(4,3)、evidence varchar(255)、author/reviewer varchar(64)、published_at;Index(status, priority)。
(注:架构 §6.2 写的列名 conclusions,协调提交统一为 effects——与"决策表"术语一致,任务文档以 models.py 实际代码为准。)
DSL 规格(知识库文档 §3.2⑩ 原文结构)
{
"rule_key": "R-BODY-A-002",
"category": "body_shape",
"priority": 90,
"conditions": {
"body_shape": "A型",
"features": { "include": ["溜肩"] },
"goals": { "include": ["修饰溜肩"] }
},
"effects": {
"recommend": [ { "slot": "top", "attrs": { "shoulder": ["垫肩","自然肩"], "sleeve": ["泡泡袖"] } } ],
"avoid": [ { "slot": "top", "attrs": { "shoulder": ["落肩"], "silhouette": ["修身"] } } ],
"explanation": "溜肩需要在肩部建立结构感……"
},
"is_hard": false,
"confidence": 0.95
}
优先级分组(知识库文档 §3.2⑩,语义:数字越大越优先)
合规 100 / 用户禁忌 95 / 体型 90 / 色彩 85 / 场景 80 / 温度 75 / 风格 70 / 偏好 60 / 趋势 50。
分层位置
packages/rules 已在 import-linter 第 4 层(kb|retrieval|rules|memory|profile),现只有 README + 空 __init__。只准 import packages.shared/packages.domain。
3. 实现要点
允许改动:packages/rules/**、测试文件。
packages/rules/dsl.py——纯逻辑(无 IO,重点单测):RuleCondition/RuleEffect/Rulepydantic 模型(解析校验 DSL;conditions支持的字段:等值匹配(str)、include/exclude列表、min/max数值区间;未识别的键拒绝而非忽略——静默忽略会让规则悄悄失效);match(condition: RuleCondition, ctx: RuleContext) -> bool:RuleContext是 frozen dataclass(body_shape/season/face_shape/temperature/features/goals/occasion/style/user_taboos…,缺字段 = 不匹配该子句);- 所有子句 AND 语义。
packages/rules/engine.py——纯域求值:evaluate(rules, ctx) -> RuleVerdict:返回recommend/avoid/explanation 汇总 + rule_hits(每条含 rule_key/priority/category/is_hard/confidence/explanation);- 排序:priority 降序,同 priority 按 confidence 降序;
- 硬约束语义:
is_hard=true且 avoid 命中 →RuleVerdict.hard_blocks非空,调用方必须用它做硬过滤(禁忌违反 = 0 是发布红线);用户禁忌(priority ≥95 的 avoid)永远压过专业推荐——同 slot 冲突时用户禁忌胜出并从 recommend 中剔除冲突项; - p95 < 50ms:求值是纯内存操作,规则量 400 条时远低于此;写一个 400 条规则 × 1000 次求值的基准单测(
time.perf_counter,断言均值 < 5ms/千次的量级即可,CI 机器差异留裕量)。
packages/rules/service.py+repo.py——DB 侧:按 status='published' 载入规则(内存缓存 +invalidate();量级 400,直接整表载入),CRUD(draft→published 状态机,同 T6 语义),DSL 写入前用 pydantic 校验。- 黄金集雏形(架构 §1081 明确要求):
tests/unit/test_rules_golden.py固定 8~10 组「输入画像 → 期望 recommend/avoid/hard_blocks」样本,其中必须包含:一条硬禁忌场景(用户禁忌压过专业推荐的用例)、一条多规则叠加场景。
4. 测试要求
- 单测:DSL 解析(合法/非法结构/未识别键拒绝)、match 矩阵(等值/include/exclude/min-max/缺字段)、排序、硬约束剔除、用户禁忌优先、基准测试、黄金集。
- 集成
tests/integration/test_rules_engine.py(新文件):插 3 条规则(含 1 条 is_hard)→ service 载入 → 求值命中与拦截正确;draft 规则不被载入。
5. 禁改清单
通用禁改之外:packages/kb/**(表共享但包不共享——repo 只碰 kb_rule 表,通过 ORM 模型访问,不改 T6 的文件)、packages/retrieval/**、packages/identity/**。
6. 验收标准(T-P2-02)
- 求值 p95 满足 400 条规则量级远低于 50ms(基准测试证明);
- 硬约束可拦截(hard_blocks 语义 + 黄金集用例);
- 用户禁忌永远压过专业推荐(黄金集用例);
- DSL 非法结构 100% 拒绝(不会静默失效);
- 全部检查绿(mypy 范围同 T6 列表 +
packages/rules)。
7. 提交信息
feat(rules): data-driven decision table engine with hard constraints
kb_rule DSL 解析 + 纯域求值(优先级分组/硬约束/用户禁忌压过专业推荐)
+ 400 条基准 + 黄金集雏形(T-P2-02,TD-10)。
偏差说明:<如有写在这里,无则写"无">