写→审链新增第五审「人物塑造」(灵感③/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 作客观锚点)。
677 lines
28 KiB
Python
677 lines
28 KiB
Python
"""续审 Agent 的结构化输出 schema(ARCH §5.4 continuity 行 / §6.1 冲突分类)。
|
||
|
||
snake_case,Pydantic v2。网关经 instructor 保证产出符合这些 schema(C1/§4.4)。
|
||
|
||
不变量 #3:四审只读——审稿期只产**冲突清单**,不产 digest(digest 在验收时
|
||
从终稿另提,见不变量 #4 / ARCH §5.5)。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from typing import Literal
|
||
|
||
from pydantic import BaseModel, Field
|
||
|
||
# ARCH §6.1 五类冲突;受限枚举,便于前端就地裁决分类与统计。
|
||
ConflictType = Literal[
|
||
"性格漂移",
|
||
"能力不符",
|
||
"设定违例",
|
||
"地理矛盾",
|
||
"时间线倒错",
|
||
]
|
||
|
||
|
||
class Conflict(BaseModel):
|
||
"""单条一致性冲突(ARCH §6.1)。"""
|
||
|
||
type: ConflictType = Field(description="冲突分类(五类之一)")
|
||
where: str = Field(description="本章定位:哪一段/哪句触发冲突")
|
||
refs: list[str] = Field(
|
||
default_factory=list,
|
||
description="冲突来源引用:相关章节号、设定项或人物卡条目",
|
||
)
|
||
suggestion: str = Field(description="改法建议")
|
||
# 一键采纳补丁对(可缺):可由局部改写修复时给出,供前端把 original→replacement
|
||
# find-replace 进终稿(作者点「采纳改法」触发,仍只读——审稿只提议、不改稿)。
|
||
# 不可定位到单一连续片段(跨段/缺设定等)则两者留空,回退到作者手改。
|
||
original: str | None = Field(
|
||
default=None,
|
||
description="可一键采纳时:从本章草稿**逐字摘录**的精确原文最小句段(含标点,不得改写);不可定位则留空",
|
||
)
|
||
replacement: str | None = Field(
|
||
default=None,
|
||
description="可一键采纳时:对应 original 的改后文本(同粒度最小句段);不可定位则留空",
|
||
)
|
||
|
||
|
||
class ContinuityReview(BaseModel):
|
||
"""续审结构化产出:仅冲突清单(无 digest)。
|
||
|
||
digest 不在审稿期产——那是验收时从**终稿**另提(不变量 #4)。
|
||
"""
|
||
|
||
conflicts: list[Conflict] = Field(
|
||
default_factory=list,
|
||
description="检出的一致性冲突;无冲突时为空列表",
|
||
)
|
||
|
||
|
||
# ---- 大纲 Agent 结构化输出(ARCH §5.4 outliner 行 / §6.2 伏笔窗口)----
|
||
|
||
|
||
class ForeshadowWindow(BaseModel):
|
||
"""伏笔回收窗口:关联本章与某条伏笔(ARCH §6.2)。
|
||
|
||
排大纲时把伏笔的「埋设章 → 期望回收区间」绑到具体章节;接近回收窗口的章节
|
||
提示作者推进/收束。下界/上界可缺省(仅埋设、尚未排定回收时间)。
|
||
"""
|
||
|
||
code: str = Field(description="伏笔编码(关联 foreshadow.code)")
|
||
plant_chapter: int | None = Field(
|
||
default=None, description="埋设章号(伏笔在本章/此章首次出现)"
|
||
)
|
||
expected_close_from: int | None = Field(default=None, description="期望回收区间下界(章号)")
|
||
expected_close_to: int | None = Field(default=None, description="期望回收区间上界(章号)")
|
||
|
||
|
||
class OutlineChapter(BaseModel):
|
||
"""单章大纲:章号 + 节拍清单 + 关联伏笔窗口。"""
|
||
|
||
no: int = Field(description="章号")
|
||
beats: list[str] = Field(default_factory=list, description="本章节拍/情节要点(有序)")
|
||
foreshadow_windows: list[ForeshadowWindow] = Field(
|
||
default_factory=list,
|
||
description="本章关联的伏笔回收窗口;无则空列表",
|
||
)
|
||
|
||
|
||
class OutlineResult(BaseModel):
|
||
"""大纲结构化产出:分卷分章的章节大纲清单(ARCH §5.4 writes=outline)。
|
||
|
||
声明式 writes(不变量 #3):本结构是排大纲的纯产物,落 `outline` 表经验收/
|
||
端点(T3.5),不在 agent 节点直接写库。
|
||
"""
|
||
|
||
chapters: list[OutlineChapter] = Field(
|
||
default_factory=list,
|
||
description="分章大纲;无则空列表",
|
||
)
|
||
|
||
|
||
# ---- 伏笔续审 Agent 结构化输出(ARCH §5.4 foreshadow-analyst 行 / §6.2)----
|
||
|
||
|
||
class ForeshadowSuggestion(BaseModel):
|
||
"""单条伏笔建议:本章新埋线 或 疑似回收(ARCH §6.2)。
|
||
|
||
只读建议——作者在验收裁决里确认后才登记/改状态(不变量 #3);本审不写库。
|
||
`code` 可缺(新埋线尚无编码时由作者命名);`where` 给本章定位。
|
||
"""
|
||
|
||
code: str | None = Field(
|
||
default=None, description="伏笔编码(关联已登记 foreshadow.code;新埋可缺)"
|
||
)
|
||
title: str = Field(description="伏笔标题/一句话描述")
|
||
where: str | None = Field(default=None, description="本章定位:哪一段埋下/疑似回收")
|
||
note: str | None = Field(default=None, description="建议说明(埋设要点 / 回收理由)")
|
||
|
||
|
||
class ForeshadowReview(BaseModel):
|
||
"""伏笔续审结构化产出:本章新埋 + 疑似回收两组建议(ARCH §5.4 / §6.2)。
|
||
|
||
只读建议清单(不变量 #3);登记/状态变更经验收裁决(作者确认)。
|
||
"""
|
||
|
||
planted: list[ForeshadowSuggestion] = Field(
|
||
default_factory=list,
|
||
description="本章疑似新埋的伏笔;无则空列表",
|
||
)
|
||
resolved: list[ForeshadowSuggestion] = Field(
|
||
default_factory=list,
|
||
description="本章疑似回收/收束的伏笔;无则空列表",
|
||
)
|
||
|
||
|
||
# ---- 节奏续审 Agent 结构化输出(ARCH §5.4 pace-checker 行 / §6.4)----
|
||
|
||
|
||
class PaceIssue(BaseModel):
|
||
"""单条节奏问题:注水段定位 + 原因(ARCH §6.4)。"""
|
||
|
||
where: str = Field(description="本章定位:哪一段疑似注水/拖沓")
|
||
reason: str = Field(description="判定理由(信息密度低 / 重复 / 偏题等)")
|
||
|
||
|
||
class PaceReview(BaseModel):
|
||
"""节奏续审结构化产出:注水段 + 章末钩子 + 爽点节拍图(ARCH §5.4 / §6.4)。
|
||
|
||
只读(不变量 #3)。`beat_map` 是逐段节拍强度序列(整数),供前端 ▁▃▅ 可视化。
|
||
"""
|
||
|
||
water: list[PaceIssue] = Field(
|
||
default_factory=list,
|
||
description="疑似注水/拖沓段;无则空列表",
|
||
)
|
||
hook: bool = Field(default=False, description="章末是否有有效钩子(悬念/反转/期待)")
|
||
beat_map: list[int] = Field(
|
||
default_factory=list,
|
||
description="逐段爽点节拍强度序列(整数,供前端节拍图可视化)",
|
||
)
|
||
|
||
|
||
# ---- 文风提取轨结构化输出(ARCH §5.4 style-auditor「提取」轨 / §6.9)----
|
||
|
||
|
||
class StyleDimension(BaseModel):
|
||
"""单维文风指纹:维度名 + 取值 + 原文证据引用(ARCH §6.9)。
|
||
|
||
每维必须带原文证据(quotes),便于作者核验「这条文风结论来自哪句」。
|
||
"""
|
||
|
||
name: str = Field(description="文风维度名(如句长节奏 / 叙事人称 / 用词风格)")
|
||
value: str = Field(description="该维度在样本中的取值/概括")
|
||
evidence: list[str] = Field(
|
||
default_factory=list,
|
||
description="原文证据:支撑该维度判定的样本原句摘录",
|
||
)
|
||
|
||
|
||
class StyleFingerprintResult(BaseModel):
|
||
"""文风指纹提取产出:16 维文风指纹清单(ARCH §5.4 提取轨)。
|
||
|
||
声明式 writes(不变量 #3):本结构是从样本提取的纯产物,落 `style_fingerprint` 表
|
||
经端点(T4.3),不在 agent 节点直接写库。16 维 = 9 通用 + 7 中文网文专有。
|
||
"""
|
||
|
||
dimensions: list[StyleDimension] = Field(
|
||
default_factory=list,
|
||
description="文风维度清单(每维带原文证据);无则空列表",
|
||
)
|
||
|
||
|
||
# ---- 文风漂移轨结构化输出(ARCH §5.4 style-auditor「打分」轨 = 第四审)----
|
||
|
||
|
||
class StyleDriftSegment(BaseModel):
|
||
"""单个漂移段:原文锚 + 段索引 + 相似度分 + 可选标签(ARCH §5.4 打分轨)。
|
||
|
||
`text` 是从本章草稿**逐字摘录**的该漂移段原文(含标点,不得改写)——前端据此用
|
||
内容匹配在终稿里定位回炉目标(**内容锚**,不靠位置 idx,避免分段方式不一致导致定位
|
||
到错段 / 越界静默失败)。`idx` 仅供前端展示排序(0 起);`score` 是该段相对文风指纹
|
||
的相似度(0–100,越低越偏);`label` 可缺(如「机翻腔」「叙述拖沓」等漂移类型说明)。
|
||
"""
|
||
|
||
idx: int = Field(description="本章段索引(0 起,仅供展示排序)")
|
||
text: str = Field(
|
||
default="",
|
||
description="该漂移段从本章草稿**逐字摘录**的原文(含标点,不得改写);供前端内容锚定位回炉目标",
|
||
)
|
||
score: int = Field(description="该段相对文风指纹的相似度(0–100,越低越偏离)")
|
||
label: str | None = Field(default=None, description="漂移类型标签(如「机翻腔」);可缺")
|
||
|
||
|
||
class StyleDriftReview(BaseModel):
|
||
"""文风漂移续审结构化产出:整体相似度 + 低相似段(ARCH §5.4 / 第四审)。
|
||
|
||
只读(不变量 #3)。`score` 是整章相对文风指纹的整体相似度(0–100)。
|
||
**无指纹优雅降级**:材料中无文风指纹时返回 `score=100, segments=[]`(默认值),
|
||
不报错、不阻塞其余审(§5.2 失败隔离之外的内容性兜底)。
|
||
"""
|
||
|
||
score: int = Field(default=100, description="整章相对文风指纹的整体相似度(0–100)")
|
||
segments: list[StyleDriftSegment] = Field(
|
||
default_factory=list,
|
||
description="低相似度(疑似漂移)段清单;无则空列表(含无指纹降级)",
|
||
)
|
||
|
||
|
||
# ---- 人物塑造续审结构化输出(灵感③ · advisory 单维;仅建议、不阻断验收)----
|
||
|
||
|
||
class CharacterizationIssue(BaseModel):
|
||
"""单条人物塑造问题:角色 + 维度 + 定位 + 逐字引用 + 诊断 + 建议 + 置信度。
|
||
|
||
以人物卡 `motive`/`appearance` 为客观锚点。`quote` 是从本章草稿**逐字摘录**的支撑
|
||
片段(内容锚,不得改写);`confidence` 供作者过滤主观/边缘诊断(对抗 LLM-judge 偏见)。
|
||
全字段给默认值守解析韧性(畸形/缺字段不炸)。
|
||
"""
|
||
|
||
character: str = Field(
|
||
default="", description="涉及的角色名(尽量对齐人物卡 name;无法归属留空)"
|
||
)
|
||
aspect: str = Field(
|
||
default="",
|
||
description="塑造维度(动机一致性 / 声音辨识度 / 行为逻辑 / 外貌一致性 / 扁平化 等)",
|
||
)
|
||
where: str = Field(default="", description="本章定位:哪一段/哪个场景体现该问题")
|
||
quote: str = Field(
|
||
default="",
|
||
description="从本章草稿**逐字摘录**的支撑片段(含标点,不得改写);供作者内容锚定位",
|
||
)
|
||
diagnosis: str = Field(default="", description="诊断说明:为何此处人物塑造偏弱/失真")
|
||
suggestion: str = Field(default="", description="改进建议(方向性,不含改写后的正文)")
|
||
confidence: float = Field(
|
||
default=0.0, description="置信度 0.0–1.0(越确信越高,主观/边缘给低值,供作者过滤)"
|
||
)
|
||
|
||
|
||
class CharacterizationReview(BaseModel):
|
||
"""人物塑造续审结构化产出:只读建议清单(advisory,不阻断验收,不变量 #3)。
|
||
|
||
**带默认值降级**:材料无人物卡 / 无明显问题时返回 `issues=[]`(默认值),不报错、
|
||
不阻塞其余审(§5.2 失败隔离之外的内容性兜底,仿 `StyleDriftReview`)。仅建议——
|
||
不进 conflicts、不碰验收 gate(灵感 D2 advisory)。
|
||
"""
|
||
|
||
issues: list[CharacterizationIssue] = Field(
|
||
default_factory=list,
|
||
description="人物塑造问题清单(每条带角色/维度/定位/逐字引用/置信度);无则空列表",
|
||
)
|
||
|
||
|
||
# ---- worldbuilder 结构化输出(ARCH §5.4 worldbuilder 行 / §6.5 / §4.5)----
|
||
|
||
|
||
class WorldEntityCard(BaseModel):
|
||
"""单个世界观实体(ARCH §5.4:`{type,name,rules}` 硬规则显式)。
|
||
|
||
`type`(势力/地理/力量体系/物品…)+ `name` + `rules`(**显式硬规则**清单:
|
||
不可违背的设定,供后续 continuity 续审引用比对)。映射 `world_entities` 表
|
||
(type/name 列 + rules 入 JSONB);落库经入库端点(T5.2,不变量 #3)。
|
||
"""
|
||
|
||
type: str = Field(description="实体类型(势力 / 地理 / 力量体系 / 物品 / 概念 等)")
|
||
name: str = Field(description="实体名")
|
||
rules: list[str] = Field(
|
||
default_factory=list,
|
||
description="该实体的硬规则清单(显式不可违背设定,供 continuity 校验引用)",
|
||
)
|
||
|
||
|
||
class WorldGenResult(BaseModel):
|
||
"""worldbuilder 结构化产出:世界观实体清单(ARCH §5.4 writes=world_entities)。
|
||
|
||
声明式 writes(不变量 #3):本结构是 worldbuilder 的纯产物,落 `world_entities` 表
|
||
经入库端点(T5.2),不在 agent 节点直接写库。
|
||
"""
|
||
|
||
entities: list[WorldEntityCard] = Field(
|
||
default_factory=list,
|
||
description="生成的世界观实体清单;无则空列表",
|
||
)
|
||
|
||
|
||
# ---- character-gen 结构化输出(ARCH §5.4 character-gen 行 / §6.5 / §4.5)----
|
||
|
||
|
||
class CharacterRelation(BaseModel):
|
||
"""单条人物关系(写入 `characters.relations` JSONB 冗余)。
|
||
|
||
`name`(关系对象角色名)+ `kind`(宿敌 / 师徒 / CP / 同盟 …)+ 可选 `note`。
|
||
双向边由编排器在入库时统一维护(§4.5 三关键能力之三),本 schema 仅声明本侧。
|
||
"""
|
||
|
||
name: str = Field(description="关系对象的角色名")
|
||
kind: str = Field(description="关系类型(宿敌 / 师徒 / CP / 同盟 / 工具人 等)")
|
||
note: str | None = Field(default=None, description="关系说明(可缺)")
|
||
|
||
|
||
class CharacterCard(BaseModel):
|
||
"""单张结构化角色卡(ARCH §5.4 character-gen 行:name/role/traits/backstory/arc/...)。
|
||
|
||
映射 `characters` 表列(name/role/backstory + traits/arc/speech_tics/tags/relations
|
||
入 JSONB);落库经入库端点(T5.2,不变量 #3)。`role` = 角色定位(主角/CP/对手/导师/
|
||
工具人,§4.5 网文专属)。
|
||
"""
|
||
|
||
name: str = Field(description="角色名(取名风格契合世界观)")
|
||
role: str = Field(description="角色定位(主角 / CP / 对手 / 导师 / 工具人 等)")
|
||
traits: list[str] = Field(
|
||
default_factory=list,
|
||
description="性格特质清单(仅核心-表层-阴影三层;动机/欲望/恐惧落 motive)",
|
||
)
|
||
appearance: str = Field(
|
||
default="",
|
||
description="外貌形象(身形/五官/气质/标志性外观/常着装扮);缺则空串(守解析韧性)",
|
||
)
|
||
motive: str = Field(
|
||
default="",
|
||
description="核心动机/欲望/恐惧(驱动行动的唯一内驱,arc 由此推动);缺则空串",
|
||
)
|
||
backstory: str = Field(description="背景故事(出身、关键经历、创伤/转折点)")
|
||
arc: str = Field(description="人物弧光(起点 → 转变 → 终点,与剧情挂钩,由 motive 推动)")
|
||
speech_tics: list[str] = Field(
|
||
default_factory=list,
|
||
description="口癖/语言风格(用词偏好、口头禅,喂给文风一致性)",
|
||
)
|
||
tags: list[str] = Field(
|
||
default_factory=list,
|
||
description="人设标签/萌点(网文专属,辅助检索与差异化)",
|
||
)
|
||
relations: list[CharacterRelation] = Field(
|
||
default_factory=list,
|
||
description="关系网:与已有/同批角色建边;无则空列表",
|
||
)
|
||
|
||
|
||
class CharacterGenResult(BaseModel):
|
||
"""character-gen 结构化产出:一组角色卡(ARCH §5.4 writes=characters / §4.5 群像)。
|
||
|
||
声明式 writes(不变量 #3):本结构是 character-gen 的纯产物,落 `characters` 表
|
||
经入库端点(T5.2),且**入库前过编排器 continuity 校验**(§6.5)。单生成 → 1 张卡;
|
||
批量群像 → 多张(防雷同:生成时注入「已生成卡 + 已有角色」要求差异化)。
|
||
"""
|
||
|
||
cards: list[CharacterCard] = Field(
|
||
default_factory=list,
|
||
description="生成的角色卡清单;无则空列表",
|
||
)
|
||
|
||
|
||
# ---- 创作工具箱 · 脑洞生成器结构化输出(T6 通用生成器框架 · brainstorm)----
|
||
|
||
|
||
class Idea(BaseModel):
|
||
"""单条脑洞:前提 + 钩子 + 适配题材(创作工具箱最简生成器产物)。
|
||
|
||
纯预览产物——不映射任何业务表、不入库(`brainstorm_spec.writes=[]`,不变量 #3)。
|
||
`genre_fit` 可缺(作者未限定赛道时由模型留空)。
|
||
"""
|
||
|
||
premise: str = Field(description="一句话脑洞/设定前提")
|
||
hook: str = Field(description="抓人钩子:为何让读者想追读")
|
||
genre_fit: str | None = Field(default=None, description="适配题材/赛道(可缺)")
|
||
|
||
|
||
class IdeaListResult(BaseModel):
|
||
"""脑洞生成器结构化产出:一组脑洞(T6 · 通用生成器框架首个最简生成器)。
|
||
|
||
声明式只读(不变量 #3):纯预览,端点同步返回,不写任何业务表(仅记 ledger)。
|
||
"""
|
||
|
||
ideas: list[Idea] = Field(
|
||
default_factory=list,
|
||
description="生成的脑洞清单;无则空列表",
|
||
)
|
||
|
||
|
||
# ---- 创作工具箱 · 书名生成器(book-title;light;纯预览 writes=[])----
|
||
|
||
|
||
class Title(BaseModel):
|
||
"""单个书名候选:书名 + 取名理由(创作工具箱 book-title 产物)。
|
||
|
||
纯预览产物——不映射任何业务表、不入库(`book_title_spec.writes=[]`,不变量 #3)。
|
||
"""
|
||
|
||
title: str = Field(description="书名候选")
|
||
rationale: str = Field(description="取名理由:为何抓人/契合题材")
|
||
|
||
|
||
class TitleListResult(BaseModel):
|
||
"""书名生成器结构化产出:一组书名候选(T6 创作工具箱)。
|
||
|
||
声明式只读(不变量 #3):纯预览,端点同步返回,不写任何业务表。
|
||
"""
|
||
|
||
titles: list[Title] = Field(
|
||
default_factory=list,
|
||
description="生成的书名候选清单;无则空列表",
|
||
)
|
||
|
||
|
||
# ---- 创作工具箱 · 简介生成器(blurb;analyst;纯预览 writes=[])----
|
||
|
||
|
||
class Blurb(BaseModel):
|
||
"""单条作品简介:正文 + 切入角度(创作工具箱 blurb 产物)。
|
||
|
||
纯预览产物——不映射任何业务表、不入库(`blurb_spec.writes=[]`,不变量 #3)。
|
||
"""
|
||
|
||
text: str = Field(description="简介正文(一段抓人文案)")
|
||
angle: str = Field(description="切入角度:从哪个卖点/钩子切入")
|
||
|
||
|
||
class BlurbResult(BaseModel):
|
||
"""简介生成器结构化产出:多版差异化简介(T6 创作工具箱)。
|
||
|
||
声明式只读(不变量 #3):纯预览,端点同步返回,不写任何业务表。
|
||
"""
|
||
|
||
variants: list[Blurb] = Field(
|
||
default_factory=list,
|
||
description="生成的简介变体清单;无则空列表",
|
||
)
|
||
|
||
|
||
# ---- 创作工具箱 · 取名生成器(name;light;纯预览 writes=[])----
|
||
|
||
|
||
class NameSuggestion(BaseModel):
|
||
"""单条命名建议:名字 + 类别 + 可选说明(创作工具箱 name 产物)。
|
||
|
||
纯预览产物——不映射任何业务表、不入库(`name_spec.writes=[]`,不变量 #3)。
|
||
`kind` 标注命名对象类别(人物/势力/地点/功法/物品…)。
|
||
"""
|
||
|
||
name: str = Field(description="建议的名字")
|
||
kind: str = Field(description="命名对象类别(人物 / 势力 / 地点 / 功法 / 物品 等)")
|
||
note: str | None = Field(default=None, description="命名说明(取意/出处,可缺)")
|
||
|
||
|
||
class NameListResult(BaseModel):
|
||
"""取名生成器结构化产出:一组命名建议(T6 创作工具箱)。
|
||
|
||
声明式只读(不变量 #3):纯预览,端点同步返回,不写任何业务表。
|
||
"""
|
||
|
||
names: list[NameSuggestion] = Field(
|
||
default_factory=list,
|
||
description="生成的命名建议清单;无则空列表",
|
||
)
|
||
|
||
|
||
# ---- 创作工具箱 · 金手指生成器(golden-finger;writer;声明式 writes=world_entities)----
|
||
|
||
|
||
class GoldenFinger(BaseModel):
|
||
"""单个金手指系统:名称 + 机制 + 成长 + 限制(创作工具箱 golden-finger 产物)。
|
||
|
||
映射 `world_entities`(力量体系类);落库经入库端点(不变量 #3)。`limits` = 不可
|
||
违背的能力边界/代价,供 continuity 续审引用比对(防能力不符)。
|
||
"""
|
||
|
||
name: str = Field(description="金手指名称")
|
||
mechanism: str = Field(description="核心机制:如何运作、触发条件")
|
||
growth: str = Field(description="成长路径:随剧情如何升级/进阶")
|
||
limits: str = Field(description="限制与代价:能力边界、副作用(防越界,供一致性校验)")
|
||
|
||
|
||
class GoldenFingerResult(BaseModel):
|
||
"""金手指生成器结构化产出:一组金手指系统(T6 创作工具箱)。
|
||
|
||
声明式 writes(不变量 #3):本结构是纯产物,落 `world_entities` 表经入库端点,
|
||
不在 agent 节点直接写库。
|
||
"""
|
||
|
||
systems: list[GoldenFinger] = Field(
|
||
default_factory=list,
|
||
description="生成的金手指系统清单;无则空列表",
|
||
)
|
||
|
||
|
||
# ---- 创作工具箱 · 术语表生成器(glossary;analyst;声明式 writes=world_entities)----
|
||
|
||
|
||
class GlossaryTerm(BaseModel):
|
||
"""单条术语:名称 + 类型 + 定义 + 硬规则(创作工具箱 glossary 产物)。
|
||
|
||
映射 `world_entities`(concept/术语类);`rules` = 该术语的硬规则清单,
|
||
对齐 `world_entities.rules`,供 continuity 续审引用比对(不变量 #3)。
|
||
"""
|
||
|
||
name: str = Field(description="术语名")
|
||
type: str = Field(description="术语类型(境界 / 功法 / 货币 / 度量 / 称谓 等)")
|
||
definition: str = Field(description="术语定义(一句话释义)")
|
||
rules: list[str] = Field(
|
||
default_factory=list,
|
||
description="该术语的硬规则清单(不可违背设定,供 continuity 校验引用)",
|
||
)
|
||
|
||
|
||
class GlossaryResult(BaseModel):
|
||
"""术语表生成器结构化产出:一组术语(T6 创作工具箱)。
|
||
|
||
声明式 writes(不变量 #3):本结构是纯产物,落 `world_entities` 表经入库端点,
|
||
不在 agent 节点直接写库。
|
||
"""
|
||
|
||
terms: list[GlossaryTerm] = Field(
|
||
default_factory=list,
|
||
description="生成的术语清单;无则空列表",
|
||
)
|
||
|
||
|
||
# ---- 创作工具箱 · 开篇生成器(opening;writer;纯预览 writes=[])----
|
||
|
||
|
||
class Opening(BaseModel):
|
||
"""单版开篇正文(创作工具箱 opening 产物)。
|
||
|
||
纯预览产物——不映射任何业务表、不入库(`opening_spec.writes=[]`,不变量 #3)。
|
||
"""
|
||
|
||
text: str = Field(description="开篇正文(一段成稿文本)")
|
||
|
||
|
||
class OpeningResult(BaseModel):
|
||
"""开篇生成器结构化产出:多版开篇(T6 创作工具箱)。
|
||
|
||
声明式只读(不变量 #3):纯预览,端点同步返回,不写任何业务表。
|
||
"""
|
||
|
||
variants: list[Opening] = Field(
|
||
default_factory=list,
|
||
description="生成的开篇变体清单;无则空列表",
|
||
)
|
||
|
||
|
||
# ---- 创作工具箱 · 细纲生成器(fine-outline;analyst;声明式 writes=outline)----
|
||
|
||
|
||
class Scene(BaseModel):
|
||
"""单个场景节拍:序号 + 节拍 + 目的 + 冲突 + 钩子(创作工具箱 fine-outline 产物)。
|
||
|
||
把某章 outline 的粗节拍展开为细粒度场景;落库经入库端点(不变量 #3)。
|
||
"""
|
||
|
||
idx: int = Field(description="场景序号(章内有序,0/1 起均可)")
|
||
beat: str = Field(description="场景节拍:本场发生什么")
|
||
purpose: str = Field(description="叙事目的:推动主线/塑造人物/铺垫伏笔")
|
||
conflict: str = Field(description="本场冲突/张力来源")
|
||
hook: str = Field(description="场景钩子:驱动读者读下一场的悬念")
|
||
|
||
|
||
class DetailedOutlineResult(BaseModel):
|
||
"""细纲生成器结构化产出:某章展开的场景清单(T6 创作工具箱)。
|
||
|
||
声明式 writes(不变量 #3):本结构是纯产物,落 `outline` 表经入库端点,
|
||
不在 agent 节点直接写库。
|
||
"""
|
||
|
||
scenes: list[Scene] = Field(
|
||
default_factory=list,
|
||
description="展开的场景清单;无则空列表",
|
||
)
|
||
|
||
|
||
# ---- 竞品快赢 · 续写生成器(continue;writer;纯预览 writes=[])----
|
||
|
||
|
||
class ContinuationResult(BaseModel):
|
||
"""续写生成器结构化产出:承接前文的续写正文(Scope B 竞品快赢)。
|
||
|
||
纯预览产物——不映射任何业务表、不入库(`continue_spec.writes=[]`,不变量 #3)。
|
||
"""
|
||
|
||
text: str = Field(description="续写正文(承接前文,可直接作为下文草稿)")
|
||
|
||
|
||
# ---- 竞品快赢 · 扩写生成器(expand;writer;纯预览 writes=[])----
|
||
|
||
|
||
class PolishResult(BaseModel):
|
||
"""扩写生成器结构化产出:在原文基础上扩写/丰富后的正文(Scope B 竞品快赢)。
|
||
|
||
纯预览产物——不映射任何业务表、不入库(`expand_spec.writes=[]`,不变量 #3)。
|
||
"""
|
||
|
||
text: str = Field(description="扩写后的正文(在原文基础上丰富细节/铺陈)")
|
||
|
||
|
||
# ---- 竞品快赢 · 降 AI 率生成器(de-ai;analyst;纯预览 writes=[])----
|
||
|
||
|
||
class DeAiResult(BaseModel):
|
||
"""降 AI 率生成器结构化产出:去 AI 腔/口语化改写后的正文(Scope B 竞品快赢)。
|
||
|
||
纯预览产物——不映射任何业务表、不入库(`de_ai_spec.writes=[]`,不变量 #3)。
|
||
"""
|
||
|
||
text: str = Field(description="降 AI 率改写后的正文(去机翻腔、更自然的人写质感)")
|
||
|
||
|
||
# ---- 竞品快赢 · 拆书生成器(teardown;analyst;纯预览 writes=[])----
|
||
|
||
|
||
class BookTeardownResult(BaseModel):
|
||
"""拆书生成器结构化产出:样本作品的结构化拆解(Scope B 竞品快赢)。
|
||
|
||
F1:可经 ingest 端点把结论拍平为项目 `rules` 条目落库(`teardown_spec.writes=["rules"]`);
|
||
入库仍经白名单 gate(不变量 #3),预览(generate)不写库。
|
||
"""
|
||
|
||
themes: list[str] = Field(
|
||
default_factory=list,
|
||
description="核心主题/立意清单;无则空列表",
|
||
)
|
||
archetypes: list[str] = Field(
|
||
default_factory=list,
|
||
description="人物原型/角色模板清单;无则空列表",
|
||
)
|
||
structure: str = Field(description="叙事结构概述(开篇/铺垫/高潮/收束的脉络)")
|
||
hooks: list[str] = Field(
|
||
default_factory=list,
|
||
description="抓人钩子/爽点套路清单;无则空列表",
|
||
)
|
||
|
||
|
||
# ---- 立项方案生成器(project-plan;analyst;纯预览 writes=())----
|
||
|
||
|
||
class ProjectPlanResult(BaseModel):
|
||
"""立项方案生成器结构化产出:书级蓝图,供预填立项向导(灵感⑤)。
|
||
|
||
纯预览产物——不映射任何业务表、不入库(`project_plan_spec.writes=()`,不变量 #3)。
|
||
**全字段给默认值守解析韧性**(仿 `StyleDriftReview` 降级范式):LLM 漏产任一字段
|
||
不致整个方案解析失败——缺字段降级为空(书名候选空列表 / 文本字段空串),前端只回填
|
||
非空交集、不覆盖作者已编辑值。
|
||
"""
|
||
|
||
title_candidates: list[str] = Field(
|
||
default_factory=list,
|
||
description="书名候选清单(3–5 个差异化候选);无则空列表",
|
||
)
|
||
setting: str = Field(default="", description="时空背景(时代/地域/世界底层规则概述);缺则空串")
|
||
narrative_structure: str = Field(
|
||
default="", description="叙事结构(主线推进骨架:立钩/升级/转折节拍);缺则空串"
|
||
)
|
||
story_core: str = Field(
|
||
default="",
|
||
description="故事核心(立意 + 核心冲突:主角要什么、挡路的是什么、代价);缺则空串",
|
||
)
|
||
ending_design: str = Field(default="", description="结局设计(收束方向与情绪落点);缺则空串")
|
||
tone: str = Field(default="", description="基调(全书情绪底色与阅读质感);缺则空串")
|