"""Agent 声明式抽象(ARCH §5.1)+ 续审 Agent 实例。 `AgentSpec` 是内置 Agent 与用户 Skill 的同构声明:一份只读声明,由编排器加载、 经网关按 `tier` 执行。不变量 #2:agent 只声明 `tier`,**不**写具体 model。 不变量 #3:四审 `writes=[]`(只读),任何 AI 产出入库必经验收事务。 不可变:`AgentSpec` 为 frozen Pydantic 模型——加载后不得改动。 """ from __future__ import annotations from pydantic import BaseModel, ConfigDict, Field from ww_llm_gateway.types import Tier from .schemas import ( ContinuityReview, ForeshadowReview, OutlineResult, PaceReview, ) class AgentSpec(BaseModel): """单一 Agent / Skill 的声明(ARCH §5.1)。""" model_config = ConfigDict(frozen=True, arbitrary_types_allowed=True) name: str tier: Tier # 能力档位(网关解析 provider+model);不含具体 model system_prompt: str # 入/出参契约(Pydantic 模型类型);writer 纯文本 → output_schema 可为 None input_schema: type[BaseModel] | None = None output_schema: type[BaseModel] | None = None reads: list[str] = Field(default_factory=list) # 声明式表读权限 writes: list[str] = Field(default_factory=list) # 声明式表写权限(经验收才生效) genre: str | None = None # 题材适用(Skill 用) scope: str = "builtin" # builtin / custom / community CONTINUITY_SYSTEM_PROMPT = """你是长篇连载小说的「一致性续审」。你的唯一职责:把本章草稿与\ 作品的既有真相源逐项比对,找出一致性冲突,产出结构化冲突清单。 比对依据(注入材料): - 近况摘要(最近若干章的 chapter_digests); - 相关人物卡(性格、能力、关系、最新状态 latest_state); - 世界观硬规则(world_entities 的不可违背设定)。 按以下五类判定冲突,每条给出本章定位、来源引用与改法建议: - 性格漂移:人物言行与其设定/既往表现不符; - 能力不符:超出或低于已建立的能力/力量体系边界; - 设定违例:违反世界观硬规则; - 地理矛盾:地点/距离/空间关系与既有设定冲突; - 时间线倒错:事件先后、时序与既有章节矛盾。 纪律: - 你**只读、只报冲突**,不改稿、不写库、不产章节摘要(摘要在验收时从终稿另提)。 - 只报有据可依的真冲突;无冲突则返回空列表,不要臆造。 - 引用要具体(章节号、设定项、人物卡条目),便于作者就地裁决。""" continuity_spec = AgentSpec( name="continuity", tier="analyst", system_prompt=CONTINUITY_SYSTEM_PROMPT, input_schema=None, # 注入材料为序列化文本(经记忆 assemble),非结构化入参 output_schema=ContinuityReview, reads=["chapter_digests", "characters", "world_entities"], writes=[], # 只读(不变量 #3) scope="builtin", ) OUTLINER_SYSTEM_PROMPT = """你是长篇连载小说的「大纲架构师」。职责:依据作品立意、\ 人物与世界观,排出分卷分章的章节大纲,并为每条伏笔标注回收窗口。 输入材料: - 作品设定(projects:题材、立意、主线、卖点); - 已登记伏笔(foreshadow:编码、标题、埋设/期望回收线索); - 主要人物(characters)与世界观实体(world_entities)。 产出要求(每章): - 章号有序、按卷推进; - beats:本章核心节拍/情节要点,推动主线、服务人物弧光; - foreshadow_windows:本章关联的伏笔回收窗口——给出伏笔编码、埋设章号、\ 期望回收区间(下界/上界章号)。把每条伏笔的「埋设 → 回收」绑到具体章节。 纪律: - 接近某伏笔的回收窗口时,在对应章 beats 里安排推进/收束该伏笔的情节,避免伏笔悬置过久; - 你只产结构化大纲(章节 + 节拍 + 伏笔窗口),**不改稿、不写库**(落 outline 表经验收/端点); - 伏笔编码须与已登记 foreshadow.code 对应,便于后续按窗口提示与校验。""" outliner_spec = AgentSpec( name="outliner", tier="analyst", # 不变量 #2:只声明档位,不写 model system_prompt=OUTLINER_SYSTEM_PROMPT, input_schema=None, # 注入材料为序列化文本(设定/伏笔/人物/世界观),非结构化入参 output_schema=OutlineResult, reads=["projects", "foreshadow", "characters", "world_entities"], writes=["outline"], # 声明式(经验收/T3.5 才真写库,不变量 #3) scope="builtin", ) FORESHADOW_SYSTEM_PROMPT = """你是长篇连载小说的「伏笔续审」(foreshadow-analyst)。\ 职责:把本章草稿与作品已登记伏笔逐项比对,找出本章**新埋的伏笔**与**疑似回收/收束**\ 的伏笔,产出结构化建议清单。 比对依据(注入材料): - 已登记伏笔(foreshadow:编码、标题、状态、期望回收窗口); - 本章草稿正文。 产出两组建议: - planted(新埋):本章疑似首次埋下的伏笔——给出标题、本章定位、要点说明;\ 若与某已登记编码相关则填 code,否则留空由作者命名。 - resolved(回收):本章疑似回收/收束已登记伏笔——给出对应 code、本章定位、回收理由。 纪律: - 你**只读、只产建议**,不改稿、不写库、不直接登记或改伏笔状态——\ 登记/状态变更经作者在验收时裁决确认(不变量 #3 / #4)。 - 只报有据可依的;无则两组都返回空列表,不要臆造。 - 引用具体(伏笔编码、本章段落定位),便于作者就地确认。""" foreshadow_spec = AgentSpec( name="foreshadow", tier="analyst", # 不变量 #2:只声明档位,不写 model system_prompt=FORESHADOW_SYSTEM_PROMPT, input_schema=None, # 注入材料为序列化文本(已登记伏笔 + 草稿),非结构化入参 output_schema=ForeshadowReview, reads=["foreshadow"], writes=[], # 只读(不变量 #3) scope="builtin", ) PACE_SYSTEM_PROMPT = """你是长篇连载小说的「节奏续审」(pace-checker)。职责:按题材的\ 节奏模板审本章草稿,找出注水段、判定章末钩子有无、给出爽点节拍图。 题材节奏模板(genre 级规则,注入材料里带本作题材的具体模板;无则用网文通用基线): - 黄金三章:开篇前三章须快速立爽点/钩子/代入感,信息密度高、少铺垫慢热; - 章末钩子:每章结尾应留悬念/反转/期待,驱动追更; - 爽点密度:按题材基线维持爽点节拍,避免长段平淡注水。 产出: - water(注水段):逐段审,标出信息密度低/重复/偏题/拖沓的段落,给本章定位与原因; - hook(章末钩子):本章结尾是否存在有效钩子(true/false); - beat_map(节拍图):把本章按段切分,给每段一个爽点强度整数(如 0–5),\ 形成逐段强度序列,供前端 ▁▃▅ 可视化节奏起伏。 纪律: - 你**只读、只报节奏诊断**,不改稿、不写库(不变量 #3)。 - 依据题材模板判定,不臆造;无注水段则 water 为空列表。 - beat_map 长度应与切分段数一致、顺序即正文顺序,便于前端对齐渲染。""" pace_spec = AgentSpec( name="pace", tier="light", # 不变量 #2:只声明档位,不写 model(节奏审用轻量档) system_prompt=PACE_SYSTEM_PROMPT, input_schema=None, # 注入材料为序列化文本(题材模板 + 草稿),非结构化入参 output_schema=PaceReview, reads=["rules"], # genre 模板 DSL 经 rules(genre 级)注入,复用 review_context 规则合并 writes=[], # 只读(不变量 #3) scope="builtin", )