#1/#2 真实 LLM 随机方差兜底:orchestrator.generate 新增 chapter_attempts(默认 3), 单章 WriterGenerationError 不连坐整次 run,重试耗尽才抛错(不吞错)。新增 tests/test_orchestrator_retry.py(前 N 次失败后恢复 / 耗尽仍抛错)。 #3 表格 caption 语言检查:find_language_violations 现检 table.caption(生成正文需跟随 输出语言),rows/headers 仍照抄源不检;QA 维度经 ChapterArtifact.blocks=(type,text,caption) 同步生效。修复真实 zh 输出中表格说明引用日文源表名绕过强制的问题。 文档收尾:README 新增 --output-language/中文模板用法;design.md §6.2.1 输出语言控制、 §7.2 校验清单 10→11 项;计划验收勾选;.gitignore 加 .opencode/。 全量 pytest 431 passed / 99.15%
173 lines
13 KiB
Markdown
173 lines
13 KiB
Markdown
# 输出语言一致性保障 + 用户可选输出语言(2026-08-24)
|
||
|
||
## 背景
|
||
|
||
真实 LLM 试运行暴露:生成正文曾出现中日混杂(早期第 1-2 章中文、第 3-7 章日文;prompt 渲染缺陷
|
||
修复后全日文,但属概率性改善)。盘点确认:
|
||
|
||
1. **唯一保障是 prompt 层【语言约束】**,无任何程序化检测(grep 全代码库证实)
|
||
2. **输入侧存在中文诱导源**:`_format_impact()` 固定标签(`受影响:`、`[警告]`)进入每章 prompt
|
||
3. design.md §7.2 QA 十项清单**没有语言一致性校验项**
|
||
4. 日文章标题多为纯汉字(「5. DB設計」无假名),仅凭标题无法判定期望语言
|
||
|
||
用户需求升级:**让用户可选择输出中文还是日文**(参数化),并配套中文模板。
|
||
|
||
## 关键用户决策(已确认)
|
||
|
||
| # | 决策点 | 结论 |
|
||
|---|--------|------|
|
||
| 1 | Excel 表格数据是否翻译 | **照抄原文**——数据是源数据忠实呈现,翻译违反 §7.2 内容准确性/可追溯性 |
|
||
| 2 | 子节标题语言 | **双模板方案**——提供中文/日文两套模板,按用户选择使用;锚点 id 语言中立不变 |
|
||
| 3 | 违规处理 | 重试耗尽后**硬失败**(WriterGenerationError),与既有失败语义一致 |
|
||
| 4 | 实施范围 | A(强制校验)+ B(impact 标签本地化)+ C(QA 第 11 维度)+ 参数贯通,全做 |
|
||
|
||
## 核心设计:模板 × 语言参数 双轨制
|
||
|
||
```
|
||
中文输出 → samples/概要设计书模板_中文.docx(新建镜像)+ --output-language zh
|
||
日文输出 → 既有日文模板 + --output-language ja(或不传,走 auto)
|
||
```
|
||
|
||
| 层 | 日文模板(现状) | 中文模板(新增镜像) |
|
||
|---|---|---|
|
||
| H1/H2 标题 | 1. はじめに / 2.1 機能一覧表 | 1. 前言 / 2.1 功能一览表 |
|
||
| 锚点 id | `section:function_list` … | **完全相同(不变)** → 映射/注入器零改动 |
|
||
| 封面 | 概要設計書 / バージョン | 概要设计书 / 版本 |
|
||
|
||
## 实施清单(TDD,每项先 RED 后 GREEN)
|
||
|
||
### 0. 中文样本模板
|
||
- 新建 `scripts/make_zh_template.py`:python-docx 镜像日文模板结构
|
||
(Title/H1×7/H2×6/锚点原样/封面字段),产出 `samples/概要设计书模板_中文.docx` 入库
|
||
- 测试:解析 zh 模板断言 7 章 anchor id 与 ja 模板逐一对应
|
||
|
||
### 1. 语言参数贯通
|
||
- `config.py`:新增 `WriterConfig(output_language: "auto"|"zh"|"ja" = "auto")` → `Settings.writer`
|
||
- `models.py`:`GenerationContext.output_language` 字段;`to_vars()` 输出动态
|
||
`{{language_instruction}}`(zh=必须使用中文撰写 / ja=必須日本語で記述 / auto=与标题一致)
|
||
- `writer_agent.py`:【语言约束】段落改引该变量
|
||
- `context_builder.py` / `orchestrator.py` / `run_trial.py`:`--output-language {auto,zh,ja}` 逐层透传
|
||
|
||
### A. 确定性脚本检测 + 失败重试【核心】
|
||
- 新建 `src/genesis/writer/language.py`:
|
||
- `detect_script(text)`:含假名(U+3040–30FF)→ "ja";CJK 汉字零假名 → "zh";否则 None
|
||
- `resolve_expected_language(explicit, title, fallback_texts)`:显式 > 标题假名 > 规则文档主导脚本;
|
||
单一事实来源(A 的重试校验与 C 的 QA 维度共用)
|
||
- `violations(...)`:仅检 paragraph/note/list 正文块;**表格 rows 不检(照抄原文)、heading 不检(跟随模板)**;
|
||
违规判定 = 含 CJK 汉字零假名且长度 ≥12(防误杀短术语)
|
||
- `writer_agent.py`:`generate_chapter` 重试循环内后置校验,违规按生成失败重试;
|
||
`max_retries` 默认 1→2(现默认无重试机会,已确认既有测试均显式传参)
|
||
|
||
### B. `_format_impact()` 标签本地化
|
||
- 按 `output_language` 选标签集(zh=受影响 / ja=影響;auto 默认 ja);只影响 prompt 变量,
|
||
不影响 impact-report.json 独立产物
|
||
|
||
### C. QA 第 11 维度 `language_consistency`
|
||
- `ChapterArtifact.expected_language: str = ""`(上层解析后传入,scorer 不自行推导)
|
||
- `qa/validator.py` 从 orchestrator 透传期望语言;歧义记 unverifiable 计入 detail
|
||
- `DEFAULT_THRESHOLDS["language_consistency"] = 1.0`
|
||
- ⚠️ 已识别风险:新维度改变 score() 总分均值,`test_eval_scorer.py` / `test_phase5_scorer.py`
|
||
具体分数断言需同步修正(诚实集成,不做旁路方法)
|
||
|
||
## 验收标准
|
||
|
||
```bash
|
||
# 中文概要设计书
|
||
python scripts/run_trial.py --template samples/概要设计书模板_中文.docx --output-language zh
|
||
# → 正文全中文、表格照抄日文原文、零中日混杂段落(程序化保证)
|
||
|
||
# 日文(现状默认,行为不变)
|
||
python scripts/run_trial.py
|
||
```
|
||
|
||
1. 全量 pytest ≥99% 覆盖率(当前基线 389 passed / 99.04%)
|
||
2. 真实 LLM 双跑(zh/ja 各一次)+ 扫描脚本确认 output.docx 无违规件
|
||
3. 提交 commit + 追加 `_AI_USAGE_LOG.md`
|
||
|
||
## 涉及文件
|
||
|
||
- 新增:`scripts/make_zh_template.py`、`samples/概要设计书模板_中文.docx`、
|
||
`src/genesis/writer/language.py`、`tests/test_writer_language.py`
|
||
- 修改:`config.py`、`writer/models.py`、`writer/writer_agent.py`、`writer/context_builder.py`、
|
||
`writer/orchestrator.py`、`eval/scorer.py`、`qa/validator.py`、`scripts/run_trial.py`
|
||
- 测试更新:`tests/test_phase5_writer_agent.py`、`tests/test_phase5_models.py`、
|
||
`tests/test_eval_scorer.py`、`tests/test_phase5_scorer.py`、`tests/test_run_trial.py`
|
||
|
||
## 状态
|
||
|
||
- [x] 计划已获用户批准(等待二次确认开工)
|
||
- [x] 计划评审(2026-08-24,对照真实代码逐条核验,见下节)
|
||
- [x] 步骤 0-3 实施
|
||
- [x] 验收
|
||
|
||
## 验收记录(2026-08-25,含真实 LLM 双跑)
|
||
|
||
- pytest 全量:**425 passed / 99.15%**(覆盖率门槛 99% 达标)
|
||
- Fake 模式双模板试运行:zh 模板 + `--output-language zh` → 7 章 docx;ja 模板默认 → 7 章 docx,均无崩溃
|
||
- **真实 LLM 双跑(2026-08-26,.env 已配 key)**:
|
||
- `--template 概要设计书模板_中文.docx --output-language zh` → 7 章 ✓,无强制硬失败
|
||
- ja 模板默认(auto)→ 首次在 function_list 硬失败(期望 zh,BUG),修复 fallback 后重跑成功 7 章 ✓
|
||
- **程序化扫描真实输出**:
|
||
- `zh_real.docx`:48 非空段落,1 处含假名 = **表5-1 表格说明段落**(中文句式 + 引用日文源表名 訂単テーブル/即時行情テーブル)→ 属"表格照抄原文"范畴,判定可接受
|
||
- `ja_real.docx`:47 非空段落,1 处纯汉字 ≥12 = **封面字段「作成日: 2026-08-26」**(模板元数据,非正文)→ 误报
|
||
- 结论:**两输出均无中日混杂正文缺陷**;检测器按设计排除 table/heading 与封面元数据
|
||
- **真实试运行暴露并修复的 BUG**:auto 模式 fallback 误用 impact/data 源数据(含中文元素名 止损风控 等 + 汉字标签无假名)→ 把日文文档误判期望 zh → 硬失败。修复:fallback 改用 **write_rules/design_rules(规则文档,日文含假名)**,并加回归测试 `test_generate_chapter_auto_with_ja_rules_but_chinese_source_data_enforces_ja`
|
||
- 约束链路单测覆盖:writer 重试/硬失败、QA 第 11 维度、validator 透传、qa_loop 透传
|
||
- ⚠️ 说明:ja 真实跑曾出现一次 function_list 随机性硬失败(LLM 单次输出疑似纯汉字段落),重跑即成功 → LLM 输出方差,强制机制按设计重试/硬失败,非确定性缺陷
|
||
|
||
## 实施进度(2026-08-25 暂停于步骤 A 修正点)
|
||
|
||
### 已完成
|
||
- **步骤 0(中文模板)**:`scripts/make_zh_template.py` + `samples/概要设计书模板_中文.docx`(已生成入库)+ `tests/test_zh_template.py`(4 passed)。锚点 id 与 ja 模板逐一对应、译文正确。
|
||
- **步骤 1(参数贯通)**:`config.py` 新增 `WriterConfig.output_language`→`Settings.writer`;`GenerationContext.output_language`+`to_vars().language_instruction`(zh/ja/auto 三档);`writer_agent.py`【语言约束】改引 `{{language_instruction}}`;`context_builder.build_contexts` 透传;`orchestrator.generate` 加 `output_language` 参数;`run_trial.py` 加 `--output-language`。`tests/test_language_plumbing.py`(5 passed)。
|
||
- **步骤 A(检测+强制)部分**:`src/genesis/writer/language.py` 已建(detect_script/has_kana/has_cjk/resolve_expected_language/find_language_violations);`writer_agent.generate_chapter` 已接入期望语言推导 + 违规重试 + `max_retries` 默认 1→2。`tests/test_writer_language.py` 多数通过。
|
||
|
||
### 待修正(暂停点)
|
||
- **`language.py` 的 `resolve_expected_language` 逻辑需修正**:当前实现在标题为纯汉字(如「DB 設計」,无假名)时 `detect_script` 返回 "zh",导致标题被误当作可靠信号、fallback 失效。
|
||
依据计划「显式 > **标题假名** > 规则文档主导脚本」,标题**仅当含假名(detect=="ja")**才可信;纯汉字标题对中/日均可能,应跳过、改看 fallback。
|
||
**修正**:将 `resolve_expected_language` 中标题分支改为 `if detect_script(title) == "ja": return "ja"`(不再用纯汉字标题推导 zh),随后才遍历 fallback_texts,最后返回 ""。
|
||
- 修正后重跑 `tests/test_writer_language.py` 应全绿(已有 2 个用例因该逻辑失败:`test_resolve_falls_back_to_fallback_text`、`test_resolve_unverifiable_when_no_hint`)。
|
||
|
||
### 后续(未动)
|
||
- 步骤 A 通过后→ 步骤 B(`_format_impact` 标签本地化已预埋参数,待补 zh 标签集使其与 ja 实际不同 + 补测试)。
|
||
- 步骤 C:QA 第 11 维度 `language_consistency` + `ChapterArtifact.expected_language` + `validator.validate_doc(expected_language=None)` + `DEFAULT_THRESHOLDS` 加项 + 同步修正 `test_eval_scorer.py`/`test_phase5_scorer.py` 总分断言(按评审 R1:不可验证=满分 1.0)。
|
||
- 验收:全量 pytest ≥99% + zh/ja 双真实试运行 + 提交 + `_AI_USAGE_LOG.md` 追加。
|
||
|
||
### 已落盘但未提交
|
||
- 上述步骤 0/1/A 的源码与测试改动**尚未 git commit**(暂停前未提交)。
|
||
|
||
## 评审记录(2026-08-24)
|
||
|
||
### 核验通过的关键假设
|
||
1. `config.py` 为 pydantic-settings + `extra="ignore"`,新增 `Settings.writer` 字段无 yaml 时取默认值,零风险
|
||
2. 中文模板锚点 id(section:introduction/function_list/screen_list/report_list/db_design/if_definition/batch_list)已从真实日文模板解析确认,镜像后映射/注入器零改动
|
||
3. `_format_impact` 无测试断言"受影响"字样,标签本地化安全;`[新規]/[変更]/[削除]/[警告]` 中日通用保留
|
||
4. `WriterAgent.max_retries` 既有测试均显式传参(2 或 1),默认值 1→2 不破坏测试
|
||
5. prompt 段落名【语言约束】保留、内容改引变量 → 既有模板断言兼容
|
||
|
||
### 评审修正(2 处)
|
||
| # | 原计划 | 修正后 | 依据 |
|
||
|---|--------|--------|------|
|
||
| R1 | C 维度"歧义记 unverifiable 跳过" | **不可验证 = 满分通过(score 1.0)**,维度恒参与均值;detail 注明 "unverifiable" | 实测 `test_eval_scorer.py L154` / `test_phase5_scorer.py L82` 断言 `total_score == 1.0`——若跳过维度或给非满分,既有用例总分断言即破裂;恒定维度数 + 满分兜底可让多数既有断言不变 |
|
||
| R2 | `ChapterArtifact.title: str = ""` 与 `expected_language` 两版表述并存 | 统一为 **仅加 `expected_language: str = ""`**,scorer 完全不自行推导 | 单一事实来源原则,避免 scorer 内再出现一套标题猜测逻辑 |
|
||
|
||
### 评审补充(3 处说明)
|
||
| # | 说明 |
|
||
|---|------|
|
||
| N1 | `validate_doc` 增加**可选** kwarg `expected_language=None`(qa_loop.py L55/L69 与 test_phase5_report/test_phase5_validator 共 4 处调用方零破坏);None 时该章按 unverifiable=满分处理 |
|
||
| N2 | `max_retries` 默认 2 后,orchestrator.py L72 与 qa_loop.py L28 两处真实管线在失败时会多一次 LLM 调用——与 QALoopController 外层循环叠加的成本上界 = 章 × 2 × QA轮次,可接受;实施时在 WriterAgent docstring 标注 |
|
||
| N3 | 运维注意事项写入 README/计划:zh 场景规则文档仍为日文 → auto 回落会判 ja,**中文输出必须显式 `--output-language zh`** |
|
||
|
||
### 遗留风险(接受)
|
||
- auto 模式对纯汉字日文正式文档(无假名标题+无假名规则)无法证明语言——保守跳过强制,由 C 维度报告层呈现 unverifiable
|
||
|
||
## 后续收尾(2026-08-26)
|
||
|
||
- **#1/#2 章级管道重试**:`orchestrator.generate` 新增 `chapter_attempts`(默认 3),单章
|
||
WriterGenerationError 不连坐整次 run,重试耗尽才抛错(不吞错)。真实 LLM 随机方差兜底。
|
||
- **#3 表格 caption 语言检查**:`find_language_violations` 现检 table 的 **caption**(生成正文需跟随
|
||
输出语言);rows/headers 仍不检(照抄源)。QA 维度通过 `ChapterArtifact.blocks=(type,text,caption)`
|
||
同步生效。修复真实 zh 输出中"表5-1 表格说明引用日文源表名"绕过强制的问题。
|
||
- 文档收尾:README 新增 `--output-language` 与中文模板用法;design.md §6.2.1/§7.2 同步(11 项校验);
|
||
`.gitignore` 加入 `.opencode/`。
|