feat(agents): Scope B A1 — 4 竞品生成器 schema/spec + 2 上下文 builder

续写/扩写/降AI率/拆书 4 个生成器的声明(preview-only writes=[],只声明 tier #2):
- schemas.py: ContinuationResult/PolishResult/DeAiResult/BookTeardownResult
- specs.py: continue(writer)/expand(writer)/de-ai(analyst)/teardown(analyst),各配 system_prompt
- generation_node.py: build_continuation_context(续写读前文)/build_text_input_context(原文输入)
- 单测: 4 spec/schema 契约 + 2 builder 纯函数(确定性/空值降级)

守不变量 #2(只声明 tier)/#3(纯预览不写库)/#9(system_prompt 进缓存前缀)。
This commit is contained in:
Yaojia Wang
2026-06-23 19:01:11 +02:00
parent 681f8e61eb
commit 87dd09a797
7 changed files with 415 additions and 0 deletions

View File

@@ -0,0 +1,68 @@
"""Scope B 竞品快赢 · 2 个新上下文 builder 的纯函数单测。
`build_continuation_context`(续写读前文)/ `build_text_input_context`(扩写/降AI/拆书原文)。
确定性、无时间戳/UUID守不变量 #9 可缓存)、空值降级。不联网、无 DB。
"""
from __future__ import annotations
from ww_core.orchestrator import build_continuation_context, build_text_input_context
# ---- build_continuation_context作品设定 + 前文 + 可选节拍 ----
def test_continuation_context_is_deterministic() -> None:
a = build_continuation_context(
project_context="题材:玄幻", prior_text="他握紧剑。", beats=["反击", "逃脱"]
)
b = build_continuation_context(
project_context="题材:玄幻", prior_text="他握紧剑。", beats=["反击", "逃脱"]
)
assert a == b
def test_continuation_context_includes_prior_and_beats() -> None:
ctx = build_continuation_context(
project_context="题材:玄幻", prior_text="他握紧剑。", beats=["反击", "逃脱"]
)
assert "题材:玄幻" in ctx
assert "他握紧剑。" in ctx
assert "1. 反击" in ctx
assert "2. 逃脱" in ctx
def test_continuation_context_no_beats_falls_back() -> None:
ctx = build_continuation_context(project_context="题材:都市", prior_text="夜深了。")
assert "本章暂无大纲节拍" in ctx
assert "夜深了。" in ctx
def test_continuation_context_empty_prior_falls_back() -> None:
ctx = build_continuation_context(project_context="题材:都市", prior_text=" ")
assert "暂无前文" in ctx
# ---- build_text_input_context作品设定 + 原文 + 需求 ----
def test_text_input_context_is_deterministic() -> None:
a = build_text_input_context(project_context="题材:玄幻", text="原文片段", brief="加强描写")
b = build_text_input_context(project_context="题材:玄幻", text="原文片段", brief="加强描写")
assert a == b
def test_text_input_context_includes_text_and_brief() -> None:
ctx = build_text_input_context(project_context="题材:玄幻", text="原文片段", brief="加强描写")
assert "题材:玄幻" in ctx
assert "原文片段" in ctx
assert "加强描写" in ctx
def test_text_input_context_empty_brief_falls_back() -> None:
ctx = build_text_input_context(project_context="题材:都市", text="原文片段", brief=" ")
assert "按默认目标处理" in ctx
def test_text_input_context_empty_text_falls_back() -> None:
ctx = build_text_input_context(project_context="题材:都市", text=" ", brief="去AI味")
assert "未提供原文" in ctx