Files
writer-work-flow/packages/agents/ww_agents/specs.py
Yaojia Wang 1652ad9d20 feat(backend): AI 反问澄清预检端点——refine 侧结构化 clarify(WFW-9 M1,路线A 两阶段)
润色「再沟通」意见含糊时先反问给选项(路线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 无漂移。
2026-07-08 08:47:13 +02:00

382 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",
"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 经 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",
)
# ---- 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"}
)