"""Prompt 外置(方案A)集成回归 —— 设计 §6 #12 / #15。 #12 编排器无回归:review / generation / outline / style_extract 节点据内置 `SPECS` spec 构请求(mock gateway,不触真 LLM),断言进缓存断点前块的 `system` 文本 == 旧行为(即 `load_prompt(name)` / `SPECS[name].system_prompt`)——证明 prompt 外迁到 `.md` 后字节级零变化(不变量 #9)。 #15 import-smoke:`from ww_agents import AgentSpec/outliner_spec/refiner_spec/ style_extract_spec/continuity_spec` 不抛,且拿到的对象 `is SPECS[name]`(同一实例) ——证明 apps/api 路由 + 编排器(本波仍用 `*_spec`)的 import 路径未破。 """ from __future__ import annotations import uuid from ww_agents import ( SPECS, AgentSpec, continuity_spec, load_prompt, outliner_spec, refiner_spec, style_extract_spec, ) from ww_core.orchestrator.generation_node import _build_request from ww_core.orchestrator.outline_node import build_outline_request from ww_core.orchestrator.review_node import build_review_request from ww_core.orchestrator.state import ChapterState from ww_core.orchestrator.style_extract_node import build_style_extract_request _USER_ID = uuid.UUID("00000000-0000-0000-0000-000000000001") _PROJECT_ID = uuid.UUID("00000000-0000-0000-0000-000000000002") def _system_text(spec: AgentSpec, req_system: list) -> str: """从 LlmRequest.system 取唯一的缓存断点前块文本(不变量 #9)。""" assert len(req_system) == 1, "system 应只有一个稳定块(缓存断点前)" block = req_system[0] assert block.cache is True, "system 稳定块须标 cache=True(缓存断点前块)" return block.text # ---- #12 编排器无回归:四节点 system 文本 == load_prompt(name) ---- def test_review_node_system_block_equals_load_prompt() -> None: # Arrange — 用 continuity(四审之一)构造审稿 state state: ChapterState = { # type: ignore[typeddict-item] "review_context": "## 审稿材料\n世界观硬规则……\n## 待审本章草稿\n正文……", "user_id": _USER_ID, "project_id": _PROJECT_ID, } # Act req = build_review_request(continuity_spec, state) # Assert — system 块文本 == 外置 .md 的运行时值,且 == spec.system_prompt(同一真相源) assert _system_text(continuity_spec, req.system) == load_prompt("continuity") assert _system_text(continuity_spec, req.system) == continuity_spec.system_prompt def test_generation_node_system_block_equals_load_prompt() -> None: # Arrange — worldbuilder 走通用 _build_request(与 character-gen 共用) spec = SPECS["worldbuilder"] # Act req = _build_request( spec, context="## 世界观需求\n灵气复苏", user_id=_USER_ID, project_id=_PROJECT_ID ) # Assert assert _system_text(spec, req.system) == load_prompt("worldbuilder") assert _system_text(spec, req.system) == spec.system_prompt def test_outline_node_system_block_equals_load_prompt() -> None: # Act req = build_outline_request( outliner_spec, context="## 设定\n……", user_id=_USER_ID, project_id=_PROJECT_ID ) # Assert assert _system_text(outliner_spec, req.system) == load_prompt("outliner") assert _system_text(outliner_spec, req.system) == outliner_spec.system_prompt def test_style_extract_node_system_block_equals_load_prompt() -> None: # Act req = build_style_extract_request( style_extract_spec, samples_text="样本正文一\n样本正文二", user_id=_USER_ID, project_id=_PROJECT_ID, ) # Assert assert _system_text(style_extract_spec, req.system) == load_prompt("style_extract") assert _system_text(style_extract_spec, req.system) == style_extract_spec.system_prompt def test_review_request_passes_tier_and_schema_unchanged() -> None: # 不变量 #2:节点只透传 spec.tier,绝不写死 model;output_schema 据 spec 派生(无回归) state: ChapterState = { # type: ignore[typeddict-item] "review_context": "材料……", "user_id": _USER_ID, "project_id": _PROJECT_ID, } req = build_review_request(continuity_spec, state) assert req.tier == continuity_spec.tier assert req.output_schema is continuity_spec.output_schema # ---- #15 import-smoke(apps/api + 编排器仍用 *_spec)---- def test_import_smoke_specs_are_same_instance() -> None: # 兼容期不变量:模块级 *_spec 与 SPECS[name] 是同一实例(不双真相,设计 §3.4 / #14) assert isinstance(AgentSpec, type) assert outliner_spec is SPECS["outliner"] assert refiner_spec is SPECS["refiner"] assert style_extract_spec is SPECS["style_extract"] assert continuity_spec is SPECS["continuity"]