Files
meida/docs/开发任务包/T7-规则引擎.md
T
xsl e511b6a66f 收尾两批并行开发的文档回写与任务包
- 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 中相应引用改为留痕表述
2026-08-14 21:07:20 +08:00

5.6 KiB
Raw Blame History

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/**、测试文件。

  1. packages/rules/dsl.py——纯逻辑(无 IO,重点单测):
    • RuleCondition / RuleEffect / Rule pydantic 模型(解析校验 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 语义。
  2. 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 机器差异留裕量)。
  3. packages/rules/service.py + repo.py——DB 侧:按 status='published' 载入规则(内存缓存 + invalidate();量级 400,直接整表载入),CRUD(draft→published 状态机,同 T6 语义),DSL 写入前用 pydantic 校验。
  4. 黄金集雏形(架构 §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)

  1. 求值 p95 满足 400 条规则量级远低于 50ms(基准测试证明);
  2. 硬约束可拦截(hard_blocks 语义 + 黄金集用例);
  3. 用户禁忌永远压过专业推荐(黄金集用例);
  4. DSL 非法结构 100% 拒绝(不会静默失效);
  5. 全部检查绿(mypy 范围同 T6 列表 + packages/rules)。

7. 提交信息

feat(rules): data-driven decision table engine with hard constraints

kb_rule DSL 解析 + 纯域求值(优先级分组/硬约束/用户禁忌压过专业推荐)
+ 400 条基准 + 黄金集雏形(T-P2-02,TD-10)。

偏差说明:<如有写在这里,无则写"无">