"""库级端到端试运行驱动(真实 LLM / 离线 Fake)。 用法: python scripts/run_trial.py [--fake] [--output out.docx] ... 默认输入为 sample/ 下既有样本(追加改修·股票场景 + 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 / "sample" / "requirements_enhancement_stock.xlsx"), help="要件定义 Excel(可多个,逗号分隔)") p.add_argument("--template", default=str(ROOT / "sample" / "template_design_ja.docx"), help="概要设计书 Word 模板") p.add_argument("--rules", nargs="*", default=[ str(ROOT / "sample" / "rules_design_ja.docx"), str(ROOT / "sample" / "rules_entry_ja.docx"), ], help="写入规则/记入规则 docx(可多个)") p.add_argument("--existing-system", default=str(ROOT / "sample" / "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 / "sample"), 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)), output_language=args.output_language, ) 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()