# 网文创作工作流 · 工程架构文档(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.4;Agent 间零直连,降耦合、可独立替换。 | | 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.js(TS)+ 后端 FastAPI(Python)分离** | 前端 SSR/流式友好;后端 Python 是 LLM 编排/工具的强生态;契约经 OpenAPI→TS 代码生成。 | | ADR-9 | 长任务 | **FastAPI BackgroundTasks + `jobs` 表**(原型不上专门队列) | 学文风/全书扫描走后台任务 + 状态表;写章/审稿走 SSE 即时反馈。规模化再上 arq/Celery。 | | ADR-10 | 编排实现 | **LangGraph(Python)** | 写章→四审→验收天然是跨请求的暂停-恢复(HITL);LangGraph 的并行/中断/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 中断 + checkpoint(ADR-10) | | 数据库 | **PostgreSQL**(无向量) | 关系一库;事务保证验收写回原子性;向量检索为 P2 再引入 | | ORM/迁移 | **SQLAlchemy 2.0(async)+ 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**:单主库起步;读多可加只读副本(看板/列表查询走副本)。 - **横向扩展**:原型单实例即可;后端无状态,规模化可多实例 + 抽独立 Worker(arq/Celery)。 --- ## 3. 数据架构 ← PRODUCT_SPEC §3.2 产品意图:记忆即真相源,每章写前检索注入、验收后增量更新;不可变追加保留历史。 ### 3.1 完整 DDL(11 张创作表 + 运营表) > 类型以 PostgreSQL 表述;`jsonb` 用于结构化但 schema 演进频繁的字段。所有业务表带 `project_id` 外键(原型单用户,多租户行级隔离为后续)。**原型无向量列**——记忆选择走确定性逻辑(§3.4);向量检索为 P2,届时再加 `embedding vector(N)` 列与 HNSW 索引。 > 下面先列 **11 张创作数据表**(对齐 PRODUCT_SPEC §3.2;其中 `timeline`/`decisions` 为 P2,MVP 可不建),再列 **运营/系统表**(用户/凭据/用量/技能/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 可配,默认 3–5)。 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),节点固定、边确定;用其**并行**跑四审、用 **interrupt(HITL)+ 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 读 AgentSpec(builtin/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 文风模仿 - **提取(一次性,分析档)**:3–5 样本(≥5 万字)→ 16 维指纹(9 通用 + 7 中文),**每维附原文证据**,存 `style_fingerprint`(版本化、可增量合并)。 - **生成约束**:写章注入指纹(缓存稳定块)。 - **漂移打分(高频,轻量档)**:每段对照指纹打相似度分;低于阈值标漂移段。 - **回炉**:仅重写漂移段、保留上下文,新旧 diff(UX §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(缓存 + 失效) | | 编辑器本地态(正文/光标/未保存) | 局部 store(Zustand 或 RSC + local) | | 流式增量(draft/review) | SSE 订阅 → 增量 reducer | | 乐观更新(验收/裁决) | 先改 UI、失败回滚 + Toast | ### 8.3 编辑器与流式渲染 - 正文编辑器:宋体 / 720px / 行高 1.9(UX §2.3);contentEditable 或 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.5);SSE 断流可续;正文自动保存兜底;长任务可重试幂等。 - **配置**:`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 引入向量时再开);定时备份 + PITR;Alembic 迁移随发布执行、CI 校验。 - **扩展性/配额**:写章并发受 LLM 限流约束——按作品**速率配额**;网关侧聚合各 provider 限流(429 退避 + 回退)。规模化再抽独立 Worker(arq/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 按依赖关系排架构落地顺序(与产品 M1–M5 对齐): | 里程碑 | 架构交付 | 依赖 | |---|---|---| | **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 运行时 + 规则/命令面板 | M2–M4 | > 网关的多 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 路线(按 P0–P2 落地) | | §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` 列/HNSW;PRODUCT_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 运营表)。