Files
writer-work-flow/ARCHITECTURE.md

62 KiB
Raw Blame History

网文创作工作流 · 工程架构文档Architecture

PRODUCT_SPEC.md(产品规格)下沉到可实现的技术架构:组件划分、接口契约、完整 DDL、LLM 网关内部设计、编排引擎、API 清单、横切关注点、部署与测试。 UX/UI 见 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/TSUX_SPEC 的页面与交互;流式渲染、冲突标注、乐观更新;经 OpenAPI 生成的 TS 客户端调后端。
  • 后端FastAPI/PythonAPI 层入参校验、SSE、编排器LangGraph、网关、记忆服务同进程。
  • 编排器LangGraph:确定性图串联 Agent写章→四审→验收 HITL并行四审、验收中断/恢复。
  • 记忆服务:写前确定性选择(显式+主角+近况无向量、prompt 组装(缓存断点)、验收写回(事务)。
  • LLM 网关:档位路由、适配器、能力协商/降级、回退、缓存、记账§4 详)。图中 OpenAI 兼容适配器一套覆盖 DeepSeek/Kimi/Qwen/GLM/OpenAIAnthropic/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 + FastAPIasync LLM 编排/工具的强生态async + SSE + Pydantic 校验天然契合
样式 Tailwind + CSS 变量(设计 token 纸感主题 token 化§8.4);快速一致
编排 LangGraphPython 写章→四审→验收的并行 + 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 → sharedcore 不依赖 db 的具体实现,只依赖 Repository 接口(依赖倒置,便于测试 mock前后端契约apps/api 的 FastAPI 自动产出 OpenAPI → 生成 apps/web/lib/api 的 TS 类型,跨语言不漂移。

2.3 运行时拓扑

  • 后端进程FastAPI:处理 HTTP/SSE承载 API 层 + 编排器(LangGraph) + 网关 + 记忆服务(同进程,低延迟);长任务用 BackgroundTasks 在同进程后台跑,状态落 jobs 表(原型无独立 Worker常驻服务部署即可避免 serverless 杀长函数)。
  • 前端进程Next.jsSSR + 静态资源;调后端 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

-- 作品(根表,承载总纲/卖点/结构)
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()
);

运营/系统表(非创作数据)

-- 用户(多租户主体; 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 索引设计

-- 租户 + 高频过滤
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

  • 每张表一个 RepositoryCharacterRepo/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暴露单一接口屏蔽各家差异

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_controlOpenAI/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_ledgerprovider/model/in/out/cache_read/costowner_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 同构——都是一份声明,由编排器加载后经网关执行:

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 表达"写章→作者裁决→验收"的跨请求暂停-恢复。

# 状态: { 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.2.1 实现现状(单章流程)与多章链图 ← 回写自实现审计 + Chain Workflow 交付2026-06-23

单章流程的实现现状(与上面"一张图串全程"的理想形态有别):上图是设计意图;实际单章流程未编译成这一张图——

  • 写章POST /projects/{id}/chapters/{no}/draft 直接 gateway.stream() 流式产草稿(stream_chapter_draft不经图。(曾有 build_write_graph 单节点图但无端点调用,已在多章链图落地时删除;write_node 本体保留供链复用。)
  • 四审POST /projects/{id}/chapters/{no}/review 编译 build_review_graph()一次性 ainvokeSTART→四审并行→collect→END不带 checkpointer
  • 验收POST /projects/{id}/chapters/{no}/accept确定性事务代码 run_accept_transaction(晋升终稿→从终稿提炼 digest→裁决留痕§5.5不是图节点;其 HITL 闸在 API/DB 层(冲突未裁决→CONFLICT_UNRESOLVED非 langgraph interrupt
  • 因此单章流程是线性的,interrupt / Postgres checkpointer 在单章流程未实际启用——线性流无需图。

多章工作流链Chain Workflow= 本项目首个真正用 LangGraph cyclic 图 + Postgres checkpointer + interrupt 的场景。 设计契约见 docs/design/chain-workflow.md。对标竞品「一键多章」,差异化在「每章过四审、遇冲突才停人」。

# ChainState(只存控制流, 不存正文/上下文/冲突明文): {project_id, chain_key,
#   start/last/current_chapter_no, written:[], has_conflicts}
# 节点各自用独立短 session(每章 accept 章原子提交); gateway 经 builder 按节点 session 重建
g = StateGraph(ChainState)
g.add_node("write_chapter",  write_chapter)   # assemble→收集版写章(gateway.run,非SSE)→落 chapters draft
g.add_node("review_chapter", review_chapter)  # 四审→落 chapter_reviews(只读留痕)
g.add_node("decide",         decide)          # 纯逻辑: 读最近审稿 → has_conflicts
g.add_node("accept_chapter", accept_chapter)  # interrupt-on-conflict; 复用 run_accept_transaction + 伏笔扫描
g.add_edge("write_chapter", "review_chapter"); g.add_edge("review_chapter", "decide")
g.add_conditional_edges("decide", ...)        # 有冲突→interrupt()暂停交人; 无冲突→直接 accept
g.add_conditional_edges("accept_chapter", ...) # current<last→回 write_chapter(循环); 否则→END
graph = g.compile(checkpointer=AsyncPostgresSaver(...))  # thread_id = job_id
  • 跑在 run_job 长任务壳里POST .../chains/{key}/run→202 {job_id} + BackgroundTaskGET /jobs/{id} 轮询进度(written);某章四审报冲突→interrupt() 暂停job=awaiting_input)→POST .../chains/runs/{job_id}/resume 携裁决 Command(resume=decisions) 续跑。
  • 守真相源边界(同 §5.2checkpoint 只存控制流位置 + 待裁决句柄;正文/审稿/冲突明文以领域表为权威resume 时重读 chapters/chapter_reviews(不变量 #5
  • checkpointer 建表setup_checkpointer 在 alembic 迁移建 langgraph 检查点 4 表DDL 只在 migrations/CI绝不 app-runtimeE2E 用 MemorySaver(单进程,零联网)。

5.3 记忆注入与 prompt 组装

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/communitytier 经网关执行(复用 §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)
  • 到期扫描:验收后触发(确定性代码,非 Agentcurrent_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? }
  • 统一错误信封
    { "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 / reviewServer-Sent Events:网关流式 Delta → 归一 SSE 事件 → 前端打字机渲染。
  • 事件类型:token(正文增量)、section(四审分项开始/完成)、conflict(冲突命中)、doneerror
  • 断流可重连:前端按已收 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/apiSSE 用 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 先打接口与单 providerM5 补齐多家与韧性——避免一开始过度工程。


附录 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 已补 usersprojects.owner_id(本架构 §3.1 运营表)。