Files
writer-work-flow/packages/agents/ww_agents/specs.py
Yaojia Wang 72b3c81146 feat(review): 人物塑造 advisory 审(第五审,仅建议不阻断验收)
写→审链新增第五审「人物塑造」(灵感③/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 作客观锚点)。
2026-07-06 17:08:38 +02:00

368 lines
16 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.

"""Agent 声明式抽象ARCH §5.1+ 续审 Agent 实例Prompt 外置方案A · 步3/5
`AgentSpec` 是内置 Agent 与用户 Skill 的同构声明:一份只读声明,由编排器加载、
经网关按 `tier` 执行。不变量 #2agent 只声明 `tier`**不**写具体 model。
不变量 #3四审 `writes=()`(只读),任何 AI 产出入库必经验收事务。
prompt 散文已外置至 `prompts/<spec.name>.md``system_prompt=load_prompt(name)`
import 期读盘+缓存+规整去BOM/LF/NFC/rstrip尾LF/fail-fast。output schema 由
`SCHEMA_CATALOG[name]` 单向派生(唯一真相源)。`AgentSpec` 从 `spec_model` re-export
import 路径不变,现有消费方不受影响)。
不可变:`AgentSpec` 为 frozen Pydantic 模型——加载后不得改动。
"""
from __future__ import annotations
from collections.abc import Mapping
from types import MappingProxyType
from typing import Final
from .prompt_loader import load_prompt
from .schema_catalog import SCHEMA_CATALOG
from .spec_model import AgentSpec
__all__ = [
"REVIEW_RESERVED_NAMES",
"SPECS",
"AgentSpec",
"blurb_spec",
"book_title_spec",
"brainstorm_spec",
"character_gen_spec",
"characterization_spec",
"continue_spec",
"continuity_spec",
"de_ai_spec",
"expand_spec",
"fine_outline_spec",
"foreshadow_spec",
"glossary_spec",
"golden_finger_spec",
"name_spec",
"opening_spec",
"outliner_spec",
"pace_spec",
"project_plan_spec",
"refiner_spec",
"style_drift_spec",
"style_extract_spec",
"teardown_spec",
"worldbuilder_spec",
]
continuity_spec = AgentSpec(
name="continuity",
tier="analyst",
system_prompt=load_prompt("continuity"),
input_schema=None, # 注入材料为序列化文本(经记忆 assemble非结构化入参
output_schema=SCHEMA_CATALOG["continuity"],
reads=("chapter_digests", "characters", "world_entities"),
writes=(), # 只读(不变量 #3
scope="builtin",
)
outliner_spec = AgentSpec(
name="outliner",
tier="analyst", # 不变量 #2只声明档位不写 model
system_prompt=load_prompt("outliner"),
input_schema=None, # 注入材料为序列化文本(设定/伏笔/人物/世界观),非结构化入参
output_schema=SCHEMA_CATALOG["outliner"],
reads=("projects", "foreshadow", "characters", "world_entities"),
writes=("outline",), # 声明式(经验收/T3.5 才真写库,不变量 #3
scope="builtin",
)
foreshadow_spec = AgentSpec(
name="foreshadow",
tier="analyst", # 不变量 #2只声明档位不写 model
system_prompt=load_prompt("foreshadow"),
input_schema=None, # 注入材料为序列化文本(已登记伏笔 + 草稿),非结构化入参
output_schema=SCHEMA_CATALOG["foreshadow"],
reads=("foreshadow",),
writes=(), # 只读(不变量 #3
scope="builtin",
)
pace_spec = AgentSpec(
name="pace",
tier="light", # 不变量 #2只声明档位不写 model节奏审用轻量档
system_prompt=load_prompt("pace"),
input_schema=None, # 注入材料为序列化文本(题材模板 + 草稿),非结构化入参
output_schema=SCHEMA_CATALOG["pace"],
reads=("rules",), # genre 模板 DSL 经 rulesgenre 级)注入,复用 review_context 规则合并
writes=(), # 只读(不变量 #3
scope="builtin",
)
# ---- 文风提取轨style-auditor「提取」轨analyst 档;独立生成,仿 outliner不进 review 图)----
style_extract_spec = AgentSpec(
name="style_extract",
tier="analyst", # 不变量 #2只声明档位不写 model
system_prompt=load_prompt("style_extract"),
input_schema=None, # 注入材料为序列化样本文本,非结构化入参
output_schema=SCHEMA_CATALOG["style_extract"],
reads=("style_fingerprint",),
writes=("style_fingerprint",), # 声明式(真写库经 T4.3 端点,不变量 #3
scope="builtin",
)
# ---- 文风漂移轨style-auditor「打分」轨 = 第四审light 档;并入 REVIEW_SPECS----
style_drift_spec = AgentSpec(
name="style", # 第四审 section/列名 = "style"
tier="light", # 不变量 #2只声明档位不写 model漂移打分用轻量档
system_prompt=load_prompt("style"),
input_schema=None, # 注入材料为序列化文本(指纹 + 草稿),非结构化入参
output_schema=SCHEMA_CATALOG["style"],
reads=("style_fingerprint",), # 指纹经 assemble 的 stable_core 注入 review_context
writes=(), # 只读(不变量 #3
scope="builtin",
)
# ---- 人物塑造续审灵感③analyst 档advisory 单维——仅建议、不阻断验收D2----
characterization_spec = AgentSpec(
name="characterization",
tier="analyst", # 不变量 #2只声明档位不写 model人物塑造诊断用分析档
system_prompt=load_prompt("characterization"),
input_schema=None, # 注入材料为序列化文本(人物卡 + 草稿,经 assemble非结构化入参
output_schema=SCHEMA_CATALOG["characterization"],
reads=(
"characters",
), # 以人物卡 motive/appearance 为客观锚点(经 assemble 的 stable_core 注入)
writes=(), # 只读(不变量 #3advisory 唯一落库=collect 留痕,不进 conflicts/验收 gate
scope="builtin",
)
# ---- 回炉refinerwriter 档;纯文本重写;非 Agent 流水线、非持久M4-e----
refiner_spec = AgentSpec(
name="refiner",
tier="writer", # 不变量 #2重写正文用 writer 档
system_prompt=load_prompt("refiner"),
input_schema=None, # 注入材料为序列化文本(选中段 + 指令 + 上下文)
output_schema=SCHEMA_CATALOG["refiner"], # 纯文本产出(重写段),无结构化 schema
reads=(),
writes=(), # 非持久(不变量 #3端点同步返回 {original, refined},不写库
scope="builtin",
)
# ---- worldbuilder世界观设计师写手档独立生成仿 outliner不进 review 图)----
worldbuilder_spec = AgentSpec(
name="worldbuilder",
tier="writer", # 不变量 #2只声明档位不写 model世界观创意用写手档
system_prompt=load_prompt("worldbuilder"),
input_schema=None, # 注入材料为序列化文本(设定 + 需求),非结构化入参
output_schema=SCHEMA_CATALOG["worldbuilder"],
reads=("projects",),
writes=("world_entities",), # 声明式(真写库经 T5.2 入库端点,不变量 #3
scope="builtin",
)
# ---- character-gen角色设计师写手档单/批量;群像防雷同;独立生成)----
character_gen_spec = AgentSpec(
name="character-gen",
tier="writer", # 不变量 #2只声明档位不写 model角色创意用写手档
system_prompt=load_prompt("character-gen"),
input_schema=None, # 注入材料为序列化文本(需求 + 约束 + 已有 + 已生成),非结构化入参
output_schema=SCHEMA_CATALOG["character-gen"],
reads=("world_entities", "characters"),
writes=("characters",), # 声明式(真写库经 T5.2 入库端点 + continuity 校验,不变量 #3
scope="builtin",
)
# ---- brainstorm脑洞生成器轻量档创作工具箱通用框架首个最简生成器T6----
brainstorm_spec = AgentSpec(
name="brainstorm",
tier="light", # 不变量 #2只声明档位不写 model脑洞发散用轻量档
system_prompt=load_prompt("brainstorm"),
input_schema=None, # 注入材料为序列化文本(作品设定 + 一句话需求),非结构化入参
output_schema=SCHEMA_CATALOG["brainstorm"],
reads=("projects",),
writes=(), # 纯预览,不写库(不变量 #3
scope="builtin",
)
# ---- book-title书名生成器轻量档纯预览T6 创作工具箱)----
book_title_spec = AgentSpec(
name="book-title",
tier="light", # 不变量 #2只声明档位不写 model书名发散用轻量档
system_prompt=load_prompt("book-title"),
input_schema=None, # 注入材料为序列化文本(作品设定 + 一句话需求),非结构化入参
output_schema=SCHEMA_CATALOG["book-title"],
reads=("projects",),
writes=(), # 纯预览,不写库(不变量 #3
scope="builtin",
)
# ---- blurb简介生成器分析档纯预览T6 创作工具箱)----
blurb_spec = AgentSpec(
name="blurb",
tier="analyst", # 不变量 #2只声明档位不写 model文案打磨用分析档
system_prompt=load_prompt("blurb"),
input_schema=None, # 注入材料为序列化文本(作品设定 + 一句话需求),非结构化入参
output_schema=SCHEMA_CATALOG["blurb"],
reads=("projects",),
writes=(), # 纯预览,不写库(不变量 #3
scope="builtin",
)
# ---- name取名生成器轻量档纯预览T6 创作工具箱)----
name_spec = AgentSpec(
name="name",
tier="light", # 不变量 #2只声明档位不写 model取名发散用轻量档
system_prompt=load_prompt("name"),
input_schema=None, # 注入材料为序列化文本(设定 + 世界观 + 已有角色 + 需求)
output_schema=SCHEMA_CATALOG["name"],
reads=("projects", "world_entities", "characters"),
writes=(), # 纯预览,不写库(不变量 #3
scope="builtin",
)
# ---- golden-finger金手指生成器写手档声明式 writes=world_entitiesT6 创作工具箱)----
golden_finger_spec = AgentSpec(
name="golden-finger",
tier="writer", # 不变量 #2只声明档位不写 model力量体系创意用写手档
system_prompt=load_prompt("golden-finger"),
input_schema=None, # 注入材料为序列化文本(设定 + 世界观 + 需求),非结构化入参
output_schema=SCHEMA_CATALOG["golden-finger"],
reads=("projects", "world_entities"),
writes=("world_entities",), # 声明式(真写库经入库端点,不变量 #3
scope="builtin",
)
# ---- glossary术语表生成器分析档声明式 writes=world_entitiesT6 创作工具箱)----
glossary_spec = AgentSpec(
name="glossary",
tier="analyst", # 不变量 #2只声明档位不写 model术语梳理用分析档
system_prompt=load_prompt("glossary"),
input_schema=None, # 注入材料为序列化文本(设定 + 世界观 + 需求),非结构化入参
output_schema=SCHEMA_CATALOG["glossary"],
reads=("projects", "world_entities"),
writes=("world_entities",), # 声明式(真写库经入库端点,不变量 #3
scope="builtin",
)
# ---- opening开篇生成器写手档纯预览T6 创作工具箱)----
opening_spec = AgentSpec(
name="opening",
tier="writer", # 不变量 #2只声明档位不写 model正文创作用写手档
system_prompt=load_prompt("opening"),
input_schema=None, # 注入材料为序列化文本(设定 + 大纲 + 需求),非结构化入参
output_schema=SCHEMA_CATALOG["opening"],
reads=("projects", "outline"),
writes=(), # 纯预览,不写库(不变量 #3
scope="builtin",
)
# ---- fine-outline细纲生成器分析档声明式 writes=outlineT6 创作工具箱)----
fine_outline_spec = AgentSpec(
name="fine-outline",
tier="analyst", # 不变量 #2只声明档位不写 model细纲拆解用分析档
system_prompt=load_prompt("fine-outline"),
input_schema=None, # 注入材料为序列化文本(设定 + 本章大纲 + 需求),非结构化入参
output_schema=SCHEMA_CATALOG["fine-outline"],
reads=("projects", "outline"),
writes=("outline",), # 声明式(真写库经入库端点,不变量 #3
scope="builtin",
)
# ---- continue续写生成器写手档纯预览Scope B 竞品快赢)----
continue_spec = AgentSpec(
name="continue",
tier="writer", # 不变量 #2只声明档位不写 model正文续写用写手档
system_prompt=load_prompt("continue"),
input_schema=None, # 注入材料为序列化文本(设定 + 前文 + 节拍 + 需求),非结构化入参
output_schema=SCHEMA_CATALOG["continue"],
reads=("projects", "outline", "chapters"),
writes=(), # 纯预览,不写库(不变量 #3
scope="builtin",
)
# ---- expand扩写生成器写手档纯预览Scope B 竞品快赢)----
expand_spec = AgentSpec(
name="expand",
tier="writer", # 不变量 #2只声明档位不写 model正文扩写用写手档
system_prompt=load_prompt("expand"),
input_schema=None, # 注入材料为序列化文本(设定 + 原文 + 需求),非结构化入参
output_schema=SCHEMA_CATALOG["expand"],
reads=("projects",),
writes=(), # 纯预览,不写库(不变量 #3
scope="builtin",
)
# ---- de-ai降 AI 率生成器分析档纯预览Scope B 竞品快赢)----
de_ai_spec = AgentSpec(
name="de-ai",
tier="analyst", # 不变量 #2只声明档位不写 model文风改写用分析档
system_prompt=load_prompt("de-ai"),
input_schema=None, # 注入材料为序列化文本(设定 + 原文 + 需求),非结构化入参
output_schema=SCHEMA_CATALOG["de-ai"],
reads=("projects",),
writes=(), # 纯预览,不写库(不变量 #3
scope="builtin",
)
# ---- teardown拆书生成器分析档纯预览Scope B 竞品快赢)----
teardown_spec = AgentSpec(
name="teardown",
tier="analyst", # 不变量 #2只声明档位不写 model拆解分析用分析档
system_prompt=load_prompt("teardown"),
input_schema=None, # 注入材料为序列化文本(设定 + 样本 + 需求),非结构化入参
output_schema=SCHEMA_CATALOG["teardown"],
reads=("projects",),
writes=("rules",), # F1拆书结论可落库为项目 rules入库仍经 ingest 白名单 gate#3
scope="builtin",
)
# ---- project-plan立项方案师分析档纯预览 writes=(),灵感⑤)----
project_plan_spec = AgentSpec(
name="project-plan",
tier="analyst", # 不变量 #2只声明档位不写 model立项方案构思用分析档
system_prompt=load_prompt("project-plan"),
input_schema=None, # 向导阶段 project 未建;材料为序列化的向导草稿(端点注入),非结构化入参
output_schema=SCHEMA_CATALOG["project-plan"],
reads=(), # 立项前 project 未建、无库可读;种子经端点序列化注入
writes=(), # 纯预览,不写库(不变量 #3
scope="builtin",
)
# 集中注册表name → spec同一实例兼容期 *_spec 与 SPECS[name] 为同对象,不变量)
# MappingProxyType只读视图运行时 `SPECS[x] = ...` / `del SPECS[x]` 抛 TypeError
# `Final` 仅静态检查,挡不住运行时变异;注册表是单一真相源,须运行时不可变)。
_SPECS_BY_NAME: dict[str, AgentSpec] = {
s.name: s
for s in (
continuity_spec,
outliner_spec,
foreshadow_spec,
pace_spec,
characterization_spec,
style_extract_spec,
style_drift_spec,
refiner_spec,
worldbuilder_spec,
character_gen_spec,
brainstorm_spec,
book_title_spec,
blurb_spec,
name_spec,
golden_finger_spec,
glossary_spec,
opening_spec,
fine_outline_spec,
continue_spec,
expand_spec,
de_ai_spec,
teardown_spec,
project_plan_spec,
)
}
assert len(_SPECS_BY_NAME) == 23, "SPECS name 冲突或缺失" # 唯一性 + 数量自检import 期)
SPECS: Final[Mapping[str, AgentSpec]] = MappingProxyType(_SPECS_BY_NAME)
# 四审受信保留名 —— 独立显式白名单(安全边界锚在此,不依附派生集合)
REVIEW_RESERVED_NAMES: Final[frozenset[str]] = frozenset(
{"continuity", "foreshadow", "style", "pace"}
)