feat: Phase 0 — monorepo 骨架 + 全表迁移 + FastAPI/Next 骨架 + CI

- uv(workspace) + pnpm monorepo;docker-compose(pg+api+web)
- SQLAlchemy 16 MVP 表 + Alembic 初版迁移(无漂移,users stub)
- FastAPI 骨架:统一错误信封(带 request_id) + structlog + /jobs/:id + OpenAPI
- Next.js 骨架:纸感主题 token + OpenAPI→TS 客户端代码生成(gen:api)
- CI(ruff/mypy/pytest + pg service + alembic 漂移校验)
- 四份设计规格(PRODUCT/UX/ARCHITECTURE/DEV_PLAN) + CLAUDE.md
This commit is contained in:
Yaojia Wang
2026-06-18 11:38:28 +02:00
commit d3dc620a71
74 changed files with 12960 additions and 0 deletions

960
ARCHITECTURE.md Normal file
View File

@@ -0,0 +1,960 @@
# 网文创作工作流 · 工程架构文档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 | `/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/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 运营表)。