"""#1/#2:orchestrator 章级重试兜底测试。 问题:真实 LLM 输出有随机方差(如 ja 跑 function_list 单次输出疑似纯汉字被判违规), WriterAgent 内部 max_retries=2 耗尽后抛 WriterGenerationError → 整次 run_trial 死亡。 修复:orchestrator.generate 增加章级管道重试(chapter_attempts),单章失败不连坐整次运行。 """ from __future__ import annotations from pathlib import Path from types import SimpleNamespace import pytest from genesis.data_models import StructuredSource from genesis.parsers.word_template_parser import WordTemplateParser from genesis.writer.exceptions import WriterGenerationError from genesis.writer.orchestrator import WriteOrchestrator class _FlakyEngine: """前 N 次调用抛错(模拟随机硬失败),之后成功。""" def __init__(self, fail_calls: int): self.fail_calls = fail_calls self.calls = 0 def chat_structured(self, *, session_id, prompt, variables, schema, retry_count=2): self.calls += 1 if self.calls <= self.fail_calls: raise RuntimeError(f"模拟第 {self.calls} 次调用失败") return SimpleNamespace( data={ "title": variables["title"], "blocks": [{"type": "paragraph", "text": "本機能はFakeLLMにより生成された十分な説明内容であり、書込規則を満たす。"}], }, status="ok", ) def _ss() -> StructuredSource: template = Path("sample/phase5-slice/template.docx") if not template.exists(): pytest.skip("样本模板缺失,跳过") parsed = WordTemplateParser().parse(str(template)) return StructuredSource(template=parsed, tables=[], rule_docs=[], image_analyses=[], existing_system=None, comments=[]) def test_generate_recovers_stochastic_single_chapter_failure(tmp_path): """前 3 次调用失败(跨章/章内重试),后续成功 → 整次生成不崩溃。""" out = tmp_path / "o.docx" engine = _FlakyEngine(fail_calls=3) # chapter_attempts=3,内部 max_retries=2 → 单章最多约 3×2 次调用 contents = WriteOrchestrator().generate( _ss(), str(out), samples_dir="nonexistent_dir_xyz", engine=engine, template_path=str(Path("sample/phase5-slice/template.docx")), ) assert contents, "应成功产出章节" assert out.is_file() def test_generate_hard_fails_after_chapter_attempts_exhausted(tmp_path): """始终失败 → 章级重试耗尽后仍抛 WriterGenerationError(不吞错)。""" out = tmp_path / "o.docx" class AlwaysFail(_FlakyEngine): def chat_structured(self, *, session_id, prompt, variables, schema, retry_count=2): self.calls += 1 raise RuntimeError("always fail") with pytest.raises(WriterGenerationError): WriteOrchestrator().generate( _ss(), str(out), samples_dir="nonexistent_dir_xyz", engine=AlwaysFail(0), template_path=str(Path("sample/phase5-slice/template.docx")), chapter_attempts=1, )