把 21 个内置 agent 的 system_prompt 从 specs.py 的 Python 常量外置为 prompts/<spec.name>.md,import 期由 load_prompt 确定性加载;建立 SPECS 名册 + SCHEMA_CATALOG(Pydantic 类型留 Python)+ 统一只读解析入口 SpecResolver。 纯重构、零功能/schema 变更,缓存断点前块字节级不变(不变量 #9)。 @llm packages/agents(步骤1-3) - spec_model.py:抽出 AgentSpec(frozen,字段不变) - prompt_loader.py:load_prompt = utf-8-sig 去BOM → LF 归一 → NFC → rstrip尾LF, 内存缓存 + fail-fast(PromptNotFoundError),import 期确定性 - schema_catalog.py:SCHEMA_CATALOG[name]→output type 唯一真相源(refiner=None) - prompts/*.md ×21:取常量「运行时值」程序化外迁(反斜杠折行已塌缩, 物理换行≡运行时换行);文件名按 spec.name 连字符(style.md/character-gen.md 等) - specs.py:删 21 常量 + AgentSpec 类;system_prompt=load_prompt(name)、 output_schema=SCHEMA_CATALOG[name];建 SPECS + REVIEW_RESERVED_NAMES; *_spec 兼容期保留且 SPECS[name] is *_spec(同一实例)。804→337 行 - __init__.py:显式 __all__ 重导出(避 F401) @backend packages/skills(步骤4-5) - SpecResolver:内置 SPECS(纯内存、零 DB)+ 用户 SkillRegistry 统一 get; 内置 name 永不触发 DB;output_schema_for 精确匹配 - skill_registry:保留命名空间守卫前移至入库校验,拒同名内置 → VALIDATION - toolbox_registry:GeneratorTool.spec 改走 SPECS[...],删 12 个 *_spec 直接 import @devops repo-root - .gitattributes:prompts/*.md text eol=lf(修正:须用完整嵌套路径才匹配) - packages/agents/pyproject:hatchling artifacts 纳入 prompts/*.md 随 wheel/sdist 分发 - ci.yml:新增 build wheel → 裸装 → import ww_agents.SPECS 冒烟 TDD 全程 mock 网关;门禁绿:ruff/format clean · mypy 209 files · pytest 744 passed (含金标准 sha256 回归 / md↔spec↔catalog 一一对应 / fail-fast / BOM+NFC / 内置守卫 / 同一实例 / 编排器无回归 / apps/api import-smoke / 打包冒烟)
338 lines
14 KiB
Python
338 lines
14 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",
|
||
"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",
|
||
"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",
|
||
)
|
||
|
||
# ---- 回炉(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",
|
||
)
|
||
|
||
|
||
# 集中注册表: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,
|
||
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,
|
||
)
|
||
}
|
||
assert len(_SPECS_BY_NAME) == 21, "SPECS name 冲突或缺失" # 唯一性 + 数量自检(import 期)
|
||
SPECS: Final[Mapping[str, AgentSpec]] = MappingProxyType(_SPECS_BY_NAME)
|
||
|
||
# 四审受信保留名 —— 独立显式白名单(安全边界锚在此,不依附派生集合)
|
||
REVIEW_RESERVED_NAMES: Final[frozenset[str]] = frozenset(
|
||
{"continuity", "foreshadow", "style", "pace"}
|
||
)
|