Files
writer-work-flow/tests/craft_eval/fixtures.py
Yaojia Wang 51bfb6e320 test(qa): craft 成稿质量前后对照评估回路(灵感 F2)
固定 3 题材金标准输入(仙侠/都市/悬疑,各含最小 spec+world+outline 及开篇章),
经真实 assemble()+build_write_request() 组装 craft-on / craft-off 两版写章请求:

- dry 模式(默认,零成本、免真实 LLM,可进 CI):断言 craft 教条块、genre 片段、
  开篇黄金三章 marker 确实按 fixture 注入正确内容,且 craft-off 基线不含这三者;
  并把两版将发送给模型的 prompt 并排 dump 供人眼看。这些断言即 tests/test_craft_eval.py,
  是 craft/审查注入路径的回归门禁。
- --live 模式(真实付费 LLM,绝不进 CI):经 gateway 各生成一章 + analyst 五维裁判,
  凭据读 DB、路由走 tier_routing,与生产写章同一网关路径。

入口 `uv run python -m tests.craft_eval [--live]`;用法/成本/门禁说明见 README.md。
2026-07-06 17:57:31 +02:00

159 lines
6.0 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.

"""金标准输入 fixtures灵感 F2——3 个不同题材的最小 spec+world+outline。
每个 fixture 是一份**自造、不依赖 DB** 的最小设定:书级蓝本(含 genre+ 世界观硬规则 +
定型主角 + 本章大纲节拍。评估脚本据此组装 craft-on / craft-off 两版写章请求。
新增题材只需追加一个 `FixtureCase`genre 必须命中 `ww_config.GENRE_CRAFT` 才有片段可注)。
"""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Any
from ww_core.domain.repositories import CharacterView, ProjectSpecView, WorldEntityView
@dataclass(frozen=True)
class FixtureCase:
"""一份金标准写章输入(题材 / 章号 / 书级蓝本 / 世界观 / 主角 / 本章节拍)。"""
slug: str
genre: str
chapter_no: int
spec: ProjectSpecView
world: tuple[WorldEntityView, ...]
characters: tuple[CharacterView, ...]
beats: dict[str, Any] = field(default_factory=dict)
_XIANXIA = FixtureCase(
slug="xianxia",
genre="仙侠",
chapter_no=3, # 开篇章≤3——覆盖黄金三章 marker 注入
spec=ProjectSpecView(
title="逆脉证道",
genre="仙侠",
logline="废脉少年叶尘为救绝症妹妹寻回春灵药,滴血觉醒上古逆天功法,逆修九境搅动仙门风云。",
premise="天地灵气稀薄,修行看灵脉品阶;废脉者纳灵极慢,被视作修行绝路。",
theme="卑微者的不屈与救赎——修的是道,护的是人。",
tone="热血隐忍",
narrative_pov="第三人称限知",
ending_type="正剧",
),
world=(
WorldEntityView(
type="设定",
name="九境修为",
rules={
"境界": ["淬体", "开脉", "灵动", "纳灵", "凝魂", "化虚", "通玄", "渡劫", "证道"],
"每境": "分前中后三重",
},
),
WorldEntityView(
type="设定",
name="灵脉",
rules={"作用": "决定纳灵速度", "废脉": "纳灵极慢,被视作修行绝路"},
),
),
characters=(
CharacterView(
name="叶尘",
role="主角",
motive="为身患寒髓之症、命不久矣的妹妹叶暖寻回春灵药",
backstory="青云宗外门弟子,天生废脉、常年受人轻慢,性子沉静隐忍。",
speech_tics={"风格": "话少、点到为止,逼到绝境时语气反而更冷更稳"},
),
),
beats={
"summary": "叶尘被同门构陷逐出青云宗,独行荒山避雨于古洞,"
"滴血觉醒《逆脉真解》,一招震退追杀者。",
"entities": ["叶尘"],
"text": "叶尘 古洞 滴血 觉醒 逆脉真解 震退 追杀者",
},
)
_URBAN = FixtureCase(
slug="urban",
genre="都市",
chapter_no=1, # 开篇第一章
spec=ProjectSpecView(
title="重启我的黄金时代",
genre="都市",
logline="落魄程序员林川一夜重生回到二十年前,凭前世记忆在时代风口逆天改命。",
premise="重生回到世纪初互联网草莽年代,机会遍地、格局未定。",
theme="不甘平庸者抓住的第二次机会。",
tone="爽快励志",
narrative_pov="第一人称",
ending_type="HE",
),
world=(
WorldEntityView(
type="设定",
name="重生规则",
rules={
"回溯": "重生回到 2003 年的大学时代",
"记忆": "完整保留前世记忆与二十年行业趋势",
},
),
),
characters=(
CharacterView(
name="林川",
role="主角",
motive="改写前世潦倒猝死的结局,护住家人、抓住时代机遇翻身",
backstory="前世三十五岁猝死于出租屋的落魄外包程序员。",
speech_tics={"风格": "表面随和、心里门儿清,说话带点自嘲"},
),
),
beats={
"summary": "林川在出租屋猝死后睁眼回到 2003 年的大学宿舍,"
"认出这是重来一次的机会,立下第一个目标。",
"entities": ["林川"],
"text": "林川 重生 2003 大学宿舍 认清机会",
},
)
_SUSPENSE = FixtureCase(
slug="suspense",
genre="悬疑",
chapter_no=2, # 开篇第二章
spec=ProjectSpecView(
title="雾隐镇失踪案",
genre="悬疑",
logline="刑警周砚回到多雾的故乡小镇调查连环失踪案,线索却一步步指向自己尘封的童年。",
premise="常年浓雾笼罩的封闭小镇,二十年前也发生过同样的失踪,无人被起诉。",
theme="逼近真相与自我和解的代价。",
tone="冷峻压抑",
narrative_pov="第三人称限知",
ending_type="开放",
),
world=(
WorldEntityView(
type="设定",
name="雾隐镇",
rules={
"环境": "常年浓雾、信号时断时续",
"旧案": "二十年前发生过同类失踪,卷宗离奇缺页",
},
),
),
characters=(
CharacterView(
name="周砚",
role="主角",
motive="查清连环失踪真相,也为解开自己童年缺失的那段记忆",
backstory="市局刑警,幼年从雾隐镇被送走,对故乡的记忆断片。",
speech_tics={"风格": "话短、爱用反问,审讯时不动声色"},
),
),
beats={
"summary": "周砚勘查第二名失踪者的住所,"
"在墙缝里发现一张与二十年前旧案笔迹相同的纸条,心头一凛。",
"entities": ["周砚"],
"text": "周砚 现场勘查 墙缝 纸条 二十年前 旧案",
},
)
# 金标准题材集:仙侠 / 都市 / 悬疑(各含开篇章,覆盖黄金三章 marker
FIXTURES: tuple[FixtureCase, ...] = (_XIANXIA, _URBAN, _SUSPENSE)