"""续审 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="改法建议") 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 打分轨)。 `idx` 是本章段索引(0 起),`score` 是该段相对文风指纹的相似度(0–100,越低越偏); `label` 可缺(如「机翻腔」「叙述拖沓」等漂移类型说明)。 """ idx: int = Field(description="本章段索引(0 起)") 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="低相似度(疑似漂移)段清单;无则空列表(含无指纹降级)", ) # ---- 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="性格特质清单(核心-表层-阴影 / 动机 / 欲望 / 恐惧)", ) backstory: str = Field(description="背景故事(出身、关键经历、创伤/转折点)") arc: str = Field(description="人物弧光(起点 → 转变 → 终点,与剧情挂钩)") 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="生成的脑洞清单;无则空列表", )