Files
meida/docs/开发任务包/T13-黄金集CI框架.md
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

72 lines
5.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# T13 · 黄金集回归框架接入 CI
> 对应任务:`T-P2-11` 🔴 | 需求:`FR-KB-11`、`NFR-MAINT-04`(指标退化阻断合并)、架构 §17「黄金集是最重要的一道防线」| 分支:`task/t13-golden-ci` | worktree:`/home/xsl/meida_backend-t13`
> 先读《README.md(协作规程)》,遵守全部纪律。
## 1. 目标
把黄金集从"散在单测里的固定样本"升级为**数据驱动的回归框架**:用例存 JSON、阈值存配置、CI 强制跑、**指标退化直接 fail**。V1 先落两个子集(规则黄金集、检索黄金集),Agent 问答与报告结构两个子集留占位(等 Sprint 3/4)。
## 2. 已就绪的契约(协调提交 `9c18021` 已提供)
- **pytest marker `golden`** 已注册(`--strict-markers` 下可直接用);默认 addopts 排除 `golden`(与 integration 同待遇),`make golden` / CI 防线 5c 强制跑(env `MEIDA_RUN_INTEGRATION=1`)。
- **规则黄金集现有资产**:`tests/unit/test_rules_golden.py` 有 8 条规则 + 11 组样本(硬禁忌压制、多规则叠加等)——**不许改它**;你的 JSON 用例集是独立交付(可参考其场景设计新写,不是搬运代码)。
- **检索侧可复用**:`packages/retrieval/embedding.py` 的 `DeterministicEmbedder`(同文本同向量→黄金集可断言确定性结果)+ `packages/rules` 的 DSL/引擎。
### 黄金集规模口径(知识库文档 §7,唯一事实源)
四子集共 **330 例**(检索 100 / Agent 问答 100 / 诊断 100 / 报告结构 30)。**本任务只搭框架 + 规则/检索各 10~15 例种子**,规模达标是内容轨 `T-P2-16` 的事——框架必须让"加用例=加 JSON 行"。
## 3. 实现要点
允许改动:`tests/golden/**`(新目录,归你全部)、**仅此一处**;不改 packages/apps/已有测试。
1. **目录结构**:
```
tests/golden/
README.md # 用例格式说明 + 如何加用例 + 阈值怎么调
thresholds.json # {"rules": {"pass_rate_min": 1.0}, "retrieval": {"pass_rate_min": 0.8, "first_hit_in_top3_min": 0.8}}
cases/
rules_cases.json # 规则黄金集种子
retrieval_cases.json # 检索黄金集种子
runner.py # 用例加载 + 执行 + 指标计算 + 明细报告
test_golden.py # pytest 入口(marker=golden,skipif 无 MEIDA_RUN_INTEGRATION)
```
2. **规则用例格式**(rules_cases.json):`{"name", "rules": [完整 Rule DSL 数组], "context": {...RuleContext}, "expect": {"must_recommend": [{"slot": "top", "attrs_include": {...}}], "must_avoid": [...], "hard_blocks_nonempty": bool, "forbidden_recommend": [...]}}`。判定:`evaluate()` 结果逐一核对 expect;一条不符 = 该用例 fail。
3. **检索用例格式**(retrieval_cases.json):`{"name", "seed_entities": [{entity_type, name, attrs, status, chunks: [text]}], "query", "user_ctx": {...}, "expect": {"must_hit_names": [...], "must_miss_names": [...], "first_hit_in": "top1|top3"}`。执行:每个用例**先清自己 seed 的实体**(按 name 前缀 `golden:` 命名,跑前 DELETE 这些行,保证幂等重跑)→ DeterministicEmbedder 写 chunk → `hybrid_search` → 判定。`must_miss` 语义 = 硬过滤红线(禁忌/draft 实体绝不出现)。
4. **指标与阻断**(runner.py):
- 每子集算 `pass_rate`(通过用例/总数);检索加算 `first_hit_in_top3_rate`;
- **规则子集阈值 = 1.0**(硬结论子集,一条不许退);检索 = 0.8 起步(阈值在 thresholds.json,顾问扩充用例后可调);
- fail 时输出**逐用例明细**(name + 期望 vs 实际的差异摘要)——CI 日志里要能直接看出哪条退化了;
- `runner.py` 是普通模块(不依赖 pytest),`test_golden.py` 只是薄壳调用——将来想在本地脚本或 admin 后台跑同一套,不用改框架。
5. **README.md**:写明四子集规划(330 例口径)、当前覆盖(两子集 N 例)、加用例流程、"为什么规则阈值必须 1.0"(核心结论由规则产出,规则退化=产品根基退化)。
## 4. 测试要求(即框架本身)
- `make golden`(或 `MEIDA_RUN_INTEGRATION=1 .venv/bin/pytest -m golden --no-cov`)全绿:两个种子子集通过率 100%(种子用例是手工校准过的,必须全对,否则框架自身有 bug);
- **阻断语义自证**:在本地临时改坏一条用例的 expect 验证会 fail 且输出明细(验证完改回来——提交里只有正确版本;这个自证过程写进提交信息,不写代码);
- 无 PG/Redis 时 `pytest -m golden` 全部 skip(不拖累本地 `make test`)。
## 5. 禁改清单
通用禁改之外:`packages/**`、`apps/**`、`tests/unit/**`、`tests/integration/**`、`pyproject.toml`(golden marker 已注册)、`Makefile`/CI(防线 5c 已挂)。**你的全部产出在 `tests/golden/**`。**
## 6. 验收标准(T-P2-11)
1. CI 防线 5c 能跑且通过(本地等价命令全绿);
2. 指标退化即 fail:人为退化会被拦截(自证过程留提交信息);
3. 加用例 = 加 JSON 行,零代码改动(README 有流程);
4. 规则子集含硬禁忌场景、检索子集含 must_miss(红线)场景;
5. 全部检查绿(tests 不在 mypy 范围;ruff 对 tests/golden 全绿)。
## 7. 提交信息
```
test(golden): data-driven golden set framework wired into CI
tests/golden:JSON 用例 + 阈值配置 + runner(规则 pass_rate=1.0、检索 0.8
起步)+ pytest 壳;规则/检索两子集种子,Agent/报告子集占位(T-P2-11)。
偏差说明:<如有写在这里,无则写"无">
```