M4(文风): style-auditor 双轨(提取指纹/漂移第四审)+ jobs 长任务框架(zombie reaper) + 回炉 refine + GET /style read-back。 M5(生成+扩展): worldbuilder/character-gen(入库 continuity 409 gate + partition_writes 白名单 + schema→JSONB 形变); 网关多 provider 回退链/熔断/能力降级(Anthropic/Gemini 适配器);Skill registry + 表权限沙箱 + 规则; 前端 角色生成器/世界观/Codex/规则页/技能库/⌘K 命令面板。 K1(Kimi Code 订阅接入): OAuth device-flow(kimi-code)+ 静态 Console key(kimi-code-key)两路径; coding 端点 KimiCLI 伪造头(实测 UA allow-list 门禁,缺则 403)+ JSON 模式结构化(thinking ⊥ tool_choice)。 本地联调修复: CORS 中间件;assemble 注入 premise+「写第N章」指令(修空 prompt 400); GET /outline·/draft read-back + 大纲/工作台/审稿页重载;写页 client/server 常量边界 + notFound 健壮化; 字数 toLocaleString locale 水合;审稿页终稿从已存草稿 seed(修 accept 422)。 门禁: backend ruff/mypy(157)/alembic 无漂移/pytest 451 · frontend lint/tsc/vitest/build。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
164 lines
6.2 KiB
Python
164 lines
6.2 KiB
Python
"""生成/入库端点的请求/响应 schema(C3 扩 / ARCH §6.5 / §7.2)。
|
||
|
||
snake_case;前端经 OpenAPI 生成 TS 类型消费。改字段 → 前端必须 `pnpm gen:api`。
|
||
|
||
生成走**即时返回**(预览 → 作者确认 → 入库),非 jobs 长任务(M5-d):
|
||
- `POST /world/generate` / `POST /characters/generate`:返回**预览**(不持久化)。
|
||
- `POST /characters`(入库):作者确认的角色卡 → 过 continuity 预检 gate + 权限白名单 → 写库。
|
||
|
||
视图字段贴 `ww_agents.WorldEntityCard`/`CharacterCard`(保持生成产物形:traits/speech_tics
|
||
是 list、arc 是 str;入库时由写侧 repo 转 DB JSONB 形,见 character_repo)。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from pydantic import BaseModel, Field
|
||
|
||
from ww_api.schemas.rules import RuleView
|
||
|
||
# 批量生成数量上限(防一次性巨量调用;原型保守值)。
|
||
MAX_GENERATE_COUNT = 12
|
||
|
||
|
||
# ---- 世界观生成(预览)----
|
||
|
||
|
||
class WorldGenerateRequest(BaseModel):
|
||
"""POST /projects/:id/world/generate:据需求生成世界观实体(预览)。"""
|
||
|
||
brief: str = Field(min_length=1, description="世界观生成需求(题材/设定方向/约束)")
|
||
|
||
|
||
class WorldEntityCardView(BaseModel):
|
||
"""单个世界观实体卡(贴 ww_agents.WorldEntityCard;预览 + 入库共用形)。"""
|
||
|
||
type: str = Field(description="实体类型(势力 / 地理 / 力量体系 / 物品 / 概念 等)")
|
||
name: str = Field(description="实体名")
|
||
rules: list[str] = Field(default_factory=list, description="该实体的硬规则清单")
|
||
|
||
|
||
class WorldGenPreviewResponse(BaseModel):
|
||
"""世界观生成预览(不持久化;作者确认后另走入库,本期入库端点为角色)。"""
|
||
|
||
entities: list[WorldEntityCardView] = Field(default_factory=list)
|
||
|
||
|
||
# ---- 角色生成(预览)----
|
||
|
||
|
||
class CharacterGenerateRequest(BaseModel):
|
||
"""POST /projects/:id/characters/generate:据需求生成角色卡(预览,群像防雷同)。"""
|
||
|
||
brief: str = Field(min_length=1, description="角色生成需求")
|
||
count: int = Field(default=1, ge=1, le=MAX_GENERATE_COUNT, description="生成数量")
|
||
role: str | None = Field(default=None, description="角色定位(主角/CP/对手/导师/工具人 等)")
|
||
|
||
|
||
class CharacterRelationView(BaseModel):
|
||
"""单条人物关系(贴 ww_agents.CharacterRelation)。"""
|
||
|
||
name: str
|
||
kind: str
|
||
note: str | None = None
|
||
|
||
|
||
class CharacterCardView(BaseModel):
|
||
"""单张角色卡(贴 ww_agents.CharacterCard;预览 + 入库请求共用形)。"""
|
||
|
||
name: str = Field(description="角色名")
|
||
role: str = Field(description="角色定位")
|
||
traits: list[str] = Field(default_factory=list, description="性格特质清单")
|
||
backstory: str = Field(description="背景故事")
|
||
arc: str = Field(description="人物弧光(一句话)")
|
||
speech_tics: list[str] = Field(default_factory=list, description="口癖/语言风格")
|
||
tags: list[str] = Field(default_factory=list, description="人设标签/萌点")
|
||
relations: list[CharacterRelationView] = Field(default_factory=list, description="关系网")
|
||
|
||
|
||
class CharacterGenPreviewResponse(BaseModel):
|
||
"""角色生成预览(不持久化;作者确认后经 POST /characters 入库)。"""
|
||
|
||
cards: list[CharacterCardView] = Field(default_factory=list)
|
||
|
||
|
||
# ---- 角色入库(作者确认后;过 continuity gate + 权限白名单)----
|
||
|
||
|
||
class CharacterIngestRequest(BaseModel):
|
||
"""POST /projects/:id/characters:把作者确认的角色卡入库。
|
||
|
||
`acknowledge_conflicts`:作者已查看并接受预检冲突时置 `true`,越过 continuity gate
|
||
(仿 accept 的冲突裁决:不静默入库,须作者确认;默认 false → 有冲突即 409,见端点)。
|
||
"""
|
||
|
||
cards: list[CharacterCardView] = Field(min_length=1, description="待入库的角色卡(至少 1 张)")
|
||
acknowledge_conflicts: bool = Field(
|
||
default=False, description="作者已知悉并接受 continuity 冲突 → 放行入库"
|
||
)
|
||
|
||
|
||
class IngestConflictView(BaseModel):
|
||
"""入库预检检出的单条 continuity 冲突(贴 ww_agents.Conflict 五类)。"""
|
||
|
||
type: str
|
||
where: str
|
||
refs: list[str] = Field(default_factory=list)
|
||
suggestion: str
|
||
|
||
|
||
class CharacterIngestResponse(BaseModel):
|
||
"""角色入库结果(201):写入的角色 + 被白名单丢弃的越权表(审计)。"""
|
||
|
||
created: list[str] = Field(default_factory=list, description="写入的角色名(按入参顺序)")
|
||
rejected_tables: list[str] = Field(
|
||
default_factory=list, description="被权限白名单丢弃的越权写表名(审计;正常为空)"
|
||
)
|
||
|
||
|
||
# ---- 读端点:设定库 Codex(角色 / 世界观全量列表)----
|
||
|
||
|
||
class CharacterListResponse(BaseModel):
|
||
"""GET /projects/:id/characters:已入库角色全量列表(设定库 Codex 真源)。
|
||
|
||
DB JSONB dict 列 → API list/str 反向解包(入库形变的逆向,见 character_repo):
|
||
`{"items":[...]}`→list、`{"text":...}`→str;tags/relations 直落 list。
|
||
"""
|
||
|
||
characters: list[CharacterCardView] = Field(default_factory=list)
|
||
|
||
|
||
class WorldEntityListResponse(BaseModel):
|
||
"""GET /projects/:id/world_entities:已入库世界观实体全量列表(设定库 Codex 真源)。
|
||
|
||
DB JSONB dict 列 `{"rules":[...]}`→裸 list(worldbuilder 形变的逆向)。
|
||
"""
|
||
|
||
world_entities: list[WorldEntityCardView] = Field(default_factory=list)
|
||
|
||
|
||
# ---- 读端点:规则列表 + 技能库(T5.6 前端)----
|
||
|
||
|
||
class RuleListResponse(BaseModel):
|
||
"""GET /projects/:id/rules:规则列表(规则页)。"""
|
||
|
||
rules: list[RuleView] = Field(default_factory=list)
|
||
|
||
|
||
class SkillView(BaseModel):
|
||
"""单个 skill 的声明式视图(技能库 UI)。"""
|
||
|
||
name: str
|
||
scope: str = Field(description="builtin / custom / community")
|
||
tier: str = Field(description="能力档位(writer / analyst / light)")
|
||
reads: list[str] = Field(default_factory=list)
|
||
writes: list[str] = Field(default_factory=list)
|
||
genre: str | None = None
|
||
|
||
|
||
class SkillListResponse(BaseModel):
|
||
"""GET /skills:技能库列表(按 name 升序)。"""
|
||
|
||
skills: list[SkillView] = Field(default_factory=list)
|