Files
2026Technology-Competition/docs/superpowers/specs/2026-08-12-phase5-writer-qa-design.md
T
lhl c7e7c95a19 plan(phase5): 锁定 Writer/QA 设计评审修正与实施计划基线
- spec 据 4 项评审决策落地 14 处修正 + GSTACK REVIEW REPORT
- 实施计划 16 任务 / 3 里程碑(M1 基础件 / M2 垂直切片 / M3 闭环硬化)
- _AI_USAGE_LOG.md 登记评审与计划
2026-08-13 09:06:11 +08:00

240 lines
17 KiB
Markdown
Raw 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.
# Phase 5 设计规格:Writer / QA 完整实现
- 日期:2026-08-12
- 范围:Writer 子系统 + QA 子系统完整实现;RAG/Impact 检索以桩接口先行(真实检索后置)
- 集成深度:核心模块 + 离线条到端(不动 Web API/UI
- 桩保真度:罐头样本数据(从 samples/ 真实脱敏样本抽取)
## 1. 背景与目标
架构审查 17 项整改(T1-T17)已完成,但 `writer/``qa/` 仅有原型/护栏:
- `writer/docx_injector.py`T17):原生 python-docx 占位符注入原型,仅支持 `Block``paragraph/heading/table` 三类
- `qa/guardrails.py`T15):`resolve_qa_model()` 强制 QA 走 fallback 模型 + `QALoopController` 循环边界
- `eval/scorer.py`T13):`ChapterScorer` 确定性维度(traceability/placeholder_residue/chapter_completeness+ LLM 维度钩子(默认中性分)
- `parsers/resolver.py`T12):`validate_source_uris()` 强验证
本阶段补齐 Writer/QA 完整逻辑,并与既有模块(InferenceEngine、DocxInjector、ChapterScorer、QALoopController、resolver)无缝衔接。
### 非目标(本阶段不做)
- Web API 端点(`POST /generate` 等)、WebSocket 推送
- Web UI 改动
- RAG/Impact 真实检索(仅定义清晰接口 + 罐头桩)
- `chapter_html` 前端预览渲染器(仅保证 docx 输出;预览渲染后置)
- 图表/chart 生成、Word 交叉引用(`REF` 域)渲染(本阶段不覆盖,列为已知缺口;`ContentBlock` 无 image/chart/diagram/cross-ref 类型)
## 2. 架构与数据流
```
samples/
├─ 要件定義_*.xlsx → SourceParser(既有) → StructuredSource
├─ 概要設計書テンプレート.docx → WordTemplateParser(既有) → ParsedTemplate
└─ 記入規則.docx / 做成説明書.docx → RuleDocParser(既有) → rule_docs
GenerationContext 聚合(per chapter:
structured_source 子集 + write_rules[](RagService桩) + design_rules[](RagService桩)
+ template_styles + prior_state(WriterState)
WriterAgent(逐章串行,async,见 §5 T10 约束)
engine.chat_structured(session_id=..., prompt=..., variables=..., schema=CONTENT_BLOCK_SCHEMA, retry_count=2) → ChapterContent
resolver.validate_source_uris 校验 → 失败计入 block 元信息(QA 捕获)
rendererChapterContent[].blocks → DocxInjector.Block[] → DocxInjector.inject → final.docx
(扩展 T17 DocxInjector 支持 list/note 两类 kind
QAValidator.run(chapters: ChapterArtifact[], source) → EvalReport
确定性维度 → ChapterScorer
LLM 语义维度(准确/幻觉/规则遵守)→ engine.chat(model=resolve_qa_model(models)) 构造 llm_evaluators
qa_looprun_qa_loop(writer, qa, template, source, engine)
生成全章 → 渲染 docx → QA → 若 fail:仅对失败章 WriterAgent.regenerate_chapter(v+1, feedback) → 仅重渲染失败章 → 重QA
受 QALoopController(max_rounds=3) 约束(T15 OV6
```
## 3. 模块契约
### 3.1 `writer/models.py`(新)
```python
@dataclass
class ContentBlock:
block_id: str
type: Literal["paragraph", "heading", "table", "list", "note"]
level: int | None = None # 仅 heading
text: str | None = None
caption: str | None = None # table
headers: list[str] | None = None # table
rows: list[list[str]] | None = None # table
items: list[str] | None = None # list
style: str | None = None # list: bullet|numbered
source_uris: list[str] = field(default_factory=list)
@dataclass
class ChapterContent:
chapter_id: str
version: int
title: str
blocks: list[ContentBlock]
@dataclass
class GenerationContext:
chapter_id: str
title: str
template_marker: ChapterMarker # 来自 ParsedTemplate
structured_source: StructuredSource
write_rules: list[str]
design_rules: list[str]
template_styles: set[str]
prior_state: WriterState | None = None
```
### 3.2 `writer/writer_state.py`(新)
- `WriterState`:内存会话级,跨章共享
- `add(chapter_id, summary: str, tables: list[dict])`:记录已完成章摘要与关键表结构
- `get_prior() -> str`:返回前章摘要拼接文本,供后章 prompt 注入(§6.9 章间引用)
- `summary_for(chapter_id) -> str | None`
### 3.3 `writer/template_mapper.py`(新)
- `map_template(parsed: ParsedTemplate) -> list[ChapterSpec]`
- 按模板 Heading 层级顺序产出有序章节列表
- 每章含 `chapter_id`slug)、`title``section_placeholder`(如 `{{section:db_design}}`,无则 None 回落 Heading 定位)
- 输出驱动 WriterAgent 串行顺序(§6.8.1
### 3.4 `writer/writer_agent.py`(新,async
- `async def generate_chapter(ctx: GenerationContext, engine: InferenceEngine) -> ChapterContent`
- 拼装 prompt(系统指令恒定 + 用户数据边界包裹,复用 engine 防护)
- `engine.chat_structured(session_id=..., prompt=..., variables=..., schema=CONTENT_BLOCK_SCHEMA, retry_count=2)`(对齐既有 `InferenceEngine` 真实签名:必填 `session_id`,数据经 `variables` 承载,非塞入 prompt 字符串)
- **单章 token 预算**:引擎 `chat_structured` 内部 `max_tokens=4096` 硬编码;WriterAgent 须对长章做内容预算与分块生成(按 Block 分组多次调用后合并),或放宽引擎配置。超限截断须在单测中覆盖(json 解析失败→重试→耗尽抛 `WriterGenerationError`
- 解析 → `resolver.validate_source_uris(all_uris, ctx.structured_source)` 校验(不阻断,记录 unresolved
- 返回 `ChapterContent(version=1)`
- `async def regenerate_chapter(ctx, engine, feedback: str) -> ChapterContent`
- 同流程,prompt 注入 QA 反馈,`version += 1`
- 串行由调用方(qa_loop / 离线条到端)保证,agent 本身单章单次 LLM 调用
### 3.5 `writer/renderer.py`(新)
- `render_docx(template_path: str, chapters: list[ChapterContent], meta: dict[str,str]) -> Document`
- 每章 `ChapterContent.blocks``list[Block]`kind 映射:paragraph→paragraph, heading→heading(level), table→table(rows), list→list, note→note
- `sections` 的 key 由 `ChapterContent.chapter_id``template_mapper` 产出的 `section_placeholder` 映射得到;`section_placeholder` 为 None 时回落以 Heading 文本定位并记 `mapping_miss` 告警;缺失占位符在抛 `DocxInjectError` 前先记录供 QA 捕获(修正外视#5chapter_id→占位符桥缺失)
- 映射保真声明:`table.headers`/`table.caption``list.items`/`list.style` 映射至 `Block(rows/text)` 时**显式丢弃**,并在单测中断言丢弃行为(外视#6);保真扩展 `Block` 字段不在本阶段
- 调用 `DocxInjector(template_path).inject(sections, meta)`
- **扩展 T17 DocxInjector**`Block.kind` 新增 `list`/`note` 支持
- `list`:逐 item 生成 `doc.add_paragraph(item, style="List Bullet"|"List Number")`
- `note`:生成带「注記」语义的段落——优先使用模板中名为 `Note`/`Intense Quote` 的样式;若模板无对应样式则降级为普通段落并加「※ 」前缀
- 扩展以 TDD 方式验证,不破坏既有 paragraph/heading/table
### 3.6 `services/rag_service.py`(新)
- `class RagService(Protocol/ABC)`
- `async def retrieve_write_rules(chapter_id: str) -> list[str]`
- `async def retrieve_design_rules(chapter_id: str) -> list[str]`
- `class CannedRagService(RagService)`:从 `samples/` 抽罐头规则文本(如读 `記入規則.docx` 经 RuleDocParser 转 Markdown,按章节切片或整体返回),供离线条到端真实感演示
### 3.7 Impact 影响分析(本阶段不实现)
-`ImpactService` 桩已删除(评审决定:Impact 属设计 non-goals,桩会伪造 `cross_refs` 却无渲染类型,具误导性)。
- `GenerationContext.impact` 字段已移除;RAG 检索仅返回 write/design 规则,不含 impact。
- 真实 Impact 实现后置,届时独立成模块。
### 3.8 `qa/validator.py`(扩 T15
- `class QAValidator`
- `__init__(self, engine: InferenceEngine, models, scorer: ChapterScorer | None = None)`
- `async def run(self, chapters: list[ChapterArtifact], source: StructuredSource) -> EvalReport`
- 确定性维度:委托 `ChapterScorer`(传入 chapters 的 text/source_uris/template_sections_expected
- LLM 语义维度:构造 `llm_evaluators` dict,每个语义维度一个闭包,闭包内 `await engine.chat(model=resolve_qa_model(models), ...)` 判定 pass/fail → `DimensionScore`
- 无真实 LLMFakeLLMClient)时,闭包按脚本返回中性/预期分(与 T13 钩子契约一致)
- `EvalReport` 须包含逐章维度结果:`chapter_results: dict[str, list[DimensionScore]]`(每章每项维度 pass/fail + feedback),供 qa_loop 定位失败章(修正外视#2:原仅整体 `passed`)。`passed = all(章) all(维度) passed`
- **LLM 语义维度本阶段为探针/占位**:无真实 LLM 时退化为中性分(与 T13 钩子一致),headless e2e 用 FakeLLM 恒 pass **不视为质量验证**(外视#4
### 3.9 QA 循环(复用 `QALoopController`,不新建 `qa/qa_loop.py`
- 复用既有 `qa/qa_loop_controller.QALoopController`T15)管控 `max_rounds=3` 边界;不新建独立 `qa/qa_loop.py`(评审决定:与既有循环功能重叠,DRY)。
- `async def run_qa_loop(writer, qa, template_path, source, engine, meta, max_rounds=3) -> QAReport`:对 `QALoopController` 的适配封装
- 首轮:生成全章 → renderer.render_docx → 构造 ChapterArtifact[] → qa.run
-`EvalReport.passed`:返回成功报告
- 否则:据 `EvalReport.chapter_results` 收集**失败章** feedback → 仅对失败章 `writer.regenerate_chapter`(version+1)→ 仅重渲染失败章 → 重QA(修正 §2/§3.9 矛盾:采用增量仅重失败章,不每轮全章重生成)
- 达上限仍 fail:返回报告(passed=False,附轮次数与人工介入提示)
### 3.10 `qa/report.py`(新)
- `QAReport`:封装 `EvalReport` + 轮次信息(`rounds: int`, `regenerated_chapters: list[str]`
- `to_json() -> dict`:供 §7.6 设计书附 QA 报告 JSON
## 4. 错误处理
| 场景 | 处理 |
|------|------|
| engine.chat_structured 失败(status=failed | 抛 `WriterGenerationError`,qa_loop 捕获并记为该章生成失败,计入报告 |
| source_uri 校验 unresolved | 不阻断生成,记录于 blockQA traceability 维度扣分 |
| DocxInjector 残留 `{{...}}` | 抛 `DocxInjectError`,上浮 qa_loop,标记渲染失败 |
| QA 循环达 `max_rounds` 仍 fail | 停循环,报告 `passed=False` + `needs_human=True` + 轮次数(OV6 护栏) |
| fallback 模型不可用 | `resolve_qa_model` 返回 None 时,QA 语义维度退化为中性分并记录告警(不静默回退 primary) |
| 单章 `chat_structured` 截断(引擎 `max_tokens=4096` 超限) | WriterAgent 须做内容预算/分块生成(按 Block 分组多次调用后合并);json 解析失败→重试→耗尽抛 `WriterGenerationError` |
新增异常:`writer/exceptions.py``WriterGenerationError`
QA 循环耗尽**不新增独立异常**,由 `QAReport(passed=False, rounds=max_rounds, needs_human=True)` 标记(与 T15 `QALoopController.is_exhausted()` 一致)。
## 5. 设计对齐与文档同步
- **docxtpl → 原生 python-docx**design.md §6.3「python-docx / docxtpl」与 §6.6「docxtpl 占位符语法」正式对齐 T17 已落地的 `DocxInjector``{{section:id}}` / `{{meta}}` 原生注入),删除 docxtpl 依赖描述
- §6.4 ContentBlock JSON schema 正式化为 `writer/models.py``ContentBlock` 字段
- §7 QA 章节补:validator 委托 `ChapterScorer` + LLM 语义走 `resolve_qa_model`qa_loop 实现 §7.4 闭环
- T10 串行约束(§6.8.1)在 qa_loop / 离线条到端中得到落实
## 5.1 实施顺序:垂直切片里程碑(评审新增)
为规避「在桩上硬化完整闭环却未验证产品可做出来」的风险(外视#10),本阶段实施分两步,不删减已批准范围:
1. **垂直切片(先做)**:选 2-3 章,跑通「真实 `CannedRagService` 检索 + 真实 LLM`InferenceEngine`,非 Fake)生成 + `DocxInjector` 注入 + 人工质量判定」。验证核心命题:RAG 检索质量与 LLM 能否产出合规章节。
2. **闭环硬化(后做)**:基于切片验证结果,再完成 `QALoopController` 闭环、确定性+语义维度 QA、headless e2eFakeLLM 仅验证管线)。
垂直切片通过人工评审后方可进入第 2 步。
## 6. 测试策略(TDD,全离线)
所有 LLM 调用经 `FakeLLMClient`(支持异步、记录被调模型以验证 fallback)。
| 模块 | 测试要点 |
|------|---------|
| writer/models | 数据类构造与默认值(轻量) |
| writer_state | add/get_prior/summary_for 跨章累积 |
| template_mapper | ParsedTemplate → 有序章节 + section 占位符映射 |
| writer_agent | 脚本化返回 ContentBlock JSON → 断言 ChapterContentsource_uri 校验;regenerate version+1 |
| renderer | ChapterContent→Block 映射;扩展 DocxInjector list/note 渲染;断言 docx 含预期 heading/table |
| services | CannedRagService/ImpactService 返回罐头样本数据 |
| qa/validator | 确定性维度(scorer 对样本 artifacts);LLM 语义维度经 FakeLLMClient(fallback) 返回 pass |
| qa/qa_loop | 模拟 1 次 fail→pass,验证 3 轮上限与最终报告 passed |
| **headless e2e** | load samples(新規開発 xlsx + 模板 + 规则)→ parse → build contexts → 生成全章 → 渲染 docx → qa_loop → 断言报告通过且 docx 非空(FakeLLM 恒 pass 仅验证管线连通性,**不验证生成质量**) |
| **人工评审样本集** | 2-3 章真实 RAG+LLM 输出 + 人工判定合格,作为可用性证据(区别于 FakeLLM 假绿,外视#4 |
覆盖率维持 fail_under=99 / 目标 100%。
## 7. 交付物
- `src/genesis/services/{__init__,rag_service}.py`Impact 模块本阶段删除)
- `src/genesis/writer/{models,writer_state,template_mapper,writer_agent,renderer,exceptions}.py`docx_injector.py 扩展)
- `src/genesis/qa/{validator,report,exceptions}.py`(循环复用既有 `QALoopController`,不新建 `qa_loop`
- `tests/test_phase5_*.py`(含 headless e2e
- `docs/design.md` §6/§7 同步修订
- `_AI_USAGE_LOG.md` 逐条登记
---
## GSTACK REVIEW REPORT
> 评审方式:`/plan-eng-review`FULL_REVIEW)。因本环境无 gstack CLI/CodexOutside Voice 回退为 Claude 子代理(已实际核对 `inference/engine.py`、`writer/docx_injector.py`、`eval/scorer.py`、`qa/guardrails.py` 真实源码),`gstack-review-log`/dashboard 步骤跳过并显式注明。
| Review | Trigger | Why | Runs | Status | Findings |
|--------|---------|-----|------|--------|----------|
| CEO Review | `/plan-ceo-review` | Scope & strategy | 0 | not run | — |
| Codex Review | `/codex review` | Independent 2nd opinion | 0 | not run (no codex in env) | — |
| Eng Review | `/plan-eng-review` | Architecture & tests (required) | 1 | issues_found → resolved | 14 findings (ARCH×7, CQ×3, Test gaps, PERF×2);全部经 4 项决策落地修正 |
| Design Review | `/plan-design-review` | UI/UX gaps | 0 | not run (backend-only) | — |
| DX Review | `/plan-devex-review` | Developer experience gaps | 0 | not run | — |
**OUTSIDE VOICE (Claude subagent):** 10 条挑刺,与 Eng Review 交叉验证并扩展——核心共识:在桩上硬化闭环 + chapter_id→占位符桥缺失 + ContentBlock→Block 字段塌缩 + 图表/cross-ref 类型缺失 + LLM 语义 QA 假绿。无张力,两项评审一致建议复用现有模块并诚实标注范围。
**REQUIRED OUTPUTS:**
- **NOT in scope(明确)**:图表/chart 生成、Word 交叉引用(`REF` 域)渲染、Impact 影响分析、LLM 语义 QA 真实质量评估(本阶段为探针)。
- **What already exists(应复用,勿重建)**:`QALoopController`(qa循环,取代新 qa/qa_loop)、`EventBus`(事件)、`ChapterScorer`(打分)、`DocxInjector`(注入)、`InferenceEngine`(LLM)、`resolver`(来源解析)、`WordTemplateParser`(模板结构)。
- **Failure modes**:①长章 token 截断→已加内容预算/分块+单测覆盖;②映射桥错配→先记 `mapping_miss` 再抛 `DocxInjectError`;③语义QA假绿→明确 e2e 仅验管线、质量以人工样本集为准。0 个 critical gap。
- **Parallelization**Lane A `services/rag_service`+`writer/models`+`template_mapper`(独立);Lane B `writer_agent`+`renderer`(依赖A);Lane C `qa/`(依赖B)。A 并行,B→C 串行。
- **Implementation Tasks**T1 对齐 engine 真实签名;T2 统一仅重失败章;T3 EvalReport 逐章结果;T4 chapter_id→placeholder 桥;T5 Block 字段塌缩显式丢弃+测试;T6 删除 ImpactServiceT7 复用 QALoopControllerT8 单章 token 分块;T9 图表/cross-ref 列已知缺口;T10 插入垂直切片里程碑;T11 人工评审样本集。
**VERDICT:** ENG REVIEWED — spec 已据 4 项决策修正并批准进入实现(垂直切片优先)。CEO/Design 评审对纯后端 spec 为可选。
NO UNRESOLVED DECISIONS