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

17 KiB
Raw Blame History

Phase 5 设计规格:Writer / QA 完整实现

  • 日期:2026-08-12
  • 范围:Writer 子系统 + QA 子系统完整实现;RAG/Impact 检索以桩接口先行(真实检索后置)
  • 集成深度:核心模块 + 离线条到端(不动 Web API/UI)
  • 桩保真度:罐头样本数据(从 samples/ 真实脱敏样本抽取)

1. 背景与目标

架构审查 17 项整改(T1-T17)已完成,但 writer/qa/ 仅有原型/护栏:

  • writer/docx_injector.pyT17):原生 python-docx 占位符注入原型,仅支持 Blockparagraph/heading/table 三类
  • qa/guardrails.pyT15):resolve_qa_model() 强制 QA 走 fallback 模型 + QALoopController 循环边界
  • eval/scorer.pyT13):ChapterScorer 确定性维度(traceability/placeholder_residue/chapter_completeness+ LLM 维度钩子(默认中性分)
  • parsers/resolver.pyT12):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(新)

@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_idslug)、titlesection_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.blockslist[Block]kind 映射:paragraph→paragraph, heading→heading(level), table→table(rows), list→list, note→note
    • sections 的 key 由 ChapterContent.chapter_idtemplate_mapper 产出的 section_placeholder 映射得到;section_placeholder 为 None 时回落以 Heading 文本定位并记 mapping_miss 告警;缺失占位符在抛 DocxInjectError 前先记录供 QA 捕获(修正外视#5:chapter_id→占位符桥缺失)
    • 映射保真声明:table.headers/table.captionlist.items/list.style 映射至 Block(rows/text)显式丢弃,并在单测中断言丢弃行为(外视#6);保真扩展 Block 字段不在本阶段
    • 调用 DocxInjector(template_path).inject(sections, meta)
  • 扩展 T17 DocxInjectorBlock.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.QALoopControllerT15)管控 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.pyWriterGenerationError。 QA 循环耗尽不新增独立异常,由 QAReport(passed=False, rounds=max_rounds, needs_human=True) 标记(与 T15 QALoopController.is_exhausted() 一致)。

5. 设计对齐与文档同步

  • docxtpl → 原生 python-docxdesign.md §6.3「python-docx / docxtpl」与 §6.6「docxtpl 占位符语法」正式对齐 T17 已落地的 DocxInjector{{section:id}} / {{meta}} 原生注入),删除 docxtpl 依赖描述
  • §6.4 ContentBlock JSON schema 正式化为 writer/models.pyContentBlock 字段
  • §7 QA 章节补:validator 委托 ChapterScorer + LLM 语义走 resolve_qa_modelqa_loop 实现 §7.4 闭环
  • T10 串行约束(§6.8.1)在 qa_loop / 离线条到端中得到落实

5.1 实施顺序:垂直切片里程碑(评审新增)

为规避「在桩上硬化完整闭环却未验证产品可做出来」的风险(外视#10),本阶段实施分两步,不删减已批准范围:

  1. 垂直切片(先做):选 2-3 章,跑通「真实 CannedRagService 检索 + 真实 LLMInferenceEngine,非 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}.pyImpact 模块本阶段删除)
  • src/genesis/writer/{models,writer_state,template_mapper,writer_agent,renderer,exceptions}.pydocx_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-reviewFULL_REVIEW)。因本环境无 gstack CLI/CodexOutside Voice 回退为 Claude 子代理(已实际核对 inference/engine.pywriter/docx_injector.pyeval/scorer.pyqa/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。
  • ParallelizationLane A services/rag_service+writer/models+template_mapper(独立);Lane B writer_agent+renderer(依赖A);Lane C qa/(依赖B)。A 并行,B→C 串行。
  • Implementation TasksT1 对齐 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