diff --git a/docs/superpowers/specs/2026-08-12-phase5-writer-qa-design.md b/docs/superpowers/specs/2026-08-12-phase5-writer-qa-design.md new file mode 100644 index 0000000..bb2a99e --- /dev/null +++ b/docs/superpowers/specs/2026-08-12-phase5-writer-qa-design.md @@ -0,0 +1,195 @@ +# 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 输出;预览渲染后置) + +## 2. 架构与数据流 + +``` +samples/ + ├─ 要件定義_*.xlsx → SourceParser(既有) → StructuredSource + ├─ 概要設計書テンプレート.docx → WordTemplateParser(既有) → ParsedTemplate + └─ 記入規則.docx / 做成説明書.docx → RuleDocParser(既有) → rule_docs + +GenerationContext 聚合(per chapter): + structured_source 子集 + write_rules[](RagService桩) + design_rules[](RagService桩) + + impact(ImpactService桩) + template_styles + prior_state(WriterState) + +WriterAgent(逐章串行,async,见 §5 T10 约束) + engine.chat_structured(schema=CONTENT_BLOCK_SCHEMA) → ChapterContent + resolver.validate_source_uris 校验 → 失败计入 block 元信息(QA 捕获) + +renderer:ChapterContent[].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_loop:run_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] + impact: ImpactReport # 桩 + 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(prompt, schema=CONTENT_BLOCK_SCHEMA, retry_count=2)` + - 解析 → `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) + - 调用 `DocxInjector(template_path).inject(sections, meta)` +- **扩展 T17 DocxInjector**:`Block.kind` 新增 `list`/`note` 支持 + - `list`:逐 item 生成 `doc.add_paragraph(item, style="List Bullet"|"List Number")` + - `note`:生成带注释样式段落(如 `doc.add_paragraph(text, style="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 `services/impact_service.py`(新) +- `class ImpactService(ABC)`:`async def get_impact(chapter_id: str) -> ImpactReport` +- `ImpactReport` 数据类(桩,字段:`chapter_id`, `cross_refs: list[dict]`) +- `class CannedImpactService(ImpactService)`:返回样例跨章关联(空或固定示例),真实 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` + - 无真实 LLM(FakeLLMClient)时,闭包按脚本返回中性/预期分(与 T13 钩子契约一致) + +### 3.9 `qa/qa_loop.py`(新) +- `async def run_qa_loop(writer, qa, template_path, source, engine, meta, max_rounds=3) -> QAReport` + - 用 `QALoopController(max_rounds)` 管控 + - 每轮:生成全章(writer.generate_chapter 串行)→ renderer.render_docx → 构造 ChapterArtifact[] → qa.run + - 若 `EvalReport.passed`:返回成功报告 + - 否则:收集 fail 维度 feedback → `writer.regenerate_chapter` 仅重生成失败章(version+1)→ 重渲染 → 重QA + - 达上限仍 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 | 不阻断生成,记录于 block,QA traceability 维度扣分 | +| DocxInjector 残留 `{{...}}` | 抛 `DocxInjectError`,上浮 qa_loop,标记渲染失败 | +| QA 循环达 `max_rounds` 仍 fail | 停循环,报告 `passed=False` + 人工介入提示(OV6 护栏) | +| fallback 模型不可用 | `resolve_qa_model` 返回 None 时,QA 语义维度退化为中性分并记录告警(不静默回退 primary) | + +新增异常:`writer/exceptions.py` → `WriterGenerationError`;`qa/exceptions.py` → `QALoopExhaustedError`(可选,或复用报告标记)。 + +## 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 / 离线条到端中得到落实 + +## 6. 测试策略(TDD,全离线) + +所有 LLM 调用经 `FakeLLMClient`(支持异步、记录被调模型以验证 fallback)。 + +| 模块 | 测试要点 | +|------|---------| +| writer/models | 数据类构造与默认值(轻量) | +| writer_state | add/get_prior/summary_for 跨章累积 | +| template_mapper | ParsedTemplate → 有序章节 + section 占位符映射 | +| writer_agent | 脚本化返回 ContentBlock JSON → 断言 ChapterContent;source_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 非空 | + +覆盖率维持 fail_under=99 / 目标 100%。 + +## 7. 交付物 + +- `src/genesis/services/{__init__,rag_service,impact_service}.py` +- `src/genesis/writer/{models,writer_state,template_mapper,writer_agent,renderer,exceptions}.py`(docx_injector.py 扩展) +- `src/genesis/qa/{validator,qa_loop,report,exceptions}.py` +- `tests/test_phase5_*.py`(含 headless e2e) +- `docs/design.md` §6/§7 同步修订 +- `_AI_USAGE_LOG.md` 逐条登记