muse-agent-example/tests/skills/deconstruct-book/test_parse_outline_offline.py

138 lines
5.2 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.

#!/usr/bin/env python3
"""parse_outline 窗覆盖门离线测试:不连库、不调用模型。"""
import pathlib
import sys
import click
PROJECT_ROOT = pathlib.Path(__file__).resolve().parents[3]
SCRIPT_DIR = PROJECT_ROOT / "muse" / "content" / "entity" / "skills" / "ingest" / "deconstruct-book" / "scripts"
sys.path.insert(0, str(SCRIPT_DIR))
import parse_outline as po # noqa: E402
def check(name, condition):
"""输出与现有 parse-book 离线测试一致的简明结果。"""
if not condition:
raise AssertionError(name)
print(f"[PASS] {name}")
def main():
"""覆盖连续、头部、中部、尾部和重叠窗五类边界。"""
check("coverage-连续无缝", po.outline_coverage_gaps([(1, 33), (34, 65)], 1, 65) == [])
check(
"coverage-识别中部缝隙",
po.outline_coverage_gaps([(951, 983), (1015, 1047)], 951, 1047) == [(984, 1014)],
)
check("coverage-识别头部缝隙", po.outline_coverage_gaps([(5, 10)], 1, 10) == [(1, 4)])
check("coverage-识别尾部缝隙", po.outline_coverage_gaps([(1, 6)], 1, 10) == [(7, 10)])
check(
"coverage-重叠窗不误报",
po.outline_coverage_gaps([(1, 20), (15, 30), (31, 40)], 1, 40) == [],
)
po.ensure_outline_coverage([(1, 33), (34, 65)], 1, 65, context="测试")
try:
po.ensure_outline_coverage([(951, 983), (1015, 1047)], 951, 1047, context="测试")
except click.ClickException as error:
check("coverage-缝隙非零阻断", "984-1014" in str(error))
else:
raise AssertionError("coverage-缝隙非零阻断")
original_chat = po.chat_degrade
try:
replies = iter([("{}", {}), ('{"outline":"补窗成功"}', {"prompt_tokens": 1})])
po.chat_degrade = lambda _prompt, _model: next(replies)
outline, usage = po.generate_outline_with_retry("prompt", "model", max_attempts=2)
check("window-空输出重试后成功", outline == "补窗成功" and usage["prompt_tokens"] == 1)
calls = {"count": 0}
def always_empty(_prompt, _model):
calls["count"] += 1
return "{}", {}
po.chat_degrade = always_empty
try:
po.generate_outline_with_retry("prompt", "model", max_attempts=2)
except click.ClickException as error:
check("window-有限重试耗尽即停书", calls["count"] == 2 and "2 次" in str(error))
else:
raise AssertionError("window-有限重试耗尽即停书")
def exhausted_chain(_prompt, _model):
raise po.SensitiveError("全部渠道耗尽")
po.chat_degrade = exhausted_chain
try:
po.generate_outline_with_retry("prompt", "model", max_attempts=2)
except click.ClickException as error:
check("window-治理链耗尽即停书", "治理链耗尽" in str(error))
else:
raise AssertionError("window-治理链耗尽即停书")
finally:
po.chat_degrade = original_chat
unordered = [
(3, 83, 984, 1014, "补插窗", "revised"),
(1, 1, 1, 33, "首窗", "revised"),
(4, 52, 1015, 1047, "后续窗", "revised"),
(2, 82, 2226, 2250, "末窗", "revised"),
]
ordered = po.order_outline_windows(unordered)
check("check-按绝对章号排序", [row[2] for row in ordered] == [1, 984, 1015, 2226])
rendered = po.render_coherence_outlines(ordered)
check(
"check-阶段号按章序重编",
"【阶段2(第984–1014章)】" in rendered and "【阶段83" not in rendered,
)
# —— 换窗参数冲突门:同 from_order、不同 to_order 必须失败关闭 ——
planned = po.plan_outline_windows(
[(1, "a", "x" * 100, None), (2, "b", "y" * 100, None), (3, "c", "z" * 50, None)],
window_chars=150,
last_order=3,
)
check("plan-按字数切出窗边界", planned == [(1, 2), (3, 3)])
planned_full = po.plan_outline_windows(
[(1, "a", "x" * 100, None), (2, "b", "y" * 100, None), (3, "c", "z" * 50, None)],
window_chars=250,
last_order=3,
)
check("plan-满窗含尾章", planned_full == [(1, 3)])
small_existing = [(1, 3)] # 验收小窗占住 from=1
large_planned = [(1, 34)] # 放量大窗同起点、不同终点
conflicts = po.detect_window_plan_conflicts(small_existing, large_planned)
check("conflict-同起点不同终点检出", len(conflicts) == 1 and conflicts[0]["from_order"] == 1)
check(
"conflict-同边界续跑无冲突",
po.detect_window_plan_conflicts([(1, 34), (35, 70)], [(1, 34), (35, 70)]) == [],
)
check(
"conflict-新起点续跑无冲突",
po.detect_window_plan_conflicts([(1, 34)], [(35, 70)]) == [],
)
try:
po.require_compatible_window_plan(
[(1, 3)], [(1, 34)], replace_windows=False,
)
except click.ClickException as error:
check("conflict-无 replace 失败关闭", "WINDOW_PLAN_CONFLICT" in str(error))
else:
raise AssertionError("conflict-无 replace 失败关闭")
po.require_compatible_window_plan([(1, 3)], [(1, 34)], replace_windows=True)
check("conflict-replace 放行", True)
print("\n全部离线自测通过(未连库、未发任何 LLM 调用)")
if __name__ == "__main__":
main()