feat(writer): 输出语言一致性保障 + 用户可选输出语言

- 步骤0: 新增中文镜像模板 scripts/make_zh_template.py 与 samples/概要设计书模板_中文.docx(7章锚点原样保留)
- 步骤1: config.WriterConfig.output_language→Settings.writer;GenerationContext.output_language + to_vars.language_instruction(zh/ja/auto);writer_agent【语言约束】改引变量;context_builder/orchestrator/run_trial 透传 --output-language
- 步骤A: 新建 src/genesis/writer/language.py(detect_script/resolve_expected_language/find_language_violations);WriterAgent.generate_chapter 按期望语言强制、违规重试、耗尽硬失败;max_retries 默认 1→2
- 步骤B: _format_impact 影响调查标签按 output_language 本地化(zh 新建/变更/删除/警告)
- 步骤C: eval scorer 第 11 维度 language_consistency(不可验证=满分,不拉低总分);ChapterArtifact.expected_language;QAValidator.validate_doc 透传;QALoop.run 透传 output_language
- 测试: test_zh_template/test_language_plumbing/test_writer_language/test_scorer_language/test_language_coverage,并更新 test_phase5_e2e
- 全量 pytest 424 passed / 99.15%(覆盖率门槛 99% 达标)
This commit is contained in:
lhl
2026-08-25 23:30:24 +08:00
parent 9d0d3409c2
commit d01e1b720f
20 changed files with 1049 additions and 31 deletions
+82
View File
@@ -0,0 +1,82 @@
"""生成中文镜像模板(步骤 0)。
镜像既有日文概要设计书模板的结构与锚点 id(section:xxx 原样保留),
仅将 H1/H2 标题与封面字段翻译为中文,产物 samples/概要设计书模板_中文.docx 入库。
锚点段落文本保持不变是硬约束:Writer 子系统(template_mapper / docx_injector
依赖这些占位符定位章节,任何改动都会破坏映射与注入。
"""
from __future__ import annotations
import shutil
from pathlib import Path
from docx import Document
# H1/H2 标题日文 → 中文(保持编号前缀一一对应)
HEADING_TRANSLATIONS: dict[str, str] = {
"1. はじめに": "1. 前言",
"2. 機能一覧": "2. 功能一览",
"2.1 機能一覧表": "2.1 功能一览表",
"2.2 機能詳細": "2.2 功能详细",
"3. 画面一覧": "3. 画面一览",
"3.1 画面遷移図": "3.1 画面迁移图",
"3.2 画面一覧表": "3.2 画面一览表",
"4. 帳票一覧": "4. 报表一览",
"5. DB設計": "5. DB设计",
"5.1 テーブル一覧": "5.1 表一览",
"5.2 ER図": "5.2 ER图",
"6. IF定義": "6. 接口定义",
"7. バッチ一覧": "7. 批处理一览",
}
# 封面字段(Normal 段落)日文 → 中文
COVER_TRANSLATIONS: dict[str, str] = {
"概要設計書": "概要设计书",
"ドキュメント名: {{doc_title}}": "文档名: {{doc_title}}",
"バージョン: {{version}}": "版本: {{version}}",
"作成日: {{created_at}}": "创建日期: {{created_at}}",
"テンプレート開始": "模板开始",
}
def _translate(text: str) -> str:
"""锚点段落({{section:...}})原样保留;其余按字典翻译,未命中则原样。"""
if text.startswith("{{section:"):
return text
return HEADING_TRANSLATIONS.get(text, COVER_TRANSLATIONS.get(text, text))
def build_zh_template(src: str, dst: str) -> str:
"""镜像 src(日文模板)为 dst(中文模板),返回 dst 路径。"""
src_path = Path(src)
dst_path = Path(dst)
if not src_path.exists():
raise FileNotFoundError(f"源模板不存在: {src_path}")
dst_path.parent.mkdir(parents=True, exist_ok=True)
# 先物理复制(保留样式/编号定义等所有非文本属性),再逐段改写文本
shutil.copyfile(str(src_path), str(dst_path))
doc = Document(str(dst_path))
for para in doc.paragraphs:
new_text = _translate(para.text)
if new_text != para.text:
para.text = new_text
doc.save(str(dst_path))
return str(dst_path)
def main() -> None:
import argparse
parser = argparse.ArgumentParser(description="生成中文镜像模板")
default_src = str(Path(__file__).resolve().parents[1] / "samples" / "概要設計書テンプレート.docx")
default_dst = str(Path(__file__).resolve().parents[1] / "samples" / "概要设计书模板_中文.docx")
parser.add_argument("--src", default=default_src, help="源日文模板")
parser.add_argument("--dst", default=default_dst, help="输出中文模板")
args = parser.parse_args()
out = build_zh_template(args.src, args.dst)
print(f"中文模板已生成: {out}")
if __name__ == "__main__":
main()
+3
View File
@@ -53,6 +53,8 @@ def _parse_args(argv: list[str] | None) -> argparse.Namespace:
p.add_argument("--existing-system", default=str(ROOT / "samples" / "existing-system"),
help="既有系统源码目录(追加/改修场景)")
p.add_argument("--language", default="java", help="既有系统开发语言(默认 java,None=自动探测)")
p.add_argument("--output-language", default="auto",
choices=["auto", "zh", "ja"], help="输出语言:auto=与标题一致,zh=简体中文,ja=日文")
p.add_argument("--samples-dir", default=str(ROOT / "samples"), help="样本资产根目录")
p.add_argument("--output", default=str(ROOT / "output" / "output.docx"), help="概要设计书输出路径")
p.add_argument("--impact-report", default=str(ROOT / "output" / "impact-report.json"),
@@ -86,6 +88,7 @@ def main(argv: list[str] | None = None) -> dict:
samples_dir=args.samples_dir,
engine=engine,
template_path=str(Path(args.template)),
output_language=args.output_language,
)
report = ss.impact_report