# T17 · 报告流水线·LLM 文案与生成后校验 > 对应任务:`T-P3-10`(文案结论与诊断值一致,无自相矛盾)、`T-P3-11` 🔴(**硬性禁忌违反数 = 0**;贬损词零通过)、架构 §7.1 步骤 17~19(「生成后校验不可跳过:校验失败最多重试 2 次,仍失败降级为规则直出模板文案,绝不放行违规内容」)| 分支:`task/t17-report-copy` | worktree:`/home/xsl/meida_backend-t17` > 先读《README.md(协作规程)》,遵守全部纪律。**第四批拼流水线:T15 定 Stage 接口,你供文案与校验两个阶段,文件名与类名已定死。** ## 2. 你要创建的文件(挂点,禁差分毫) | 文件 | 导出 | 流水线 dotted path | | --- | --- | --- | | `packages/report/copy.py` | 纯逻辑:结论→文案素材、模板降级渲染 | — | | `packages/report/copy_stage.py` | `class CopyStage` | `packages.report.copy_stage:CopyStage` | | `packages/report/validation.py` | 纯逻辑:贬损词表、一致性校验器 | — | | `packages/report/validation_stage.py` | `class ValidationStage` | `packages.report.validation_stage:ValidationStage` | Stage 接口以协调提交 `packages/report/pipeline.py` 骨架为准;`ctx.gateway` 是 T15 注入的 `LLMGateway`(集成测试可换 FakeProvider——参考 `tests/integration/test_vertical_slice.py` 的做法)。 ## 3. LLM 文案(copy.py + copy_stage.py) 1. **Prompt**:`prompts/report_copy/v1.txt`(你新建,版本化目录规则见 `prompts/README.md`——`[system]` 段**不许含任何变量**(缓存前缀纪律,`validate_cache_prefix` 会拦);`[user]` 段放 `{dimensions_json}`)。system 定角色:形象顾问报告文案;红线写进 system:描述而非评判、不用绝对化用语、不出现贬损词、结论只能复述给定诊断值**不得新增维度结论**、简体中文。 2. **结构化输出**:`gateway.complete(scene=Scene.REPORT_COPY, prompt_vars={...}, schema=ReportCopy, user_id=..., ref_type="report", ref_id=str(report_id))`——先读 `packages/llm/gateway.py` 的 `complete` 签名与 `Scene` 枚举、`packages/llm/routing.py` 里 REPORT_COPY 场景现有配置(temperature/prompt_id 指向你的 v1)。`ReportCopy(BaseModel)`:`summary: str`、`sections: list[Section]`(Section:`title/paragraphs: list[str]`)——**schema 宽进严出的取舍写注释**(字段越少结构化成功率越高,文案主体自由度放 sections 内)。 3. **素材组装(纯函数 `build_prompt_material(result_diagnosis) -> str`)**:从 T16 的 `result["diagnosis"]["dimensions"]` 取 status=ok 的维度(skipped 不进 prompt——**LLM 看不到缺失维度就不会编造它**,这是防幻觉的第一道闸);序列化成紧凑 JSON(key+value+evidence,舍 confidence 小数位)。**总长控制**:拼好后若超 ~6000 字符,按 basic_coverage 优先级裁剪维度并记录 `dropped_keys`(对齐架构 §7.2 的裁剪思想:低重要度先丢,硬结论永不丢)。 4. **`CopyStage`(name="copy")**:无 diagnosis 产物 → 抛错(流水线顺序错);调 gateway → `result["copy"] = {"summary": ..., "sections": [...], "model": completion.model, "prompt_version": ..., "dropped_keys": [...]}`。**这里不做校验**——校验是下一阶段的独立职责(T-P3-10 的"生成后校验"是流水线步骤 18,不是文案函数的内嵌断言)。 ## 4. 生成后校验(validation.py + validation_stage.py) 1. **贬损词表初版**(`validation.py` 模块常量 `PEJORATIVE_TERMS`,来源:`G:\meida\docs\合规与安全方案.md` §3「内容伦理红线」——**开工先读该节**,把"描述而非评判/不使用缺陷框架"翻译成具体词表:胖/丑/矮小/缺陷/土气/难看/显老/失败 等约 20~40 词 + 简单变形("有点胖"命中"胖"即可,不做词干)。词表带注释:这是初版内嵌版,正式版归合规词库任务(T-P0-07)。 2. **一致性校验(纯函数)**: - `check_pejorative(text) -> list[str]`(命中词列表); - `check_conclusion_consistency(copy: ReportCopy, dimensions: list[dict]) -> list[str]`(不一致清单):初版三条规则——① 文案中出现的**体型/脸型/季型枚举词**必须是诊断值同款(诊断说 H 型、文案写 A 型 → 不一致);② 文案提到具体色值 `#RRGGBB` 必须在色板维度 value 集合内;③ 出现"建议"句式却无对应 rule_hits 支撑的维度结论 → 标记(宽松口径,注释说明误报代价 vs 漏报代价的取舍)。 - `check_absolute_claims(text)`:绝对化用语(最/第一/彻底/百分百——prompt 禁了,校验兜底)。 3. **`ValidationStage`(name="validation")**:取 copy + diagnosis → 全部校验 → - **全过**:`result["validation"] = {"passed": true, "checks": {...计数}}`; - **有违规**:**重试一次**——把违规清单作为修正指令追加(messages 上下文,参考 gateway 的结构化重试机制但不能用它内部的重试——那是解析层,这是语义层,两层独立)再生成 → 再校验 → 仍违规 → **降级模板**:`render_fallback_copy(dimensions, rule_hits)`(纯函数:按维度 evidence + rule explanation 拼模板文案,零 LLM)→ `result["validation"] = {"passed": true, "degraded": true, "violations_first_pass": [...]}`。**任何出口 passed 必须为 true 或阶段抛错——绝不放行未过校验的文案**(T-P3-11 红线)。 4. 校验产物同段落库(`result["validation"]`),报告查询侧能看到是否降级。 ## 5. 测试要求 - 单测 `tests/unit/test_report_copy.py`:素材组装(skipped 不进、超长裁剪记录 dropped、硬结论不裁)、模板降级渲染(有 rule_hits 时含 explanation)。 - 单测 `tests/unit/test_report_validation.py`:词表命中(含"有点胖"变形)、一致性三条规则各一正一反、绝对化用语、重试后仍违规 → 降级路径纯函数级验证。 - 集成 `tests/integration/test_report_copy_stage.py`(新文件,FakeProvider 替换 gateway——参考 test_vertical_slice): 1. 干净 LLM(Fake 返回合规文案)→ copy + validation 两阶段 passed=true; 2. **脏 LLM**(Fake 第一次返回含"微胖/最合适",修正后干净)→ 第一轮 violations 非空 → 重试通过; 3. **持续脏 LLM**(恒返贬损词)→ 降级模板 passed=true degraded=true,**最终文案零贬损词**(T-P3-11 的黄金断言)。 - 不测整条流水线(T15 集成已覆盖编排);只直接构造 ctx 调你的阶段。 ## 6. 禁改清单 通用禁改之外:`packages/report/` 除你四个文件 + `prompts/report_copy/**` 外全部、`apps/**`、全部已有测试。**`prompts/report_copy/v1.txt` 归你**(新目录不与他人冲突)。 ## 7. 验收标准(T-P3-10/11) 1. 校验不可跳过:任何出口要么 passed 要么抛错; 2. 贬损词零通过(含持续脏 LLM 的降级路径断言); 3. 文案-结论一致性三条规则生效; 4. LLM 看不到 skipped 维度(防幻觉得到结构保证); 5. 全部检查绿(mypy 范围同 T15 + 你的文件)。 ## 8. 提交信息 ``` feat(report): LLM copy generation with mandatory post-validation and fallback report_copy/v1 prompt(系统段零变量)+ 结构化 ReportCopy + 素材组装 (skipped 不进 prompt/超长裁剪)+ 贬损词表/一致性/绝对化三重校验 + 重试一次后降级规则直出模板(T-P3-10/11)。 偏差说明:<如有写在这里,无则写"无"> ```