Files
writer-work-flow/packages/agents/tests/test_specs.py
Yaojia Wang 68f194a043 feat: M2 — 写→审(一致性)→裁决→验收(事务);未决冲突禁验收
- 续审 Agent 声明(AgentSpec) + 结构化输出契约(ContinuityReview/Conflict 五类)
- LangGraph 并行审子图(可扩四审) + collect 落 chapter_reviews 留痕 + review SSE(section/conflict)
- 验收-side Repository:章节 accepted 版本晋升 + digest append-only + 审稿留痕/裁决
- API:review(SSE) + reviews 历史 + accept(单原子事务:晋升 version + 终稿 digest + 裁决留痕)
- 冲突 gate:未决裁决拦截(CONFLICT_UNRESOLVED);digest 从终稿提炼(不变量#4)
- 前端:审稿报告页 + 冲突就地标注 + 裁决(采纳/忽略/手改) + 未决禁验收 + 「本次将更新」清单
- M2 E2E:真实 DB + 多档位 mock 网关零 token 走通 写→审→裁决→验收→摘要入库
- 多 agent 协同台账(PROGRESS.md) + 共享记忆(memory/contracts·decisions·gotchas)
2026-06-18 11:38:28 +02:00

96 lines
3.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""T2.1 续审契约测试ContinuityReview schema + continuity_spec 声明C6
契约测试——构造符合 schema 的 mock 响应,校验 type 枚举约束、conflicts 列表、
四审只读writes==[])。不联网、无 DB。
"""
from __future__ import annotations
import pytest
from pydantic import ValidationError
from ww_agents import AgentSpec, Conflict, ContinuityReview, continuity_spec
def test_continuity_review_parses_mock_response() -> None:
# Arrange模拟网关 instructor 校验后的结构化产出
mock = {
"conflicts": [
{
"type": "性格漂移",
"where": "第 3 段,主角忽然示弱",
"refs": ["第 1 章人物卡:性格刚烈"],
"suggestion": "改为强硬回应,或补内心转折铺垫",
},
{
"type": "时间线倒错",
"where": "开篇提到三日后",
"refs": ["第 5 章 digest事件发生在次日"],
"suggestion": "统一为次日",
},
]
}
# Act
review = ContinuityReview.model_validate(mock)
# Assert
assert len(review.conflicts) == 2
assert review.conflicts[0].type == "性格漂移"
assert review.conflicts[1].refs == ["第 5 章 digest事件发生在次日"]
def test_continuity_review_allows_empty_conflicts() -> None:
review = ContinuityReview.model_validate({"conflicts": []})
assert review.conflicts == []
def test_continuity_review_defaults_to_empty_conflicts() -> None:
review = ContinuityReview()
assert review.conflicts == []
def test_conflict_type_rejects_unknown_value() -> None:
with pytest.raises(ValidationError):
Conflict.model_validate({"type": "排版错误", "where": "x", "refs": [], "suggestion": "y"})
def test_conflict_refs_default_empty() -> None:
conflict = Conflict.model_validate(
{"type": "设定违例", "where": "战斗场景", "suggestion": "删去违例能力"}
)
assert conflict.refs == []
def test_continuity_spec_is_analyst_tier() -> None:
assert continuity_spec.tier == "analyst"
assert continuity_spec.name == "continuity"
def test_continuity_spec_is_read_only() -> None:
# 不变量 #3四审只读
assert continuity_spec.writes == []
def test_continuity_spec_declares_expected_reads() -> None:
assert continuity_spec.reads == ["chapter_digests", "characters", "world_entities"]
def test_continuity_spec_output_schema_is_continuity_review() -> None:
assert continuity_spec.output_schema is ContinuityReview
def test_continuity_spec_has_nonempty_system_prompt() -> None:
assert continuity_spec.system_prompt.strip()
def test_agent_spec_is_immutable() -> None:
# frozen加载后不得改动
with pytest.raises(ValidationError):
continuity_spec.tier = "writer"
def test_agent_spec_writer_allows_none_output_schema() -> None:
writer = AgentSpec(name="writer", tier="writer", system_prompt="")
assert writer.output_schema is None
assert writer.scope == "builtin"