Files
writer-work-flow/packages/agents/tests/test_clarify_spec.py
Yaojia Wang 4a09f273d9 feat(llm): 整章重写预检澄清 spec + 教条 clarify_rewrite(WFW-9 M2)
镜像 clarify_refine(仅 name/prompt 不同,复用 ClarifyDecision,不新增 schema):
新增 clarify_rewrite_spec(analyst 档、reads=()/writes=() 只读,不变量 #2/#3)+ 章级
教条 prompts/clarify_rewrite.md(segment→整章:判作者章级重写意见是否含糊)。注册进
SPECS/SCHEMA_CATALOG,自检数量 24→25,__init__ 导出,金标准仅新增 1 条哈希无漂移。
2026-07-08 11:42:21 +02:00

168 lines
6.1 KiB
Python
Raw Permalink 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.

"""WFW-9 M1 clarify_refine 契约测试schema + spec 声明路线A 两阶段之「问题」阶段)。
契约测试——构造符合 schema 的 mock 响应,校验字段/默认值/tier/读写权限。
ClarifyDecision`{need_clarification, questions[≤1], verification?}`
analyst 档、reads=()、writes=()。全字段带默认值守解析韧性LLM 漏产字段降级为
need_clarification=false不阻塞润色。不联网、无 DB。
"""
from __future__ import annotations
import pytest
from pydantic import ValidationError
from ww_agents import (
SPECS,
AgentSpec,
ClarifyDecision,
ClarifyOption,
ClarifyQuestion,
clarify_refine_spec,
clarify_rewrite_spec,
)
# ---- ClarifyDecision / ClarifyQuestion / ClarifyOption schema ----
def test_clarify_decision_parses_need_clarification_with_one_question() -> None:
# Arrange含糊 → 反问 1 问 + 3 个锚定选项 + 自由输入兜底
mock = {
"need_clarification": True,
"questions": [
{
"question": "你想让这段更有张力,是指哪种方向?",
"options": [
{"label": "加快节奏", "value": "把长句拆短,压缩铺陈,加快推进"},
{"label": "加重冲突", "value": "强化人物对立,增加正面交锋"},
{"label": "收紧对白", "value": "删掉旁白,让张力靠对话推进"},
],
"allow_free_text": True,
}
],
"verification": None,
}
# Act
decision = ClarifyDecision.model_validate(mock)
# Assert
assert decision.need_clarification is True
assert len(decision.questions) == 1
q = decision.questions[0]
assert q.question.startswith("你想让这段")
assert len(q.options) == 3
assert q.options[0].label == "加快节奏"
assert q.options[0].value.startswith("把长句拆短")
assert q.allow_free_text is True
assert decision.verification is None
def test_clarify_decision_parses_clear_with_verification() -> None:
# 明确 → 不问、给确认语
mock = {
"need_clarification": False,
"questions": [],
"verification": "我会把第二句的被字句改成主动句,其余不动,对吗?",
}
decision = ClarifyDecision.model_validate(mock)
assert decision.need_clarification is False
assert decision.questions == []
assert decision.verification is not None
def test_clarify_decision_all_fields_default_for_resilience() -> None:
# 全字段默认值守解析韧性:空对象降级为 need_clarification=false不问、放行
decision = ClarifyDecision.model_validate({})
assert decision.need_clarification is False
assert decision.questions == []
assert decision.verification is None
def test_clarify_question_options_default_empty_and_free_text_true() -> None:
# 凑不出具体选项 → 空 options + 常驻自由输入兜底
q = ClarifyQuestion.model_validate({"question": "你具体想改哪里?"})
assert q.options == []
assert q.allow_free_text is True
def test_clarify_option_requires_label_and_value() -> None:
with pytest.raises(ValidationError):
ClarifyOption.model_validate({"label": "加快节奏"})
def test_clarify_question_requires_question_text() -> None:
with pytest.raises(ValidationError):
ClarifyQuestion.model_validate({"options": []})
# ---- clarify_refine_spec 声明 ----
def test_clarify_refine_spec_is_analyst_tier() -> None:
# 不变量 #2含糊判别 + 反问构造用分析档,只声明 tier
assert clarify_refine_spec.tier == "analyst"
assert clarify_refine_spec.name == "clarify_refine"
def test_clarify_refine_spec_is_read_only() -> None:
# 不变量 #3只读——不读库真相源材料端点注入、不写任何表
assert clarify_refine_spec.reads == ()
assert clarify_refine_spec.writes == ()
def test_clarify_refine_spec_output_schema() -> None:
assert clarify_refine_spec.output_schema is ClarifyDecision
def test_clarify_refine_spec_prompt_documents_at_most_one_question() -> None:
# v1 硬上限 1 问 + 自由输入兜底须在教条里显式约束
prompt = clarify_refine_spec.system_prompt
assert "1 问" in prompt
assert "自由输入" in prompt
assert "防循环" in prompt
def test_clarify_refine_spec_is_agent_spec_and_immutable() -> None:
assert isinstance(clarify_refine_spec, AgentSpec)
with pytest.raises(ValidationError):
clarify_refine_spec.tier = "writer"
# ---- clarify_rewrite_spec 声明WFW-9 M2整章重写预检澄清镜像 clarify_refine----
def test_clarify_rewrite_spec_is_analyst_tier() -> None:
# 不变量 #2整章含糊判别 + 反问构造用分析档,只声明 tier不写 model
assert clarify_rewrite_spec.tier == "analyst"
assert clarify_rewrite_spec.name == "clarify_rewrite"
def test_clarify_rewrite_spec_is_read_only() -> None:
# 不变量 #3只读——不读库真相源材料端点注入、不写任何表
assert clarify_rewrite_spec.reads == ()
assert clarify_rewrite_spec.writes == ()
def test_clarify_rewrite_spec_output_schema_reuses_clarify_decision() -> None:
# 复用 ClarifyDecision不新增 schema与 refine 侧同构
assert clarify_rewrite_spec.output_schema is ClarifyDecision
def test_clarify_rewrite_spec_prompt_documents_constraints() -> None:
# v1 硬上限 1 问 + 自由输入兜底 + 防循环须在教条里显式约束(与 refine 侧同纪律)
prompt = clarify_rewrite_spec.system_prompt
assert prompt # 非空
assert "1 问" in prompt
assert "自由输入" in prompt
assert "防循环" in prompt
def test_clarify_rewrite_spec_registered_in_specs() -> None:
# 注册表同一实例(兼容期 *_spec 与 SPECS[name] 为同对象,不变量)
assert SPECS["clarify_rewrite"] is clarify_rewrite_spec
def test_clarify_rewrite_spec_is_agent_spec_and_immutable() -> None:
assert isinstance(clarify_rewrite_spec, AgentSpec)
with pytest.raises(ValidationError):
clarify_rewrite_spec.tier = "writer"