Files
writer-work-flow/ARCHITECTURE.md
Yaojia Wang f43ccd293f feat(toolbox): T6 创作工具箱通用生成器框架 — 8 新生成器 + 声明驱动落地页 + P2 收尾
通用执行路径驱动全部生成器("加生成器=加一份声明"):
- @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>
2026-06-22 20:37:55 +02:00

965 lines
59 KiB
Markdown
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.

# 网文创作工作流 · 工程架构文档Architecture
> 把 [PRODUCT_SPEC.md](./PRODUCT_SPEC.md)(产品规格)下沉到**可实现的技术架构**:组件划分、接口契约、完整 DDL、LLM 网关内部设计、编排引擎、API 清单、横切关注点、部署与测试。
> UX/UI 见 [UX_SPEC.md](./UX_SPEC.md)。本文按 PRODUCT_SPEC 章节顺序逐节展开,每节末标注回溯锚点 `← PRODUCT_SPEC §x`。
---
## 0. 文档说明
| 项 | 内容 |
|---|---|
| 文档类型 | 软件架构文档SAD面向工程实现 |
| 目标读者 | 后端/前端工程师、技术负责人、SRE |
| 上游 | `PRODUCT_SPEC.md`(产品规格)、`UX_SPEC.md`(界面规格) |
| 范围 | 技术架构与设计;**不含**业务代码与脚手架(属 M1 实现阶段) |
| 状态 | 设计定稿待评审;评审通过后据此搭 M1 骨架 |
| 关键约束 | 提供商中立(不绑定单一 LLM 厂商记忆即真相源AI 不黑箱(产出经验收 gate |
**与上游的关系**PRODUCT_SPEC 回答"做什么/为什么",本文回答"怎么实现"。本文不重复产品论证,只在每节顶部用一句话点出对应的产品意图,随后给技术方案。如展开中发现规格需调整,集中列于 [§12 后 · 待回写规格的发现](#待回写规格的发现),不擅自改上游。
---
## 1. 架构总览 ← PRODUCT_SPEC §1 / §1.5 / §2
### 1.1 质量属性NFR与架构驱动
产品的四大痛点 + 工程哲学,转译为可度量的架构驱动力:
| 质量属性 | 目标 | 架构手段 |
|---|---|---|
| **一致性**(首要) | 几十万字不崩设定/不忘伏笔 | 记忆即真相源 + 每章事实摘要 + 写前检索 + 四审 gate |
| **可扩展(提供商)** | 任意接入/切换 LLM 厂商 | LLM 网关 + 适配器 + 能力档位抽象 |
| **可扩展(能力)** | 新增 Agent/题材模板零侵入 | 声明式 Skill + registry + 表权限契约 |
| **成本可控** | 长记忆注入不烧钱 | 前缀缓存抽象 + **确定性按需注入**(显式+近况,无向量) + 档位分流 |
| **可用性/韧性** | 单厂商故障不致瘫 | 故障回退链 + 重试/熔断 + 能力降级 |
| **安全/隔离** | 密钥与作品数据隔离 | 密钥托管 + 用户 Skill 沙箱(**原型单用户**;多租户行级隔离为后续,见 §9.1 |
| **可掌控(产品约束)** | AI 改动经作者裁决 | 四审只读 + 验收事务写回 |
| **可观测** | 每次 LLM 调用可追踪可计费 | 结构化日志 + LLM trace + 按 provider+model 记账 |
### 1.2 架构风格与关键决策ADR 摘要)
| # | 决策 | 选择 | 理由 / 取舍 |
|---|---|---|---|
| ADR-1 | 总体形态 | 前后端分离:**后端为模块化单体**Modular Monolith起步 | 前端 Next、后端 FastAPI 两个可部署单元;后端内部编排器/网关/记忆为清晰模块、预留拆服务接缝,避免过早微服务化。 |
| ADR-2 | 真相源 | **数据库为唯一真相源**Agent 经库交换 | 对齐 PRODUCT_SPEC §5.4Agent 间零直连,降耦合、可独立替换。 |
| ADR-3 | LLM 接入 | **自建网关 + 适配器**,直连各家 API | 对齐 §3.4;提供商中立、确定性编排;不用厂商 CLI/托管运行时(锁定单厂商)。 |
| ADR-4 | Agent 形态 | **声明式 Skill = 单一职责 Agent**;用 **LangGraph** 做确定性编排 | 对齐 §5非自治 agent loop编排为固定图节点/并行/验收 HITL 中断/checkpoint可测、可回放。 |
| ADR-5 | 写章模型 | **章 = 纯函数 f(大纲, 选择的状态, 文风)**,状态变更集中在验收 | 可缓存、可重放;副作用隔离到验收事务。 |
| ADR-6 | 检索 | **Postgres 单库 + 确定性记忆选择**(无向量) | 原型设定集不大,"本章有谁"大纲已点名,确定性选择(显式+主角+近况)足够且可调试;**向量检索降为 P2**,规模化再在同一接口后接入。 |
| ADR-7 | 结构化输出 | **网关统一封装**原生优先、JSON+校验兜底 | 各家能力参差§3.3 能力协商),上层无感。 |
| ADR-8 | 前后端 | **前端 Next.jsTS+ 后端 FastAPIPython分离** | 前端 SSR/流式友好;后端 Python 是 LLM 编排/工具的强生态;契约经 OpenAPI→TS 代码生成。 |
| ADR-9 | 长任务 | **FastAPI BackgroundTasks + `jobs` 表**(原型不上专门队列) | 学文风/全书扫描走后台任务 + 状态表;写章/审稿走 SSE 即时反馈。规模化再上 arq/Celery。 |
| ADR-10 | 编排实现 | **LangGraphPython** | 写章→四审→验收天然是跨请求的暂停-恢复HITLLangGraph 的并行/中断/checkpoint 正合,且 Python 版成熟。 |
### 1.3 系统上下文图C4 L1
```
┌───────────────┐
│ 网文作者 │ 浏览器
└──────┬────────┘
│ HTTPS
┌────────▼─────────────────────────────┐
│ 网文创作工作流(本系统) │
│ 立项·设定·大纲·写章·四审·验收·伏笔 │
└───┬──────────────────────────────────┘
│ 各家 API(经网关)
┌────────▼────────┐
│ LLM 提供商 │
│ Claude/DeepSeek │
│ Kimi/GPT/GLM/… │
└─────────────────┘
```
外部依赖①LLM 提供商(多家,经网关);②对象存储(可选,存文风样本原文/导出稿)。
> 注:原型**不依赖 Embedding 服务**砍向量检索改确定性记忆选择§3.4);向量检索为 P2届时再引入嵌入服务。
### 1.4 容器图C4 L2
```
┌──── 前端 Next.js(TS) ────┐ ┌──────────── 后端 FastAPI(Python) ────────────┐
│ 作品库 立项 工作台 审稿 │ fetch │ │
│ 设定库 伏笔看板 设置页 │ ─/SSE─▶ │ API 层 ─▶ 编排器(LangGraph) ─▶ LLM 网关 │
└──────────────────────────┘ │ │ │ │ ┌────────┐│
OpenAPI→TS 客户端类型 │ │ ▼ ├─▶│OpenAI兼容││ DeepSeek…
│ │ 记忆服务(选择/注入/写回) ├─▶│Anthropic ││
│ ▼ │ └─▶│Gemini ││
│ BackgroundTasks 数据访问层(Repo) 密钥管理 │
└────┬───────────────┬───────────────┬─────────┘
│(jobs) │ │
┌──────▼──────┐ ┌──────▼──────┐ ┌──────▼──────┐
│ jobs 表(同库)│ │ Postgres │ │ 密钥存储 │
│ │ │ (真相源,无向量)│ │ (KMS/加密列) │
└─────────────┘ └─────────────┘ └─────────────┘
```
**容器职责**
- **前端Next.js/TS**UX_SPEC 的页面与交互;流式渲染、冲突标注、乐观更新;经 OpenAPI 生成的 TS 客户端调后端。
- **后端FastAPI/Python**API 层入参校验、SSE、编排器LangGraph、网关、记忆服务同进程。
- **编排器LangGraph**:确定性图串联 Agent写章→四审→验收 HITL并行四审、验收中断/恢复。
- **记忆服务**:写前**确定性选择**(显式+主角+近况无向量、prompt 组装(缓存断点)、验收写回(事务)。
- **LLM 网关**:档位路由、适配器、能力协商/降级、回退、缓存、记账§4 详)。图中 **OpenAI 兼容适配器一套覆盖 DeepSeek/Kimi/Qwen/GLM/OpenAI**Anthropic/Gemini 各一适配器§4.2)。
- **数据访问层**Repository 模式封装所有表读写(原型单用户,多租户行级隔离为后续)。
- **BackgroundTasks + `jobs` 表**:学文风、全书扫描等长任务(原型不上专门队列)。
- **密钥管理**:各提供商 API Key 加密托管。
---
## 2. 技术栈与工程结构 ← PRODUCT_SPEC §3.1
### 2.1 选型与理由
| 层 | 选型 | 理由 |
|---|---|---|
| 前端语言/框架 | **TypeScript + Next.js App Router + React** | SSR/流式/编辑器友好;纯 UI调后端 API |
| 后端语言/框架 | **Python + FastAPI**async | LLM 编排/工具的强生态async + SSE + Pydantic 校验天然契合 |
| 样式 | Tailwind + CSS 变量(设计 token | 纸感主题 token 化§8.4);快速一致 |
| 编排 | **LangGraph**Python | 写章→四审→验收的并行 + HITL 中断 + checkpointADR-10 |
| 数据库 | **PostgreSQL**(无向量) | 关系一库;事务保证验收写回原子性;向量检索为 P2 再引入 |
| ORM/迁移 | **SQLAlchemy 2.0async+ Alembic** | 类型化 + 迁移管理;贴近 SQL/索引控制 |
| 长任务 | **FastAPI BackgroundTasks + `jobs` 表** | 原型免专门队列;规模化再上 arq/Celery |
| LLM SDK | `anthropic` + `openai`(baseURL 覆盖 DeepSeek/Kimi/Qwen/GLM) + `google-genai` | 网关内适配器按需选用 |
| 校验/结构化输出 | **Pydantic + `instructor`** | 入参 + 结构化输出 schema 单一来源 |
| 前后端契约 | **FastAPI OpenAPI → 生成 TS 客户端**openapi-typescript/orval | 找回"共享类型",跨语言不漂移 |
| 鉴权 | **原型单用户 stub**(多租户 + Auth 为后续) | 先做原型§9.1 |
| 观测 | OpenTelemetry + 结构化日志structlog | LLM 调用 trace、成本指标 |
> 说明:本架构约束**模块边界与契约**;上表为原型锁定栈,向量检索/专门队列/多租户均为后续可选(标注于各节)。
### 2.2 代码组织monorepo 模块边界)
```
writer-work-flow/
├── apps/
│ ├── web/ # 前端 Next.js(TS):纯 UI调后端 API
│ │ ├── app/ # App Router 页面(对齐 UX_SPEC §6)
│ │ │ ├── (dashboard)/ # 作品库
│ │ │ ├── projects/[id]/ # 工作台: write/outline/codex/foreshadow/review/style/rules
│ │ │ └── settings/ # 模型与提供商设置(UX §6.10)
│ │ ├── components/ # UI 组件(对齐 UX §7)
│ │ └── lib/api/ # OpenAPI 生成的 TS 客户端
│ └── api/ # 后端 FastAPI(Python)
│ ├── routers/ # 端点(对齐 §7)
│ └── main.py # 应用入口 + OpenAPI
├── packages/ # Python 包(后端共享)
│ ├── core/ # 领域核心(无 IO)
│ │ ├── domain/ # 实体/状态机(伏笔状态机/一致性规则)
│ │ ├── orchestrator/ # 编排器(LangGraph 图)
│ │ └── memory/ # 确定性选择/注入/prompt 组装
│ ├── llm_gateway/ # LLM 网关(适配器/路由/降级/回退/缓存/记账)
│ │ └── adapters/ # openai_compatible / anthropic / gemini
│ ├── agents/ # 8 个内置 Agent 的声明(prompt+schema+reads/writes)
│ ├── skills/ # Skill registry + loader + 权限沙箱
│ ├── db/ # SQLAlchemy 模型 / Alembic 迁移 / Repository
│ ├── shared/ # Pydantic schema / 错误信封(后端; 经 OpenAPI 暴露给前端)
│ └── config/ # 提供商配置/档位映射/env 解析
└── PRODUCT_SPEC.md / UX_SPEC.md / ARCHITECTURE.md / DEV_PLAN.md
```
**模块依赖方向**(高→低,禁止反向):
`apps/api(routers) → core / agents / skills → llm_gateway / memory → db → shared`
`core` 不依赖 `db` 的具体实现,只依赖 Repository 接口(依赖倒置,便于测试 mock
**前后端契约**`apps/api` 的 FastAPI 自动产出 OpenAPI → 生成 `apps/web/lib/api` 的 TS 类型,跨语言不漂移。
### 2.3 运行时拓扑
- **后端进程FastAPI**:处理 HTTP/SSE承载 API 层 + 编排器(LangGraph) + 网关 + 记忆服务(同进程,低延迟);长任务用 **BackgroundTasks** 在同进程后台跑,状态落 `jobs` 表(原型无独立 Worker常驻服务部署即可避免 serverless 杀长函数)。
- **前端进程Next.js**SSR + 静态资源;调后端 API。
- **Postgres**:单主库起步;读多可加只读副本(看板/列表查询走副本)。
- **横向扩展**:原型单实例即可;后端无状态,规模化可多实例 + 抽独立 Workerarq/Celery
---
## 3. 数据架构 ← PRODUCT_SPEC §3.2
产品意图:记忆即真相源,每章写前检索注入、验收后增量更新;不可变追加保留历史。
### 3.1 完整 DDL11 张创作表 + 运营表)
> 类型以 PostgreSQL 表述;`jsonb` 用于结构化但 schema 演进频繁的字段。所有业务表带 `project_id` 外键(原型单用户,多租户行级隔离为后续)。**原型无向量列**——记忆选择走确定性逻辑§3.4);向量检索为 P2届时再加 `embedding vector(N)` 列与 HNSW 索引。
> 下面先列 **11 张创作数据表**(对齐 PRODUCT_SPEC §3.2;其中 `timeline`/`decisions` 为 P2MVP 可不建),再列 **运营/系统表**(用户/凭据/用量/技能/jobs
```sql
-- 作品(根表,承载总纲/卖点/结构)
CREATE TABLE projects (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
owner_id uuid NOT NULL REFERENCES users(id),
title text NOT NULL,
genre text,
logline text, -- 一句话故事
premise text, -- 总纲
theme text, -- 立意
selling_points jsonb DEFAULT '[]', -- 卖点数组
structure text, -- 故事结构选型(三幕/故事圈/雪花)
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
);
-- 人物(设定库 / 角色生成器产出)
CREATE TABLE characters (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
project_id uuid NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
name text NOT NULL,
role text, -- 定位: 主角/CP/对手/导师/工具人
traits jsonb, -- 性格(核心-表层-阴影/大五)
appearance text,
motive text,
backstory text, -- 背景故事
arc jsonb, -- 人物弧光: 起点→转变→终点
speech_tics jsonb, -- 口癖/语言风格
tags jsonb DEFAULT '[]', -- 人设标签/萌点
relations jsonb DEFAULT '[]', -- 关系网边: [{target, type}]
first_chapter int,
latest_state text, -- 最新状态(随验收更新)
created_at timestamptz NOT NULL DEFAULT now()
);
-- 世界观实体(势力/地理/力量体系/物品)
CREATE TABLE world_entities (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
project_id uuid NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
type text NOT NULL, -- faction/place/power_system/item/rule
name text NOT NULL,
rules jsonb, -- 硬规则(供一致性校验引用)
first_chapter int,
latest_state text,
created_at timestamptz NOT NULL DEFAULT now()
);
-- 大纲(分卷分章 + 场景)
CREATE TABLE outline (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
project_id uuid NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
volume int NOT NULL,
chapter_no int NOT NULL,
beats jsonb, -- 节拍/场景清单
foreshadow_windows jsonb DEFAULT '[]', -- 本章关联的伏笔回收窗口(可多条)
UNIQUE (project_id, chapter_no)
);
-- 章节事实摘要(append-only, 防矛盾的关键)
CREATE TABLE chapter_digests (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
project_id uuid NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
chapter_no int NOT NULL,
facts jsonb NOT NULL, -- 结构化事实: 谁做了什么/状态变化/新增设定
created_at timestamptz NOT NULL DEFAULT now()
);
-- 伏笔账本
CREATE TABLE foreshadow (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
project_id uuid NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
code text NOT NULL, -- F-012
title text NOT NULL,
status text NOT NULL DEFAULT 'OPEN', -- OPEN/PARTIAL/CLOSED/OVERDUE
planted_at int, -- 埋设章号
content text,
expected_close_from int,
expected_close_to int,
importance text, -- 主线/支线
links jsonb DEFAULT '[]', -- 关联人物/势力
progress jsonb DEFAULT '[]', -- [{chapter, note, status}] append
updated_at timestamptz NOT NULL DEFAULT now(),
UNIQUE (project_id, code)
);
-- 文风指纹
CREATE TABLE style_fingerprint (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
project_id uuid NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
dimensions_json jsonb NOT NULL, -- 16 维: {dim: value}
evidence_json jsonb NOT NULL, -- 每维原文证据
version int NOT NULL DEFAULT 1, -- 增量更新版本
created_at timestamptz NOT NULL DEFAULT now()
);
-- 四级规则
CREATE TABLE rules (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
project_id uuid REFERENCES projects(id) ON DELETE CASCADE, -- global 级可为 NULL
level text NOT NULL, -- global/genre/style/project
content text NOT NULL,
created_at timestamptz NOT NULL DEFAULT now()
);
-- 章节正文(带版本, 支持改稿回溯)
CREATE TABLE chapters (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
project_id uuid NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
volume int NOT NULL,
chapter_no int NOT NULL,
content text,
status text NOT NULL DEFAULT 'draft', -- draft/accepted
version int NOT NULL DEFAULT 1,
created_at timestamptz NOT NULL DEFAULT now(),
UNIQUE (project_id, chapter_no, version)
);
-- 审稿留痕(四审报告 + 裁决; 支撑 UX 审稿历史; append 每次审稿)
CREATE TABLE chapter_reviews (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
project_id uuid NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
chapter_no int NOT NULL,
chapter_version int, -- 审的是哪个草稿版本
conflicts jsonb DEFAULT '[]', -- continuity 冲突清单
foreshadow_sug jsonb DEFAULT '[]', -- 新埋/回收建议
style jsonb, -- 漂移段 + 评分
pace jsonb, -- 注水/钩子/节拍图
health_score int, -- 0-100
decisions jsonb, -- 作者裁决(采纳/忽略/回炉)留痕
created_at timestamptz NOT NULL DEFAULT now()
);
-- 时间线(P2; MVP 先由 chapter_digests 推导)
CREATE TABLE timeline (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
project_id uuid NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
event text NOT NULL,
chapter_no int,
story_time text, -- 故事内时间(可模糊)
created_at timestamptz NOT NULL DEFAULT now()
);
-- 设定讨论决议留痕(P2)
CREATE TABLE decisions (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
project_id uuid NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
topic text NOT NULL,
decision text NOT NULL,
created_at timestamptz NOT NULL DEFAULT now()
);
```
**运营/系统表(非创作数据)**
```sql
-- 用户(多租户主体; owner_id 外键指向此)
CREATE TABLE users (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
email text UNIQUE NOT NULL,
display_name text,
created_at timestamptz NOT NULL DEFAULT now()
);
-- 提供商凭据(各家 API Key, 加密存储; 按用户/作品)
CREATE TABLE provider_credentials (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
owner_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
project_id uuid REFERENCES projects(id) ON DELETE CASCADE, -- NULL=用户级默认
provider text NOT NULL, -- anthropic/deepseek/kimi/openai/...
api_key_enc bytea NOT NULL, -- 加密密文(KMS/加密列), 永不回显
created_at timestamptz NOT NULL DEFAULT now(),
UNIQUE (owner_id, project_id, provider)
);
-- 档位路由配置(全局默认在 config; 作品级覆盖落库)
CREATE TABLE tier_routing (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
project_id uuid REFERENCES projects(id) ON DELETE CASCADE, -- NULL=用户/全局
tier text NOT NULL, -- writer/analyst/light
provider text NOT NULL,
model text NOT NULL,
fallback jsonb DEFAULT '[]', -- [{provider,model}] 回退链
UNIQUE (project_id, tier)
);
-- 用量记账(按 provider+model 计费, 支持多币种)
CREATE TABLE usage_ledger (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
owner_id uuid NOT NULL REFERENCES users(id),
project_id uuid REFERENCES projects(id),
provider text NOT NULL,
model text NOT NULL,
input_tokens int NOT NULL,
output_tokens int NOT NULL,
cache_read int DEFAULT 0,
cost_minor bigint NOT NULL, -- 最小货币单位
currency text NOT NULL, -- USD/CNY...(混币种)
created_at timestamptz NOT NULL DEFAULT now()
);
-- 技能 registry(内置/自定义/社区; 对齐 PRODUCT_SPEC §5.5)
CREATE TABLE skills (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
scope text NOT NULL, -- builtin/custom/community
owner_id uuid REFERENCES users(id), -- custom 时归属
name text NOT NULL,
description text,
tier text NOT NULL, -- 档位 或 provider:model
system_prompt text NOT NULL,
input_schema jsonb,
output_schema jsonb,
reads jsonb DEFAULT '[]', -- 声明式表读权限
writes jsonb DEFAULT '[]', -- 声明式表写权限
genre text,
examples jsonb DEFAULT '[]',
created_at timestamptz NOT NULL DEFAULT now()
);
-- 异步长任务状态(BackgroundTasks + 轮询; §7.4)
CREATE TABLE jobs (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
project_id uuid REFERENCES projects(id) ON DELETE CASCADE,
kind text NOT NULL, -- style_learn / full_scan / batch_chars
status text NOT NULL DEFAULT 'queued', -- queued/running/done/failed
progress int DEFAULT 0, -- 0-100
result jsonb,
error text,
created_at timestamptz NOT NULL DEFAULT now(),
updated_at timestamptz NOT NULL DEFAULT now()
);
```
### 3.2 索引设计
```sql
-- 租户 + 高频过滤
CREATE INDEX idx_characters_proj ON characters(project_id);
CREATE INDEX idx_world_proj ON world_entities(project_id);
CREATE INDEX idx_digests_proj_ch ON chapter_digests(project_id, chapter_no);
CREATE INDEX idx_chapters_proj_ch ON chapters(project_id, chapter_no);
CREATE INDEX idx_foreshadow_proj_status ON foreshadow(project_id, status); -- 看板/到期扫描
-- 可选: 实体按名匹配(确定性检索的 FTS 兜底, §3.4)
CREATE INDEX idx_char_name_trgm ON characters USING gin (name gin_trgm_ops);
CREATE INDEX idx_world_name_trgm ON world_entities USING gin (name gin_trgm_ops);
-- (P2 引入向量时再加 embedding 列 + HNSW 索引)
```
### 3.3 不可变与版本化策略
| 数据 | 策略 | 说明 |
|---|---|---|
| 章节摘要 `chapter_digests` | **append-only** | 每章一行,从不改;历史可回溯"第 X 章发生了什么" |
| 伏笔进展 `foreshadow.progress` | **数组 append** | 状态字段可变,但进展只追加 |
| 章节正文 `chapters` | **多版本行** | 改稿写新 version 行,旧版保留;`(project_id, chapter_no, version)` 唯一 |
| 文风指纹 | **版本号递增** | 增量补样本生成新 version |
| 设定(人物/世界观) | **就地更新 + latest_state** | 冲突不静默覆盖,标 `[CONFLICT]` 待裁决;重大变更可在 `decisions` 留痕 |
> 不可变原则呼应用户全局编码规范(不就地覆盖、保留历史)。验收写回为**单事务**§5.5),保证摘要/伏笔/状态一致落库或全回滚。
### 3.4 确定性记忆选择(无向量)
原型用**确定性选择**取代向量检索——"本章有谁"大纲已点名,设定集也不大,无需语义相似度去猜,且完全可调试。
- **选择来源(并集去重)**
1. **显式引用**——本章大纲 `beats` 点名的人物/世界观实体(最精准)。
2. **主角常驻**——`role` 为主角/核心的角色永远纳入。
3. **近况实体**——近 N 章 `chapter_digests.facts` 里出现过的实体N 可配,默认 35
4. **(可选)按名匹配**——正文/大纲提到的实体名,用 Postgres `pg_trgm`/全文匹配兜底§3.2 trgm 索引)。
5. 命中本章 `outline.foreshadow_windows` 的伏笔。
- **渲染为卡片**:每个选中实体行渲染成一张"角色/设定卡"文本块(喂给 AI 的"MD"由行生成,非手管文件)。
- **拼装**:选中卡 + latest_state → "易变状态块",置于 prompt 缓存断点**之后**§4.6)。
- **目的**:只注入相关设定(控成本)+ 覆盖关键实体(抗上下文衰减),对齐 PRODUCT_SPEC §3.5。
- **已知局限(覆盖盲区)**:若本章涉及一个**既未在 beats 点名、又久未出场(不在近 N 章)**的实体(如回收 50 章前的伏笔、重提冷门角色),确定性选择会**漏注入**→ AI 可能写出与旧设定冲突的内容——这正是产品要防的失败。缓解:①命中 `outline.foreshadow_windows` 的伏笔关联实体强制纳入;②对正文/大纲做按名 `pg_trgm` 匹配兜底;③**允许作者在工作台手动 pin 实体**进本章注入。这是砍向量换简单的代价向量检索P2能从语义上补这个盲区。
- **P2 升级位**:设定集膨胀或盲区频发时,在 `select_relevant_entities()` 同一接口后接入**向量检索**(加 `embedding` 列 + HNSW + 嵌入服务),上层无感。
### 3.5 数据访问层Repository
- 每张表一个 Repository`CharacterRepo`/`ForeshadowRepo`…),封装 SQL**统一强制 `project_id` 过滤**(数据隔离;多租户化时此层再加 `owner_id` 校验)。
- `core` 依赖 Repository **接口**而非实现(依赖倒置),测试时注入内存实现。
- 迁移:**Alembic** 版本化迁移CI 校验 schema 与 SQLAlchemy 模型一致。
---
## 4. LLM 网关架构 ← PRODUCT_SPEC §3.3 / §3.4 / §3.5
产品意图不绑定单一厂商Agent 只声明**能力档位**,网关映射到具体 provider+model并屏蔽各家能力差异。
### 4.1 统一内部接口契约
网关对上层(编排器/Agent暴露单一接口屏蔽各家差异
```python
class Block(BaseModel):
text: str
cache: bool = False # 稳定块可标缓存断点
class LlmRequest(BaseModel):
tier: Literal["writer", "analyst", "light"] # 档位(或显式 provider:model 锁定)
system: list[Block] = [] # 稳定块在前
input: str | list[Block] # 易变内容(断点之后)
stream: bool = False
output_schema: type[BaseModel] | None = None # 需结构化输出时(Pydantic 模型/instructor)
thinking: bool = False # 是否启用思考/推理(有则启用)
max_tokens: int | None = None
scope: "Scope" # {project_id; user_id 原型可固定}
class Usage(BaseModel):
provider: str; model: str
input_tokens: int; output_tokens: int
cache_read_tokens: int = 0
cost_minor: int; currency: str # 混币种: USD/CNY…
class LlmResponse(BaseModel):
text: str
parsed: BaseModel | None = None # output_schema 命中时的结构化结果
usage: Usage
served_by: "ServedBy" # {provider; model; fell_back}
```
- 流式:`stream=True` 返回 `AsyncIterator[Delta]`,归一各家 SSE 事件为统一 `Delta`
- 上层永远不碰具体厂商字段——换厂商对 Agent 透明。
### 4.2 适配器设计
```
LlmRequest(统一)
┌────────▼─────────┐
│ 网关核心 │ 路由→能力协商→(缓存)→调用→(回退)→记账
└────────┬─────────┘
│ 归一化请求
┌─────────────┼──────────────┐
▼ ▼ ▼
OpenAI兼容适配器 Anthropic适配器 Gemini适配器
(DeepSeek/Kimi/ (Messages API (generateContent)
Qwen/GLM/OpenAI) 形态)
```
- **适配器职责**:把 `LlmRequest` 翻译成目标家 API 请求;把响应/流/usage 翻译回统一形态;声明**能力矩阵**(见 4.4)。
- **OpenAI 兼容适配器一套覆盖多家**DeepSeek/Kimi/Qwen/GLM/OpenAI 仅 baseURL + model + key 不同。
- Anthropic、Gemini 各一适配器(请求形态不同)。
- 新增厂商 = 新增一个适配器 + 在 `config` 注册,零侵入上层。
### 4.3 档位路由与三级配置解析
```
解析优先级(高→低):单 Agent 锁定 > 作品级覆盖 > 全局默认
tier ──▶ resolve(scope) ──▶ { provider, model, fallback[] }
```
- 配置来源:`config`(全局默认)+ 作品设置DB+ Skill 内 `tier/provider:model`§5.6)。
- 解析结果含**主模型 + 回退链**UX §6.10 的"主/回退")。
- 切换厂商只改配置,不改 Agent 代码(对齐 ADR-3
### 4.4 能力协商与降级
各家能力参差,网关维护**能力矩阵**并优雅降级:
| 能力 | 支持时 | 不支持时(降级) |
|---|---|---|
| 结构化输出(JSON Schema) | 走原生 | JSON 提示 + Pydantic/instructor 校验 + 重试(**上限 2-3 次**,超限标"该审未完成"不死循环) |
| 前缀缓存 | 标记缓存断点 | 跳过缓存(不影响正确性,仅成本) |
| 思考/推理 | 启用 | 普通生成 |
| 工具调用 | 原生 | 本系统四审/生成均用结构化输出实现,不强依赖工具调用 |
- 能力矩阵由适配器声明(`capabilities()`),网关据此决定调用形态。
- 降级对上层透明,仅在 `usage`/日志标注实际行为。
- **轻量档可靠性风险**最便宜模型Qwen-turbo/GLM-flash 类JSON 结构化输出不稳,易触发重试。故 pace/style 这类轻量档审稿要用上面的重试上限兜底;若某 provider 在轻量档反复失败,运营可在档位路由把它换成更稳的模型(仍属轻量档)。
### 4.5 故障回退与重试
```
调用主模型
├─ 成功 ────────────────────────▶ 返回(标 served_by.fell_back=false)
├─ 限流(429)/超时/5xx ─▶ 指数退避重试(SDK/自实现, 上限 R 次)
│ └─ 仍失败 ─▶ 切回退链下一个 provider
└─ 内容策略拒绝 ─────────▶ 按策略可切回退或上抛(让作者知情)
回退链耗尽 ─▶ 抛 LlmUnavailable(上层降级: 提示作者换档位/稍后重试; 不丢正文)
```
- **中途失败**:流式写章已吐部分 token 后回退耗尽 → 已生成部分由自动保存留在 `chapters(draft)`LangGraph write 节点报错使图停在 write 前的 checkpoint前端提示"生成中断,可重试或基于已存部分续写",不丢稿。
- **熔断**:某 provider 连续失败超阈值,短时熔断、直接走回退,避免雪崩。
- **缓存隔离**:回退到不同 provider 时缓存失效(缓存按 `provider+model` 维度§4.6)。
- 重试只对幂等的生成/校验;写库副作用在编排层、不在网关。
### 4.6 缓存抽象(稳定前缀)
- **原则跨厂商通用**`system` 中的稳定内核(世界观硬规则 + 文风指纹 + 少量真正定型的角色)置于缓存断点前;易变(活跃角色的 `latest_state` + 本章大纲)置于断点后。
- **务实预期**:真正稳定的多是世界观硬规则 + 文风指纹;角色 `latest_state` 每次出场验收都变、新角色随时加入,所以"稳定内核"会随连载缩水、失效更频繁。**"省约 90%" 仅在稳定内核占比高时成立**不是通用承诺缓存收益应按命中率实测评估§9.3)。
- **机制按厂商封装**Claude 显式 `cache_control`OpenAI/DeepSeek 自动前缀缓存;不支持者跳过。
- **失效面**:缓存键含 `provider+model`;上游稳定块任一字节变更即失效(故组装时排序序列化、不混入时间戳/UUID
- 命中指标回传 `usage.cache_read_tokens`供观测§9.3)。
### 4.7 凭据管理与隔离
- 各提供商 API Key 加密存储KMS 或 DB 加密列),**前端永不回显明文**UX §6.10)。
- 作用域:平台级默认 Key可选+ 用户/作品级自带 Key解析时按 scope 选取。
- `测试连接`:发一个最小探测请求验证 Key 有效 + 拉取能力矩阵。
### 4.8 用量记账
- 每次调用落 `usage_ledger`provider/model/in/out/cache_read/cost`owner_id+project_id+provider+model+day` 聚合。
- 成本换算表随 provider 配置维护单价随厂商而异§3.3)。
- 供 UX §6.10 月度概览 + 按档位/作品下钻。
---
## 5. Agent 编排引擎 ← PRODUCT_SPEC §3.6 / §5
产品意图:单一职责 Agent + 确定性编排Agent 经记忆库交换、不直连;四审只读、产出经验收 gate。
### 5.1 Agent / Skill 声明式抽象
内置 Agent 与用户 Skill 同构——都是一份**声明**,由编排器加载后经网关执行:
```python
class AgentSpec(BaseModel):
name: str # worldbuilder / writer / continuity ...
tier: Tier # 能力档位(网关解析 provider+model)
system_prompt: str # 角色与约束
input_schema: type[BaseModel] # 入参契约
output_schema: type[BaseModel] | None # 结构化产出契约(网关保证; writer 为 None=纯文本)
reads: list[TableName] # 声明式表读权限
writes: list[TableName] # 声明式表写权限(经验收才生效)
genre: str | None = None # 题材适用(Skill 用)
```
- 内置 8 Agent 即 `scope=builtin` 的 AgentSpec用户 Skill 为 `custom/community`§5.6)。
- `reads/writes` 是**契约**:运行时强制,越权拒绝(安全见 §5.6 / §9.2)。
### 5.2 编排器LangGraph 图)
编排器是一张 **LangGraph 状态图**(非自治 agent loop节点固定、边确定用其**并行**跑四审、用 **interruptHITL+ checkpoint** 表达"写章→作者裁决→验收"的跨请求暂停-恢复。
```python
# 状态: { project_id, chapter_no, ctx, draft, reviews, decisions }
g = StateGraph(ChapterState)
g.add_node("assemble", lambda s: memory.assemble(s.project_id, s.chapter_no)) # §5.3
g.add_node("write", lambda s: gateway.run(writer_spec, s.ctx, stream=True)) # 纯函数产草稿
# 四审为并行分支(无依赖), 汇入 collect
for spec in (continuity_spec, foreshadow_spec, style_spec, pace_spec):
g.add_node(spec.name, make_review_node(spec)) # 只读, 结构化输出
g.add_node("collect", collect_reviews) # 汇总四审 → 落 chapter_reviews 表(留痕)
g.add_edge("assemble", "write")
for spec in REVIEW_SPECS: # write ─並行→ 四审
g.add_edge("write", spec.name); g.add_edge(spec.name, "collect")
# 验收 = HITL 中断: 图在此 interrupt; 审稿结果已落 chapter_reviews 表, 前端从表读取并裁决;
# 作者 POST /accept 携 decisions(+ 可能改过的终稿) 后, 从 checkpoint 恢复, 进 accept 节点
g.add_node("accept", commit_accept) # 单事务(含「从终稿提炼 digest」): 见 §5.5
graph = g.compile(checkpointer=PostgresSaver(...), interrupt_before=["accept"])
```
- **确定性**:图固定、可单测、可回放(给定输入 + mock 网关输出确定checkpoint 落 Postgres
- **并行**:四审为并行分支汇入 collect任一失败不阻塞其余报告标"该审未完成"。
- **HITL 恢复 + 真相源边界**:用 `interrupt_before=["accept"]` + checkpointer 支持"写章请求返回、验收请求恢复"的跨请求流程。**正文以 `chapters` 表、审稿结果以 `chapter_reviews` 表为权威真相源checkpoint 只存"图走到哪 + 待裁决句柄",不作正文/审稿的真相源**——恢复时按 `chapter_no` 从领域表重读,避免双真相源(对齐 ADR-2
- **编排 vs Agent**图做控制流与事务边界Agent 节点只做单次认知任务、经记忆库交换(不直连)。
### 5.3 记忆注入与 prompt 组装
```python
def assemble(project_id, chapter_no):
outline_row = outline_repo.get(project_id, chapter_no)
entities = select_relevant_entities(project_id, outline_row) # 确定性, §3.4
# = 显式点名 主角常驻 近况实体 (可选)按名匹配
forewin = foreshadow_repo.windows_for(chapter_no)
digests = digest_repo.recent(project_id, k) # 近况摘要
fingerprint = style_repo.latest(project_id)
rules = rules_repo.effective(project_id) # 四级合并
stable_core = serialize_sorted(world_hard_rules + settled_chars + fingerprint + rules)
volatile = serialize_sorted(render_cards(entities) + forewin + digests + outline_row.beats)
return LlmRequest(system=[Block(text=stable_core, cache=True)], input=volatile)
```
- **缓存断点**stableCore 标 `cache:true`断点前volatile 在后§4.6)。
- **确定性序列化**:排序 + 无时间戳/UUID保证缓存字节稳定。
- 四级规则合并:`global → genre → style → project`,越具体越优先。
### 5.4 8 个 Agent 的 I/O 契约(对齐 §5.2 花名册)
| Agent | 档位 | reads | writes(经验收) | 输出 schema 要点 |
|---|---|---|---|---|
| worldbuilder | 写手 | projects | world_entities | `{entities:[{type,name,rules}]}` 硬规则显式 |
| character-gen | 写手 | world_entities, characters | characters | `{cards:[{name,role,traits,backstory,arc,speech_tics,tags,relations}]}` |
| outliner | 分析 | projects, foreshadow, characters, world_entities | outline | `{chapters:[{no,beats,foreshadow_windows}]}` |
| writer | 写手 | (注入) | —(产草稿,持久化见下注) | 纯文本流(stream) |
| continuity | 分析 | chapter_digests, characters, world_entities | —(只读) | `{conflicts:[{type,where,refs,suggestion}]}`仅冲突digest 在验收时从终稿另提,见下注) |
| foreshadow-analyst | 分析 | foreshadow | —(只读) | `{planted:[...], resolved:[...]}` 建议 |
| style-auditor | 轻量(打分)/分析(提取) | style_fingerprint | style_fingerprint(仅学文风提取) | 提取`{dims,evidence}` / 打分`{segments:[{idx,score}]}` |
| pace-checker | 轻量 | rules(genre) | —(只读) | `{water:[...], hook:bool, beat_map:[...]}` |
- 所有非 writer 的 Agent **结构化输出**网关保证§4.4),便于程序消费与就地裁决。
- **writer 不直接写库**:产草稿(stream)→自动保存落 `chapters(draft)`(确定性代码,即时)→验收晋升为 `accepted` 新 version§5.5)。四审 `writes` 为空(只读)。
- **style-auditor 的写入是例外**:发生在**学文风**流程(写 `style_fingerprint`),不在写章流水线;写章时它只读指纹做漂移打分。
- **digest 从终稿提取,不从审稿草稿**:审稿时 continuity 只出冲突;作者裁决/改稿后,验收事务里用**终稿**另跑一次轻量提炼得 `digest` 再追加 `chapter_digests`§5.5/§6.1)——否则改稿前的草稿事实会污染真相源。
### 5.5 验收与状态写回(事务)
- 验收是**确定性代码**,非 Agent。把作者裁决后的变更**单事务**落库:
1. 章节晋升 `accepted` 新 version
2. **从终稿提炼 `digest`**(轻量 LLM 调用,输入是最终验收文本而非审稿草稿)→ 追加 `chapter_digests`
3. 应用伏笔状态变更/新登记、更新人物 latest_state、可选加规则
4. 写入本次裁决留痕(`chapter_reviews.decisions`)。
- 事务保证一致:全成功或全回滚,杜绝"摘要更了但伏笔没更"的半态。
- 写回后触发伏笔到期扫描§6.2)、看板/仪表盘缓存失效。
### 5.6 技能系统运行时Skill loader + 沙箱)
- **加载**:从 `skills` registry 读 AgentSpecbuiltin/custom/community`tier` 经网关执行(复用 §5.1 机制)。
- **表权限强制(执行点说明)**:这**不是进程级沙箱**——Skill 是声明式 prompt本身不执行代码。强制点在**编排/写库层**:① 注入时只把声明 `reads` 的表数据喂给该 skill② 验收/写库代码只把该 skill 产出**应用到其声明的 `writes` 表**(白名单),越权的产出字段丢弃并审计。即"声明式权限 + apply 层白名单",而非隔离运行时。
- **纯声明式**Skill 不含可执行代码,仅 prompt + schema + 权限,杜绝任意代码执行。
- **写库仍经验收 gate**:自定义 Skill 产出同样要过作者验收/四审才入库,不开后门。
- 安全细节见 §9.2。
---
## 6. 核心模块详细设计 ← PRODUCT_SPEC §4
### 6.1 长篇一致性
- **摘要提炼**:验收时从**终稿**(作者裁决/改稿后的最终文本,非审稿草稿)提炼 `digest`(结构化事实),事务追加 `chapter_digests`——避免改稿前的草稿事实污染真相源。
- **实体状态**:人物/世界观维护 `latest_state`;验收按裁决更新(带 `[CONFLICT]` 的不自动覆盖)。
- **冲突检测**:审稿时 continuity 比对草稿 vs近况摘要 + 人物卡 + 世界观硬规则),产出结构化冲突清单:
```
conflict = { type: 性格漂移|能力不符|设定违例|地理矛盾|时间线倒错,
where: 本章定位, refs: [冲突来源章/设定], suggestion: 改法 }
```
- **裁决流**:前端就地高亮 + 报告条目 → 作者选 `采纳改法 / 忽略 / 手改`;忽略可沉淀为规则。**未决冲突不允许验收**UX §9 状态)。
- **校验清单**:参考 *Lost in Stories 2026* 分类(人物/设定/时间三类)。
- 时间线MVP 由 `chapter_digests` 推导P2 落 `timeline` 表加速。
### 6.2 伏笔账本
**状态机**
```
登记 首次照应 完成回收
─────▶ OPEN ──────▶ PARTIAL ─────────▶ CLOSED
│ │
└─────────────┴──▶ OVERDUE (章号 > expected_close_to 且未 CLOSED)
(到期扫描置位; 回收后可转 CLOSED)
```
- **到期扫描**:验收后触发(确定性代码,非 Agent`current_ch > expected_close_to AND status≠CLOSED → OVERDUE`,写回并在看板/报告提醒。
- **新埋/回收建议**:审稿时 foreshadow-analyst 检测本章新埋线 / 疑似回收 → 建议,作者确认后登记/改状态。
- **依赖图/窗口**`outline.foreshadow_windows` 关联章与伏笔;排大纲时提示接近回收窗口的伏笔。
- **看板**:按 `(project_id, status)` 索引查询,四泳道 + OVERDUE 强调UX §6.8)。
### 6.3 文风模仿
- **提取(一次性,分析档)**35 样本≥5 万字)→ 16 维指纹9 通用 + 7 中文),**每维附原文证据**,存 `style_fingerprint`(版本化、可增量合并)。
- **生成约束**:写章注入指纹(缓存稳定块)。
- **漂移打分(高频,轻量档)**:每段对照指纹打相似度分;低于阈值标漂移段。
- **回炉**:仅重写漂移段、保留上下文,新旧 diffUX §8.3。阈值可配§10 风险:需校准)。
### 6.4 节奏引擎
- **模板 DSL**(存 `rules` genre 级):黄金三章、章末钩子、爽点密度(每 N 千字一拍)、情绪曲线。
- **检测pace-checker轻量档**:注水段(信息密度过低/重复铺陈)、章末钩子有无、爽点节拍图、情绪曲线。
- **产出**`{water:[段], hook:bool, beat_map:[...]}` → 报告以"爽点节拍图 ▁▃▅"可视化UX §6.4)。
- 这是对英文工具的差异化(中文网文模板)。
### 6.5 角色生成器
- **单/批量**:输入一句话需求 + 数量 + 定位 → character-gen写手档产出结构化角色卡。
- **防雷同(批量)**一次性生成一组时prompt 注入"已生成卡 + 已有角色",要求差异化定位/性格;可对产出做相似度去重复核。
- **一致性校验接入**:入库前由**编排器**追加一道 continuity 检查(非 character-gen 直接互调,符合 §5.4 数据流),确认不与世界观/力量体系冲突。
- **入库**:过校验 → 写 `characters`角色行即注入单元§3.4 渲染成卡片)→ 进入后续确定性选择注入。
---
## 7. API 设计 ← PRODUCT_SPEC §6
### 7.1 REST 约定与错误信封
- **作用域嵌套**:章节端点统一 `/projects/:id/chapters/:no/...`(修正 PRODUCT_SPEC §6 中 `:no` 跨项目不唯一的 nit见文末发现
- **鉴权****原型单用户**(无登录/归属校验);多租户 + 按 `owner_id` 校验为后续§9.1)。
- **统一成功**`200/201 { data, meta? }`。
- **统一错误信封**
```json
{ "error": { "code": "CONFLICT_UNRESOLVED", "message": "...", "details": {} } }
```
常见码:`NOT_FOUND / VALIDATION / CONFLICT_UNRESOLVED(未决冲突禁验收) / LLM_UNAVAILABLE(回退耗尽) / RATE_LIMITED`(多租户上线后补 `UNAUTHORIZED/FORBIDDEN`)。
### 7.2 端点清单(对齐 §6 + 设置/伏笔/看板)
| Method | Path | 请求 | 响应 | 码 |
|---|---|---|---|---|
| POST | `/projects` | 立项向导各字段 | project | 201 |
| GET | `/projects` | — | 作品列表(含待办徽标) | 200 |
| GET | `/projects/:id` | — | 作品详情 | 200 |
| POST | `/projects/:id/world/generate` | `{需求}` | world_entities(预览) | 200 |
| POST | `/projects/:id/characters/generate` | `{需求, count?, role?}` | 角色卡(预览,待入库) | 200 |
| POST | `/projects/:id/characters` | 角色卡 | 入库结果 | 201 |
| POST | `/projects/:id/style` | 样本(`mode=update?`) | `{job_id}`(异步见 7.4) | 202 |
| GET | `/projects/:id/style` | — | 最新文风指纹 `{dimensions, evidence, version}` | 200 (无指纹 404) |
| GET | `/jobs/:id` | — | 任务状态/进度/结果 | 200 |
| POST | `/projects/:id/outline` | `{范围}` | 大纲(含伏笔窗口) | 200 |
| POST | `/projects/:id/chapters/:no/draft` | `{}` | **SSE 流**草稿 | 200(stream) |
| PUT | `/projects/:id/chapters/:no/draft` | `{text}` | 自动保存草稿(幂等,写 `chapters.draft`,断流兜底) | 200 |
| POST | `/projects/:id/chapters/:no/review` | `{draft}` | **SSE 流**四审报告 | 200(stream) |
| POST | `/projects/:id/chapters/:no/accept` | `{裁决清单}` | 写回结果(将更新清单) | 200 |
| GET | `/projects/:id/chapters/:no/reviews` | — | 审稿历史(`chapter_reviews`) | 200 |
| POST | `/projects/:id/chapters/:no/refine` | `{段落, 指令}` | 回炉重写段(diff) | 200 |
| GET | `/projects/:id/foreshadow` | `?status=` | 伏笔看板数据 | 200 |
| POST | `/projects/:id/rules` | `{level, content}` | rule | 201 |
| GET | `/skills/toolbox` | — | 生成器描述符列表(key/标题/input_fields/ingestable/legacy_route) | 200 |
| POST | `/projects/:id/skills/:tool_key/generate` | `{brief, chapter_no?, count?, kind?}` | 结构化预览(不写库,仅记账) | 200 (未知 key 404/无 provider 503) |
| POST | `/projects/:id/skills/:tool_key/ingest` | `{world_entities?\|scenes?, chapter_no?, acknowledge_conflicts}` | 入库结果 | 201 (continuity 冲突 409) |
| GET/PUT | `/settings/providers` | 凭据/档位配置 | 配置(Key 脱敏) | 200 |
| POST | `/settings/providers/test` | `{provider}` | 连接+能力矩阵 | 200 |
### 7.3 流式接口SSE
- `draft` / `review` 用 **Server-Sent Events**:网关流式 `Delta` → 归一 SSE 事件 → 前端打字机渲染。
- 事件类型:`token`(正文增量)、`section`(四审分项开始/完成)、`conflict`(冲突命中)、`done`、`error`。
- 断流可重连:前端按已收 token 续接;正文自动保存兜底(不丢稿)。
### 7.4 异步长任务BackgroundTasks + jobs 表)
- **长任务**:学文风(解析数万字样本)、全书一致性回归扫描、批量群像 → **FastAPI BackgroundTasks** 在同进程后台跑,先写 `jobs` 行返回 `{job_id}`202前端轮询 `GET /jobs/:id` 取进度/结果。
- 幂等以 `job_id` 去重;失败置 `jobs.status=failed` 可重试。
- **持久性局限 + 缓解**BackgroundTasks 在进程内跑,**进程重启/部署会丢任务**、`jobs.status` 卡在 `running`。原型缓解:**启动时把所有 `running` 僵尸 job 标 `failed`**,让用户看到失败并重试(而非进度条永转)。真正的持久重试/跨进程恢复属规模化项——换 arq/Celery接口不变
- **原型不上专门队列**。
- 写章/审稿**不走后台**(要即时流式反馈),走 SSE 同步流。
---
## 8. 前端架构 ← UX_SPEC
### 8.1 应用结构Next.js App Router前端独立于后端
- 路由对齐 UX §3.1 导航:`app/(dashboard)` 作品库;`app/projects/[id]/{write,outline,codex,foreshadow,review,style,rules}``app/settings/providers`。
- **Server Components** 取数(列表/看板/设定库只读视图);**Client Components** 承载编辑器、流式、交互。
- **调用独立 FastAPI 后端**§7 端点),经 **OpenAPI 生成的 TS 客户端**`app/lib/api`SSE 用 `EventSource`/`fetch` 流读。无 Next API Routes 业务逻辑(仅可留 BFF 代理/鉴权透传)。
### 8.2 状态管理
| 状态类别 | 方案 |
|---|---|
| 服务端数据(列表/设定/看板) | React Query / SWR缓存 + 失效) |
| 编辑器本地态(正文/光标/未保存) | 局部 storeZustand 或 RSC + local |
| 流式增量(draft/review) | SSE 订阅 → 增量 reducer |
| 乐观更新(验收/裁决) | 先改 UI、失败回滚 + Toast |
### 8.3 编辑器与流式渲染
- 正文编辑器:宋体 / 720px / 行高 1.9UX §2.3contentEditable 或 ProseMirror/TipTap富文本 + 锚点标注)。
- **流式打字机**SSE `token` 逐字淡入 + 朱砂光标;尊重 `prefers-reduced-motion`(关动效)。
- **冲突标注**`conflict` 事件 → 正文对应区间挂朱砂波浪线 + 悬浮卡 + 报告联动跳转。
- **自动保存**debounce 持续保存草稿(写 `chapters` 当前 version显示"保存于 hh:mm"。
### 8.4 设计 token 落地
- UX §2 的色板/字体/间距 → CSS 变量(`--paper`, `--ink`, `--vermilion` …)+ Tailwind theme。
- 纸感主题集中在 `:root`;预留 `[data-theme="night"]` 夜读模式扩展位UX §2.1 备注)。
- 组件库UX §7一一实现AppShell / Editor / ReviewCard / ForeshadowCard / TierRouter / ProviderRow …
---
## 9. 横切关注点 ← PRODUCT_SPEC §10 / §5.5
### 9.1 鉴权与多租户隔离(原型:单用户)
- **原型阶段**:单用户,无登录/无归属校验;`users` 表与 `projects.owner_id` 保留为接缝(可写死一个 stub 用户),暂不强制。
- **多租户化(后续)**:接入 Auth邮箱/OAuth→ 所有 Repository 强制 `project_id + owner_id` 行级隔离 → 越权返回 `FORBIDDEN` + 审计;再远期多人协作预留 `project_members`。
- 因 Repository 已统一封装表访问,多租户化时只在该层加归属校验,不动业务逻辑。
### 9.2 安全
| 面 | 措施 |
|---|---|
| 密钥 | 提供商 Key 加密存储KMS/加密列);前端永不回显;仅后端解密用于调用 |
| 用户 Skill不可信输入 | 纯声明式(无代码执行);`reads/writes` 表权限白名单强制;产出经验收 gate§5.6 |
| 提示注入 | 用户/作品文本以**数据**注入,不与系统指令混层;结构化输出 + 校验抑制越权产出Skill prompt 不得提权 |
| 输入校验 | 所有端点 Pydantic 校验(系统边界),失败 `VALIDATION` |
| 数据外发 | LLM 调用即把作品内容发往第三方——UX §6.10 标境内/境外,由作者/运营按合规取舍 |
| 注入/越权审计 | 关键操作(验收/改设定/Key 变更/Skill 越权拦截)留审计日志 |
### 9.3 可观测性
- **结构化日志**structlog请求、编排步骤、网关调用provider/model/usage/fell_back/cache_read
- **追踪**OpenTelemetry一次写章 = 一条 trace跨"检索→writer→四审→验收"span定位慢点。
- **指标**:四审耗时、缓存命中率、回退率、每作品成本;驱动 UX §6.10 成本看板与告警。
### 9.4 韧性与配置
- **韧性**:网关回退/熔断/重试§4.5SSE 断流可续;正文自动保存兜底;长任务可重试幂等。
- **配置**`config` 模块集中——提供商注册、档位默认映射、env 解析、特性开关P1/P2 功能灰度)。
---
## 10. 部署与运维 ← PRODUCT_SPEC §3.1 / §10
- **环境**dev / staging / prod 三套env 与密钥分环境管理。
- **部署拓扑**:前端 Next.js + 后端 FastAPI + Postgres**常驻服务**Fly.io/Render + 托管 PG或 Docker 自托管)——因后端有 BackgroundTasks 长任务,避免 serverless 杀长函数。后端无状态可水平扩。
- **数据库**:托管 Postgres**无需 pgvector 扩展**P2 引入向量时再开);定时备份 + PITRAlembic 迁移随发布执行、CI 校验。
- **扩展性/配额**:写章并发受 LLM 限流约束——按作品**速率配额**;网关侧聚合各 provider 限流429 退避 + 回退)。规模化再抽独立 Workerarq/Celery削峰。
- **发布**CI 跑测试 + 迁移校验 + 类型检查;蓝绿/滚动DB 迁移向后兼容(先加列后用)。
---
## 11. 测试策略
对齐团队规范≥80% 覆盖,单元/集成/E2E 三类TDD
| 层 | 范围 | 要点 |
|---|---|---|
| 单元 | 领域逻辑/状态机/纯函数 | 伏笔状态机、四级规则合并、prompt 组装确定性、档位解析 |
| 集成 | Repository + DB、LangGraph 图 + mock 网关、端点 | 验收事务原子性、HITL 恢复、SSE 流 |
| 契约 | **LLM 结构化输出 schema** | 每个 Agent 的 outputSchema 用录制样例做契约测试;适配器能力矩阵测试 |
| E2E | 关键流程 | 写一章闭环(写→审→裁决→验收)、生成群像、切换提供商 |
- **LLM mock**:网关可注入 mock provider返回固定/录制响应),使编排器/Agent 可确定性测试,不烧真实 token。
- **降级/回退测试**:模拟 429/不支持结构化输出,验证降级与回退路径。
- TDD新模块先写测试RED→GREEN→重构
---
## 12. 实施路线(架构视角) ← PRODUCT_SPEC §9
按依赖关系排架构落地顺序(与产品 M1M5 对齐):
| 里程碑 | 架构交付 | 依赖 |
|---|---|---|
| **M1** | `db`(**建全部表**+迁移,含 rules) → `llm_gateway`(单 provider 起 + 档位) → `memory`(确定性选择+组装) → `orchestrator`(写章) → API(立项/写本章/SSE) → 前端 AppShell+工作台 + **设置页(至少一个 provider)** | 无 |
| **M2** | continuity Agent + review SSE + 验收事务 + 冲突裁决 UI | M1 |
| **M3** | 伏笔状态机 + 到期扫描 + 看板 + outliner 伏笔窗口 + pace-checker | M2 |
| **M4** | 文风提取(异步任务) + style-auditor 双轨 + 回炉 | M1 网关 |
| **M5** | worldbuilder/character-gen 生成 + 多 provider 回退/降级完善 + Skill 运行时 + 规则/命令面板 | M2M4 |
> 网关的多 provider/回退/降级在 M1 先打**接口与单 provider**M5 补齐多家与韧性——避免一开始过度工程。
---
## 附录 A · ADR 关键决策记录
见 §1.2 摘要表;正式实现期每条 ADR 单独建档(背景/选项/决策/后果)。核心十条:模块化单体、库为真相源、自建网关、声明式 Agent、写章纯函数、Postgres 单库(确定性选择/无向量)、网关统一结构化输出、前端 Next + 后端 FastAPI 分离、BackgroundTasks 长任务、LangGraph 编排。
## 附录 B · ARCHITECTURE ↔ PRODUCT_SPEC 覆盖矩阵
| PRODUCT_SPEC 章节 | 对应 ARCHITECTURE |
|---|---|
| §1 问题 / §1.5 哲学 / §2 目标 | §1.1 NFR / §1.2 决策 |
| §2.5 功能总览 | §12 路线(按 P0P2 落地) |
| §3.1 技术栈 | §2 技术栈与工程结构 / §10 部署 |
| §3.2 数据模型 | §3 数据架构(完整 DDL |
| §3.3/§3.4/§3.5 网关/直连/缓存 | §4 LLM 网关架构 |
| §3.6 编排示意 | §5.2 编排器 |
| §4 四大模块 + 角色生成 | §6 核心模块详细设计 |
| §5 多 Agent | §5 Agent 编排引擎 |
| §6 接口 | §7 API 设计 |
| §7 规则积累 | §5.3 四级规则合并 + §7 rules 端点 |
| §8 差异化 | (产品论证,不在架构展开) |
| §9 实施路线 | §12 实施路线(架构视角) |
| §10 风险 | §9 横切关注点(安全/韧性/合规) |
| UX_SPEC | §8 前端架构 |
## 附录 C · 术语表
| 术语 | 含义 |
|---|---|
| 档位(tier) | 能力等级(写手/分析/轻量),网关映射到 provider+model |
| 适配器 | 把统一 LlmRequest 翻译成某厂商 API 的模块 |
| 真相源 | 数据库——Agent 间唯一交换媒介 |
| 四审 | continuity/foreshadow/style/pace 四个并行质检 Agent |
| 验收 gate | 作者裁决后的事务性写回AI 产出入库唯一入口 |
| 稳定内核 | prompt 中可缓存的稳定前缀(世界观硬规则+已定型角色+文风指纹) |
---
## 待回写规格的发现(已同步三份文档一致)
下列在架构下沉中暴露的上游缺口**已回写** PRODUCT_SPEC / UX_SPEC三份文档现已一致
1. ✅ **API 作用域**:章节端点统一为 `/projects/:id/chapters/:no/...`PRODUCT_SPEC §6 + UX §3.2 已改)。
2. ✅ **向量检索砍除**原型改确定性记忆选择§3.4),删除 `embedding` 列/HNSWPRODUCT_SPEC §3.2 已注明"向量检索为 P2届时再加 embedding 列 + 嵌入服务"。
3. ✅ **回炉端点**PRODUCT_SPEC §6 已增 `POST /projects/:id/chapters/:no/refine`(本架构 §7.2)。
4. ✅ **users 表**PRODUCT_SPEC §3.2 已补 `users` 与 `projects.owner_id`(本架构 §3.1 运营表)。