feat(run): 试运行脚本 + 运行说明(真实 LLM 默认 / --fake 离线)

- scripts/run_trial.py:库级端到端驱动(argparse 默认指向 samples/ 股票追加改修+sunOnly+真实模板)
  - --fake 离线 FakeEngine(无 key)/ 默认 build_inference_engine 真实 LLM(读 .env)
  - SourceParser → WriteOrchestrator 门控自动影响调查 → output/output.docx + impact-report.json
  - 暴露 main(argv) 供测试
- tests/test_run_trial.py(fake 模式断言 13 章 + summary 16/5/8/3/50/0 + 文件产出 + docx 注入)
- .gitignore 新增 output/(试运行产物不入库)
- 新建 README.md(安装 / .env 配置 / 离线与真实试运行 / 输出 / 测试)
- 全量 364 passed / 99.28%;fake 试运行 PASS(13 章、summary 与 MVP 基线一致)
This commit is contained in:
lhl
2026-08-24 11:05:45 +08:00
parent 61ac47c7af
commit 6372bb17b9
5 changed files with 218 additions and 0 deletions
+115
View File
@@ -0,0 +1,115 @@
"""库级端到端试运行驱动(真实 LLM / 离线 Fake)。
用法:
python scripts/run_trial.py [--fake] [--output out.docx] ...
默认输入为 samples/ 下既有样本(追加改修·股票场景 + sunOnly 既有系统 + 真实概要设计书模板)。
真实模式需先在 .env 配置 GENESIS_INFERENCE__API_KEY(见 README「运行说明」),
缺失时抛 LLMNotConfiguredError 并给出配置提示;--fake 为离线确定性引擎,无需 key。
输出:
概要设计书 docx + 影响调查书 impact-report.json(默认写至 output/,不入库)。
"""
from __future__ import annotations
import argparse
import json
import sys
from pathlib import Path
from types import SimpleNamespace
ROOT = Path(__file__).resolve().parents[1]
if str(ROOT / "src") not in sys.path:
sys.path.insert(0, str(ROOT / "src"))
from genesis.impact.impact_agent import impact_report_to_dict # noqa: E402
from genesis.parsers.source_aggregator import SourceParser # noqa: E402
from genesis.writer.orchestrator import WriteOrchestrator # noqa: E402
class FakeEngine:
"""离线确定性引擎:章节正文固定为占位文本(用于无 key 验证整条管线)。"""
def chat_structured(self, *, session_id, prompt, variables, schema, retry_count=2):
return SimpleNamespace(
data={
"title": variables["title"],
"blocks": [{"type": "paragraph", "text": "自动生成内容"}],
},
status="ok",
)
def _parse_args(argv: list[str] | None) -> argparse.Namespace:
p = argparse.ArgumentParser(description="概要设计书自动生成试运行")
p.add_argument("--requirement", default=str(ROOT / "samples" / "要件定義_追加改修_股票.xlsx"),
help="要件定义 Excel(可多个,逗号分隔)")
p.add_argument("--template", default=str(ROOT / "samples" / "概要設計書テンプレート.docx"),
help="概要设计书 Word 模板")
p.add_argument("--rules", nargs="*", default=[
str(ROOT / "samples" / "概要設計做成説明書.docx"),
str(ROOT / "samples" / "記入規則.docx"),
], help="写入规则/记入规则 docx(可多个)")
p.add_argument("--existing-system", default=str(ROOT / "samples" / "existing-system"),
help="既有系统源码目录(追加/改修场景)")
p.add_argument("--language", default="java", help="既有系统开发语言(默认 java,None=自动探测)")
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"),
help="影响调查书 JSON 输出路径")
p.add_argument("--fake", action="store_true", help="离线 Fake 引擎(无需 API key")
return p.parse_args(argv)
def main(argv: list[str] | None = None) -> dict:
args = _parse_args(argv if argv is not None else sys.argv[1:])
if args.fake:
engine = FakeEngine()
else:
from genesis.inference.factory import build_inference_engine # noqa: E402
engine = build_inference_engine()
ss = SourceParser().parse(
requirement_paths=[args.requirement],
template_path=args.template,
rule_paths=list(args.rules),
existing_system_path=args.existing_system,
existing_system_language=args.language,
)
out_path = Path(args.output)
out_path.parent.mkdir(parents=True, exist_ok=True)
orch = WriteOrchestrator()
contents = orch.generate(
ss, str(out_path),
samples_dir=args.samples_dir,
engine=engine,
template_path=str(Path(args.template)),
)
report = ss.impact_report
if report is None:
raise RuntimeError("影响调查未执行(未提供既有系统或门控未触发)")
report_path = Path(args.impact_report)
report_path.parent.mkdir(parents=True, exist_ok=True)
report_path.write_text(
json.dumps(impact_report_to_dict(report), ensure_ascii=False, indent=2),
encoding="utf-8",
)
summary = dict(report.summary)
print(f"章节数: {len(contents)}")
print(f"影响调查: {json.dumps(summary, ensure_ascii=False)}")
print(f"概要设计书: {out_path}")
print(f"影响调查书: {report_path}")
return {
"chapters": len(contents),
"summary": summary,
"output": str(out_path),
"impact_report": str(report_path),
}
if __name__ == "__main__":
main()