feat(review): 人物塑造 advisory 审(第五审,仅建议不阻断验收)
写→审链新增第五审「人物塑造」(灵感③/D2):advisory 单维——仅建议、不阻断
验收(不进 conflicts、不碰 assert_conflicts_resolved、不入 REVIEW_RESERVED_NAMES);
只做人物塑造、不做氛围。
- SPEC characterization_spec(analyst,reads=("characters",),writes=())+
prompts/characterization.md(只读、显式忽略文本长度与辞藻华丽度、引用逐字片段+置信度)
- schema CharacterizationReview{issues[...]}(全字段默认值守解析韧性)+ 注册 catalog
- 计数三处 22→23 + regen prompt_hashes.json(仅加一条)
- DB chapter_reviews 加 characterization JSONB nullable 列(迁移 f6a7b8c9d0e1)
- 接线:graph REVIEW_SPECS 追加 · chain ReviewRecordRepo · collect(extract/record) ·
review_repo(Protocol/Sql/_to_view/ReviewView) · sse(event/factory/section) · normalize 自动纳入
- 契约 ReviewHistoryItem 加 characterization(gen:api 已重生成)
- 前端 sse/history/useReviewStream + CharacterizationPanel(只展示无裁决 UI、置信度降序+低置信折叠)+ ReviewReport 挂面板
- 测试 test_characterization_does_not_block_accept + 计数/golden + collect/normalize + 前端 reducer/normalize/parse
守不变量 #2/#3/#9;依赖 ⑧(motive/appearance 作客观锚点)。
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
"book-title": "aa53ccf22da1d4e555c8168fc23f2f3547cc6f59e56e5df57540b7a946755e39",
|
||||
"brainstorm": "f670dc77e3d9bd4a1df2b8cb29b51edb8b229d214148907d4b05519492364ada",
|
||||
"character-gen": "f65e2ec3158be378a6cd40c23035b326dc9f6b297b6135007a93fceedf575cfd",
|
||||
"characterization": "643988cb899a66472acb3e18709f3299d2025f9bda132c774ee26c589266b51f",
|
||||
"continue": "f1ce02be3b186fc966c89cf0d54ea57a400e91d435ea91f5722cb17e43d11763",
|
||||
"continuity": "1bdf9799bad9076e54de2023433ec7da7b559509d986392a0563dc1ee20cf4a1",
|
||||
"de-ai": "9ce3020cdd4b223cc4d13c289babd2811bbfece4b392711dcba4fd0301c87249",
|
||||
|
||||
115
packages/agents/tests/test_characterization_spec.py
Normal file
115
packages/agents/tests/test_characterization_spec.py
Normal file
@@ -0,0 +1,115 @@
|
||||
"""灵感③ 人物塑造续审契约测试:CharacterizationReview schema + characterization_spec。
|
||||
|
||||
advisory 单维——仅建议、不阻断验收(D2)。
|
||||
|
||||
契约测试——构造符合 schema 的 mock 响应,校验字段默认值降级、tier、只读(writes==())、
|
||||
输出 schema 派生。人物塑造是 advisory:**不加入 REVIEW_RESERVED_NAMES**。不联网、无 DB。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
from pydantic import ValidationError
|
||||
from ww_agents import (
|
||||
REVIEW_RESERVED_NAMES,
|
||||
AgentSpec,
|
||||
CharacterizationIssue,
|
||||
CharacterizationReview,
|
||||
characterization_spec,
|
||||
)
|
||||
|
||||
# ---- CharacterizationReview schema ----
|
||||
|
||||
|
||||
def test_characterization_parses_mock_response() -> None:
|
||||
# Arrange:模拟网关 instructor 校验后的结构化产出(每条带逐字引用 + 置信度)
|
||||
mock = {
|
||||
"issues": [
|
||||
{
|
||||
"character": "沈砚",
|
||||
"aspect": "动机一致性",
|
||||
"where": "第4段决斗前退缩",
|
||||
"quote": "他忽然转身就走,什么也没说。",
|
||||
"diagnosis": "与人物卡既定「护妹至死」动机断裂,无过渡",
|
||||
"suggestion": "让退缩扣回护妹动机(如察觉陷阱护妹先撤)",
|
||||
"confidence": 0.82,
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
# Act
|
||||
review = CharacterizationReview.model_validate(mock)
|
||||
|
||||
# Assert
|
||||
assert len(review.issues) == 1
|
||||
issue = review.issues[0]
|
||||
assert issue.character == "沈砚"
|
||||
assert issue.aspect == "动机一致性"
|
||||
assert issue.quote == "他忽然转身就走,什么也没说。"
|
||||
assert issue.confidence == pytest.approx(0.82)
|
||||
|
||||
|
||||
def test_characterization_defaults_to_empty_issues() -> None:
|
||||
# 无人物卡 / 无问题 → 空 issues(默认值降级,仿 StyleDriftReview 韧性)
|
||||
review = CharacterizationReview()
|
||||
assert review.issues == []
|
||||
|
||||
|
||||
def test_characterization_issue_all_fields_have_defaults() -> None:
|
||||
# 畸形/缺字段守韧性:全字段给默认值,空 dict 也能解析
|
||||
issue = CharacterizationIssue.model_validate({})
|
||||
assert issue.character == ""
|
||||
assert issue.aspect == ""
|
||||
assert issue.where == ""
|
||||
assert issue.quote == ""
|
||||
assert issue.diagnosis == ""
|
||||
assert issue.suggestion == ""
|
||||
assert issue.confidence == 0.0
|
||||
|
||||
|
||||
def test_characterization_issue_partial_fields_ok() -> None:
|
||||
# 缺 confidence/quote 时仍解析(降级默认),不抛
|
||||
issue = CharacterizationIssue.model_validate({"character": "阿黎", "diagnosis": "扁平"})
|
||||
assert issue.character == "阿黎"
|
||||
assert issue.diagnosis == "扁平"
|
||||
assert issue.confidence == 0.0
|
||||
|
||||
|
||||
# ---- characterization_spec 声明(analyst 档,advisory 单维)----
|
||||
|
||||
|
||||
def test_characterization_spec_is_analyst_tier() -> None:
|
||||
# 不变量 #2:只声明档位,不写 model
|
||||
assert characterization_spec.tier == "analyst"
|
||||
assert characterization_spec.name == "characterization"
|
||||
|
||||
|
||||
def test_characterization_spec_is_read_only() -> None:
|
||||
# 不变量 #3:审查只读,唯一落库=collect 留痕
|
||||
assert characterization_spec.writes == ()
|
||||
|
||||
|
||||
def test_characterization_spec_reads_characters() -> None:
|
||||
# 以人物卡 motive/appearance 为客观锚点
|
||||
assert characterization_spec.reads == ("characters",)
|
||||
|
||||
|
||||
def test_characterization_spec_output_schema() -> None:
|
||||
assert characterization_spec.output_schema is CharacterizationReview
|
||||
|
||||
|
||||
def test_characterization_spec_prompt_ignores_length_and_diction() -> None:
|
||||
# 对抗 LLM-judge 冗长/辞藻偏见:prompt 必须显式声明忽略文本长度与辞藻华丽度
|
||||
prompt = characterization_spec.system_prompt
|
||||
assert "忽略文本长度与辞藻华丽度" in prompt
|
||||
|
||||
|
||||
def test_characterization_not_in_review_reserved_names() -> None:
|
||||
# advisory:不进受信保留名(不阻断验收、无 conflicts 通道)
|
||||
assert "characterization" not in REVIEW_RESERVED_NAMES
|
||||
|
||||
|
||||
def test_characterization_spec_is_agent_spec_and_immutable() -> None:
|
||||
assert isinstance(characterization_spec, AgentSpec)
|
||||
with pytest.raises(ValidationError):
|
||||
characterization_spec.tier = "writer"
|
||||
@@ -52,8 +52,8 @@ def test_load_prompt_matches_golden(name: str) -> None:
|
||||
|
||||
|
||||
# ---- #1 注册表唯一性 ----
|
||||
def test_specs_registry_len_is_22() -> None:
|
||||
assert len(SPECS) == 22
|
||||
def test_specs_registry_len_is_23() -> None:
|
||||
assert len(SPECS) == 23
|
||||
# name 即 key,dict 已去重;逐项确认 key == spec.name(无错位)
|
||||
assert all(key == spec.name for key, spec in SPECS.items())
|
||||
|
||||
|
||||
Reference in New Issue
Block a user