通用执行路径驱动全部生成器("加生成器=加一份声明"):
- @llm: ww_agents +7 输出 schema + 7 spec(book-title/blurb/name/golden-finger/
glossary/opening/fine-outline,只声明 tier)+ build_outline_chapter_context
- @backend: ww_skills GeneratorTool 描述符 + TOOLBOX(11) + get_tool;3 通用端点
GET /skills/toolbox · POST .../skills/{tool_key}/generate(预览不写库,仅记账) ·
POST .../ingest(复用 continuity 409 + partition_writes 白名单);纯 context 派发
- @frontend: 工具箱落地页 RSC + 声明驱动 GeneratorRunner + lib/toolbox 纯函数
+ LeftNav「工具箱」+ ⌘K nav-toolbox/action-gen-*;legacy 3 跳现页
- @qa: tests/test_t6_toolbox_e2e.py 5 用例真 pg + mock 网关零 token,无端点 bug
- P2 收尾: 限流→decisions.md 记延后(单用户原型);noopener/Committable 早已修
守不变量 #2(只声明 tier)/#3(预览不写库,入库经验收 gate)/#9(缓存前缀)。无 DB 迁移。
门禁绿: 后端 ruff/format/mypy 195/alembic 无漂移/pytest 583;前端 lint/tsc/vitest 279/build。
spec 回写 PRODUCT_SPEC §7 + ARCHITECTURE §7.2 端点表。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
142 lines
5.7 KiB
Python
142 lines
5.7 KiB
Python
"""创作工具箱通用端点的请求/响应 schema(C3 扩 T6 / 通用生成器框架)。
|
||
|
||
snake_case;前端经 OpenAPI 生成 TS 类型消费。改字段 → 前端必须 `pnpm gen:api`。
|
||
|
||
- `GET /skills/toolbox` → `ToolboxListResponse`(声明式描述符视图,含 legacy_route 供前端路由)。
|
||
- `POST .../skills/{tool_key}/generate` → `ToolGeneratePreviewResponse`(结构化预览,不入库)。
|
||
- `POST .../skills/{tool_key}/ingest` → `ToolIngestResponse`(过 continuity gate + 白名单后入库)。
|
||
|
||
预览/入库共用既有 schema:world_entities 入库走 `WorldEntityCardView`(贴 worldbuilder 形),
|
||
outline 入库走 `OutlineSceneIngestView`(贴 fine-outline 场景行)。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from typing import Any
|
||
|
||
from pydantic import BaseModel, Field
|
||
|
||
from ww_api.schemas.generation import IngestConflictView, WorldEntityCardView
|
||
|
||
# 一次性生成数量上限沿用 generation 的保守值(避免巨量调用);章号下限。
|
||
MIN_CHAPTER_NO = 1
|
||
|
||
|
||
# ---- GET /skills/toolbox(描述符列表)----
|
||
|
||
|
||
class ToolInputFieldView(BaseModel):
|
||
"""单个声明式表单字段视图(贴 ww_skills.InputField)。"""
|
||
|
||
name: str
|
||
label: str
|
||
type: str = Field(description="控件类型:text / textarea / number / select 等")
|
||
required: bool = True
|
||
default: str | None = None
|
||
help: str | None = None
|
||
|
||
|
||
class ToolDescriptorView(BaseModel):
|
||
"""单个生成器卡片描述符视图(前端据此渲染卡片栅格 + 输入表单)。"""
|
||
|
||
key: str = Field(description="路由用稳定标识(brainstorm / book-title ...)")
|
||
title: str
|
||
subtitle: str
|
||
genre: str | None = None
|
||
is_legacy: bool = Field(description="legacy 工具(spec=None)→ 前端走 legacy_route 跳现有页面")
|
||
ingestable: bool = Field(description="是否可入库(声明了 ingest 目标)")
|
||
input_fields: list[ToolInputFieldView] = Field(default_factory=list)
|
||
legacy_route: str | None = Field(
|
||
default=None, description="legacy 工具指向的现有页面路径(含 {id} 占位)"
|
||
)
|
||
|
||
|
||
class ToolboxListResponse(BaseModel):
|
||
"""GET /skills/toolbox:创作工具箱全量描述符(legacy + 新工具)。"""
|
||
|
||
tools: list[ToolDescriptorView] = Field(default_factory=list)
|
||
|
||
|
||
# ---- POST .../skills/{tool_key}/generate(结构化预览)----
|
||
|
||
|
||
class ToolGenerateRequest(BaseModel):
|
||
"""通用生成请求(覆盖全部工具的可选入参;按工具 input_fields 取用,snake_case)。"""
|
||
|
||
brief: str = Field(default="", description="一句话需求/方向(可空,空则按题材发散)")
|
||
chapter_no: int | None = Field(
|
||
default=None, ge=MIN_CHAPTER_NO, description="按章展开类工具的章号(开篇/细纲)"
|
||
)
|
||
count: int | None = Field(default=None, ge=1, le=12, description="生成数量(部分工具可用)")
|
||
kind: str | None = Field(default=None, description="命名对象类别等(部分工具可用)")
|
||
|
||
|
||
class ToolGeneratePreviewResponse(BaseModel):
|
||
"""通用生成预览:结构化产物(不持久化;仅记 ledger)。
|
||
|
||
`output_kind` = 该工具产物的 schema 名(如 IdeaListResult),供前端按类型选预览渲染器;
|
||
`preview` = 解析后的结构化产物(按各工具 output_schema 的形,键名见 ww_agents.schemas)。
|
||
"""
|
||
|
||
tool_key: str
|
||
output_kind: str = Field(description="产物 schema 名(前端按此选预览渲染器)")
|
||
preview: dict[str, Any] = Field(
|
||
default_factory=dict,
|
||
description="结构化产物(各工具 output_schema 的 model_dump;不入库)",
|
||
)
|
||
|
||
|
||
# ---- POST .../skills/{tool_key}/ingest(入库;过 continuity gate + 白名单)----
|
||
|
||
|
||
class OutlineSceneIngestView(BaseModel):
|
||
"""细纲入库的单章场景行(贴 ww_agents.Scene;落 outline 表)。"""
|
||
|
||
idx: int
|
||
beat: str
|
||
purpose: str = ""
|
||
conflict: str = ""
|
||
hook: str = ""
|
||
|
||
|
||
class ToolIngestRequest(BaseModel):
|
||
"""通用入库请求:待持久化的产物 + 冲突确认。
|
||
|
||
仅声明了 `ingest` 的工具可用。按入库表取用对应字段:
|
||
- world_entities(金手指/词条)→ `world_entities`(贴 WorldEntityCardView:type/name/rules);
|
||
- outline(细纲)→ `chapter_no` + `scenes`(贴 OutlineSceneIngestView)。
|
||
`acknowledge_conflicts`:作者已查看并接受 continuity 预检冲突时置 true 放行(仿 accept gate)。
|
||
"""
|
||
|
||
world_entities: list[WorldEntityCardView] = Field(
|
||
default_factory=list, description="world_entities 入库项(金手指/词条)"
|
||
)
|
||
chapter_no: int | None = Field(
|
||
default=None, ge=MIN_CHAPTER_NO, description="outline 入库的目标章号(细纲)"
|
||
)
|
||
scenes: list[OutlineSceneIngestView] = Field(
|
||
default_factory=list, description="outline 入库的场景行(细纲)"
|
||
)
|
||
acknowledge_conflicts: bool = Field(
|
||
default=False, description="作者已知悉并接受 continuity 冲突 → 放行入库"
|
||
)
|
||
|
||
|
||
class ToolIngestResponse(BaseModel):
|
||
"""通用入库结果(201):写入项标识 + 被白名单丢弃的越权表(审计)。"""
|
||
|
||
table: str = Field(description="入库目标表")
|
||
created: list[str] = Field(
|
||
default_factory=list, description="写入项的标识(实体名 / 场景索引),按入参顺序"
|
||
)
|
||
rejected_tables: list[str] = Field(
|
||
default_factory=list, description="被权限白名单丢弃的越权写表名(审计;正常为空)"
|
||
)
|
||
|
||
|
||
class ToolIngestConflictDetails(BaseModel):
|
||
"""409 冲突详情(贴 generation 的 IngestConflictView)。"""
|
||
|
||
conflicts: list[IngestConflictView] = Field(default_factory=list)
|
||
conflict_count: int = 0
|