润色「再沟通」意见含糊时先反问给选项(路线A:问题走独立非流式 JSON 预检端点,正文仍走
既有 refine 一字不改)。新增:
- ClarifyDecision/ClarifyQuestion/ClarifyOption 结构化 schema(既有 output-schema 处,供
producer 与端点共用);clarify_refine.md 教条(含糊→need_clarification+≤1问+2–4锚定选项+
自由输入;明确→verification 放行;防循环);注册 clarify_refine_spec(analyst 档,#24)+
SCHEMA_CATALOG + 重生成金标准。
- clarify_node:build_clarify_request 纯函数(缓存前缀不含易变) + run_clarify(gateway.run
结构化,判别/校验失败确定性回退 need_clarification=false,只读不写库)。
- POST /projects/{id}/chapters/{no}/refine/clarify → ClarifyDecision(analyst 网关,404/503,
末尾 commit 记账)。**既有 refine 端点/RefineRequest/Response 完全未改**。
门禁绿:ruff/mypy 227/pytest 900(+test_clarify_spec/_node/style clarify)/alembic 无漂移。
382 lines
16 KiB
Python
382 lines
16 KiB
Python
"""Agent 声明式抽象(ARCH §5.1)+ 续审 Agent 实例(Prompt 外置方案A · 步3/5)。
|
||
|
||
`AgentSpec` 是内置 Agent 与用户 Skill 的同构声明:一份只读声明,由编排器加载、
|
||
经网关按 `tier` 执行。不变量 #2:agent 只声明 `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",
|
||
"clarify_refine_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 经 rules(genre 级)注入,复用 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=(), # 只读(不变量 #3);advisory 唯一落库=collect 留痕,不进 conflicts/验收 gate
|
||
scope="builtin",
|
||
)
|
||
|
||
# ---- 回炉(refiner;writer 档;纯文本重写;非 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_entities,T6 创作工具箱)----
|
||
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_entities,T6 创作工具箱)----
|
||
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=outline,T6 创作工具箱)----
|
||
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",
|
||
)
|
||
|
||
# ---- clarify_refine(润色预检澄清;分析档;纯只读 writes=(),WFW-9 M1 路线A 两阶段)----
|
||
clarify_refine_spec = AgentSpec(
|
||
name="clarify_refine",
|
||
tier="analyst", # 不变量 #2:只声明档位,不写 model(含糊判别 + 反问构造用分析档)
|
||
system_prompt=load_prompt("clarify_refine"),
|
||
input_schema=None, # 注入材料为序列化文本(选段 + 再沟通指令),非结构化入参
|
||
output_schema=SCHEMA_CATALOG["clarify_refine"],
|
||
reads=(), # 只判本段是否含糊;材料经端点序列化注入,不直读库
|
||
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,
|
||
clarify_refine_spec,
|
||
)
|
||
}
|
||
assert len(_SPECS_BY_NAME) == 24, "SPECS name 冲突或缺失" # 唯一性 + 数量自检(import 期)
|
||
SPECS: Final[Mapping[str, AgentSpec]] = MappingProxyType(_SPECS_BY_NAME)
|
||
|
||
# 四审受信保留名 —— 独立显式白名单(安全边界锚在此,不依附派生集合)
|
||
REVIEW_RESERVED_NAMES: Final[frozenset[str]] = frozenset(
|
||
{"continuity", "foreshadow", "style", "pace"}
|
||
)
|