Files
writer-work-flow/apps/api/ww_api/schemas/toolbox.py
Yaojia Wang a5351320a2 feat(skills): Scope B A2 — 4 竞品生成器注册表+上下文分派+端点接线
注册表 TOOLBOX +4 entry(continue/expand/de-ai/teardown,全 preview-only writes=[]);
ContextStrategy +with_prior_chapter/text_input;ToolGenerateRequest +text 字段。
toolbox_context 分派新策略(续写→build_continuation_context、原文→build_text_input_context);
路由续写经 chapter_repo 读最新 accepted/draft 正文注入 builder(core 不 import apps/api,仿 accept_op)。
预览仅 commit ledger 不写业务表(#3);只声明 tier(#2);system_prompt 进缓存前缀(#9)。
单测:4 生成器 generate 预览 + 续写读前文/草稿路由 + 扩写/降AI text 路由 + 拆书结构化;
更新 registry/strategy 计数测试(15 工具 / 6 策略)。门禁绿:ruff/format/mypy 196 files/pytest 168(api)+32(skills)。
2026-06-23 19:10:35 +02:00

148 lines
5.9 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.

"""创作工具箱通用端点的请求/响应 schemaC3 扩 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 + 白名单后入库)。
预览/入库共用既有 schemaworld_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="一句话需求/方向(可空,空则按题材发散)")
text: str | None = Field(
default=None,
description="原文输入(扩写/降AI 的原文、拆书的样本text_input 策略工具用)",
)
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`(贴 WorldEntityCardViewtype/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