docs: Phase 5 Writer/QA 完整实现设计规格(方案A,离线条到端)

This commit is contained in:
lhl
2026-08-12 23:32:26 +08:00
parent 0b704fb07e
commit 0b16aeab49
@@ -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 捕获)
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 → 若 failWriterAgent.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`
- 无真实 LLMFakeLLMClient)时,闭包按脚本返回中性/预期分(与 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 | 不阻断生成,记录于 blockQA 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 → 断言 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 非空 |
覆盖率维持 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` 逐条登记