生产级长篇小说生成引擎。基于强指令驱动 + 全并发生产架构,将 LLM 协作创作从"自由漫游"升级为"方法派剧场"。
传统 AI 小说生成要么靠蛮力堆上下文,要么靠静态模板框死创作空间。StoryRunner 的答案是第三条路:
把 LLM 当演员,不当作者。
系统不要求模型"自由发挥",而是扮演一个严格的剧场:剧本分析师事先把每章的关键事件排列好(称为"节拍/Beat"),导演按节拍向演员下达明确指令("愤怒地质问她为何偷走羊皮纸"),演员只负责用符合人设的语气、台词和微表情完美演绎。结果是:逻辑不会跑偏,但风格和细节仍然涌现。
核心特性:
- L2 分析师会议室:各卷分析师通过 P2P 通讯协作规划章节衔接,用户可实时干预讨论。
- 工业级 L5 管线:采用 Map-Reduce 架构,支持磁盘流式 I/O,在大模型长篇创作中实现“生肉日志”到“成品散文”的高效转换。
┌─────────────────────────────────────────────────────────────────────┐
│ Step 1: Outline Architect │
│ ┌─────────────────────┬───────────────────────────────────┐ │
│ │ Chat Panel │ JSON Preview │ │
│ │ 用户与 Architect │ 实时显示 currentDraftState │ │
│ │ 聊天确定大纲 │ (通过 update_outline 工具) │ │
│ └─────────────────────┴───────────────────────────────────┘ │
│ [Confirm Outline & Proceed] │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ Step 2: Chapter Planning Room (AnalystRoom) │
│ ┌─────────────┬───────────────────────┬─────────────────────┐ │
│ │ Analysts │ P2P Message Stream │ Intervene │ │
│ │ ● Vol_1 ✅ │ [Architect]→[Vol_1] │ Targets: □□□ │ │
│ │ ○ Vol_2 │ [Vol_1]→[Vol_2] │ Message: ___ │ │
│ │ ● Vol_3 ✅ │ [User]→[Vol_2,Vol_3] │ [Send] │ │
│ │ │ │ │ │
│ │ [Architect] │ │ Submitted Plans: │ │
│ │ (Supervisor)│ │ Vol_1: 10 chapters │ │
│ └─────────────┴───────────────────────┴─────────────────────┘ │
│ [Start Planning] [Proceed to Writing] (解锁后可用) │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ Step 3: Parallel Generation Studio (Kanban) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Vol 1 │ │ Vol 2 │ │ Vol 3 │ │
│ │ ─────────── │ │ ─────────── │ │ ─────────── │ │
│ │ Ch 1 [Raw]✅│ │ Ch 1 [Raw]⏳│ │ Ch 1 [Raw]⏸️ │ │
│ │ Ch 2 [Raw]⏳│ │ │ │ │ │
│ └─────────────┘ └─────────────┘ └─────────────┘ │
│ [Global Logs] [Actor Performances] -> [Output: Raw Transcripts] │
└─────────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────────┐
│ Step 4: Industrial Rendering Pipeline (L5 Polisher) │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ [Map] Render Drafts (Parallel) | ch1_draft, ch2_draft... │ │
│ ├─────────────────────────────────────────────────────────────┤ │
│ │ [Reduce] Stitch Volume (Rolling) | vol_1_full.md │ │
│ ├─────────────────────────────────────────────────────────────┤ │
│ │ [Finalize] Global Stitching | story_final.md │ │
│ └─────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
用户输入
│
▼
┌─────────────────────────────────────────────┐
│ L1 Architect(大纲规划师) │ 单次,CRITIC 模型
│ 输出:WorldSetting + StoryOutline │
│ 创建 Volume Analysts 并发送开场指令 │
└────────────────┬────────────────────────────┘
│ P2P 协作讨论
▼
┌─────────────────────────────────────────────┐
│ L2 AnalystRoom(分析师会议室) │ 持久化对话,CRITIC 模型
│ Volume Analysts 通过 P2P 讨论卷间衔接 │
│ 输出:VolumeChapterPlan(各卷章节规划) │
└────────────────┬────────────────────────────┘
│ approve_all 后按卷并发 spawn
▼
┌─────────────────────────────────────────────┐
│ L3 Director(导演) │ SCHEDULER 模型
│ 根据章节规划生成 Beats,向 Actor 下达指令 │
│ │ │
│ ▼ │
│ L4 Actor × M(角色演员) │ ACTOR 模型,含重试校验
│ 输出:dialogueAndAction + 状态更新 │
└────────────────┬────────────────────────────┘
│ 并发 Map → 串行 Reduce
▼
┌─────────────────────────────────────────────┐
│ L5 MapReducePolisher(渲染管线) │ ACTOR/CRITIC 模型
│ Map:极速并行,剧本转散文初稿 (_draft.md) │
│ Reduce:滚动熔接,磁盘流式处理章节缝隙 │
│ Finalize:跨卷装帧,产出最终全本书籍 │
└─────────────────────────────────────────────┘
- Node.js 18+
- pnpm
pnpm install在项目根目录创建 .env(或直接修改 src/utils/llm.ts):
# 调度层(导演决策,推荐快速模型)
SCHEDULER_API_KEY=your-key
SCHEDULER_BASE_URL=https://api.deepseek.com
SCHEDULER_MODEL=deepseek-chat
# 表演层(角色生成,推荐富有表现力的模型)
ACTOR_API_KEY=your-key
ACTOR_BASE_URL=https://api.deepseek.com
ACTOR_MODEL=deepseek-chat
# 规划层(架构/分析,推荐逻辑最强的模型)
CRITIC_API_KEY=your-key
CRITIC_BASE_URL=https://api.deepseek.com
CRITIC_MODEL=deepseek-chat三个 tier 可以指向不同的 API 提供商和模型,以在成本和质量之间取得最优平衡。
pnpm start
# 服务运行在 http://localhost:3000# 列出指定故事的所有卷规划
pnpm tsx src/index.ts list <storyName>
# 为指定故事生成特定章节的生肉日志
pnpm tsx src/index.ts generate <storyName> <volId> <chapIndex>src/core/macro/macro.ts · Architect 类
接收用户的模糊创作需求,产出两份结构化数据:
WorldSetting:物理规则、魔法体系、势力格局、地理环境StoryOutline:卷数、各卷核心冲突、全角色档案(含身世和私有动机)
在 Step 2 中,Architect 还负责:
- 创建所有 Volume Analysts
- 发送开场指令指派任务
- 监督分析师讨论并最终 approve_all
使用 CRITIC 模型(最强逻辑)执行,确保世界观自洽。
src/core/meso/analyst_room.ts · AnalystRoom 类
核心创新:各卷 Analyst 通过 P2P 通讯讨论卷间衔接,而非闭门造车。
| 工具 | 用途 |
|---|---|
create_analysts() |
Architect 创建所有 Volume Analysts |
send_message(target, msg) |
P2P 点对点通讯 |
submit_chapter_plan(plan) |
Analyst 提交章节规划(含 Beat 级别数据) |
inject_user_message() |
用户在 Step 2 期间向分析师直接下达指令 |
approve_all() |
Architect 批准所有规划,解锁生成流程 |
Analyst 生命周期:
- 对话历史在整个规划阶段持久化(不销毁)
- 提交后进入
finished挂起态 - 收到新消息自动
unfinish,重新处理
输出契约:VolumeChapterPlan
interface VolumeChapterPlan {
volumeId: number;
volumeTitle: string;
chapters: Array<{
chapterIndex: number;
title: string;
summary: string;
keyEvents: string[];
transitionNote?: string; // 与下一章衔接
}>;
transitionToNext?: {
targetVolumeId: number;
setupRequired: string; // 卷尾铺垫
};
}src/core/micro/micro.ts · MicroInference.executeChapter()
拿到 VolumeChapterPlan 后,解析其中的 ChapterPackage(含 Beats 和记忆快照),驱动章节演进:
- 根据章节规划生成 3–6 个有序节拍
- 识别当前 Beat 涉及的角色
- 组装导演指令文本(包含 Beat 内容 + 角色当前叙事状态)
- 向 Actor 发出强指令,等待表演结果
- 更新叙事状态快照,推进到下一 Beat
src/core/micro/micro.ts · MicroInference.executeActorBeat()
每次被唤醒时通过 AgentFactory.spawn() 动态创建,注入严格的四模块 System Prompt:
[人物设定和身世] ← 决定腔调与道德底线
[渐进式记忆总结] ← 决定当前情绪与反应
[写作规范] ← 决定文笔风格
[当前叙事状态] ← 描述性快照,替代数值血量
Actor 没有改变大剧情走向的权力,但拥有台词权、表情权、细节描写权。其表演结果被实时记录到 Raw Transcript (生肉日志) 中。
输出格式固定为:
{
"dialogueAndAction": "完整台词与动作叙述",
"narrativeStateUpdate": {
"newPosture": "执行后的位置/姿态",
"statusChange": "身体/情绪状态变化"
}
}指令跟随校验:系统提取 Beat 描述中的关键词,验证 Actor 输出是否确实执行了导演指令。若不符,自动附加高权重警告后重试(最多 2 次);重试仍失败则使用降级输出。
src/core/render/polisher.ts · MapReducePolisher 类
这是 StoryRunner V2 的“工业化”核心,负责将生硬的角色表演日志转化为优美的小说正文。
阶段 A:Map — 并发初稿生成
- 操作:每章独立调用 ACTOR 模型进行渲染。
- 职责:去除剧本标签(如
**角色名**:),将括号内的动作描述与台词融合为自然散文,补充感官细节描写。 - 输出:每章对应的
_draft.md(初稿)。
阶段 B:Reduce — 滚动累积拼接(Rolling Stitch)
- 操作:串行滑动窗口处理。每次读取第 N 章末尾和第 N+1 章开头(约 200–500 字)。
- 职责:调用 CRITIC/ACTOR 模型生成“熔接过渡段(Joint)”,消除场景跳跃感和逻辑断层。
- 技术细节:采用磁盘流式 I/O,通过
truncate和append动态改写文件,彻底杜绝超长篇(百万字级)内存溢出风险。 - 输出:卷级全本
vol_X_full.md。
阶段 C:Finalize — 全书装帧
- 操作:跨卷层面的最终熔接。
- 职责:处理卷与卷之间的大尺度时间/空间跳越,确保全书语调统一。
- 输出:最终定稿
story_full.md。
src/engine/factory/agent_factory.ts
所有 Agent 遵循严格的 按需 Spawn → 注入纯净上下文 → 执行单一任务 → 销毁 生命周期,根除记忆污染和注意力衰减:
const actor = AgentFactory.spawn({
role: 'Actor',
modelTier: 'ACTOR',
characterName: '亚瑟',
personaAndBackground: '落魄的流亡骑士,极度珍视荣誉...',
memorySync: '【卷一】...【上一章】莉莉亚偷走羊皮纸,你重伤逃脱...',
writingGuidelines: '多写心理活动,禁用现代词汇',
narrativeState: '左臂刀伤渗血,右手握铁剑,情绪处于暴怒边缘'
});
const result = await actor.act(directorInstruction);
actor.destroy(); // 用完即弃例外:L2 AnalystRoom 中的 Volume Analysts 在规划阶段保持持久化对话历史。
src/engine/bus/message_bus.ts
基于 Node.js EventEmitter 的 Pub/Sub 系统,支持:
- Topic 订阅:旁听某章节频道内的所有消息(Eavesdropping)
- 定向投递:发送给指定的 Agent ID
- 广播:向频道内所有 Agent 广播
- Topic 清理:章节结束时一键销毁所有相关监听
src/engine/memory/factory.ts
双轨记忆系统:
- 短时记忆:最近 5 个场景/章节摘要,维持叙事语境
- 硬性状态:
<主体, 动作, 客体, 有效期>四元组,支持三种有效期:always/chapter_count: N/until_arc_end
章节推进时自动清理过期事实,并同步更新角色状态对象,防止长篇叙事中的逻辑不一致。
src/utils/storage.ts
V2 引入了目录式持久化架构,所有数据按项目隔离,支持断点续传:
storage/[项目名]/
├── outline.json # L1 大纲 + 角色 + 世界规则
├── plans/
│ ├── vol_1.json # L2 分析师产出的卷规划 (含 ChapterPackage)
│ └── vol_2.json
└── chapters/
├── vol_1_chap_1_raw.txt # L3/L4 原始剧场日志
├── vol_1_chap_1_draft.md # L5 Map 产出的初稿
├── vol_1_full.md # L5 Reduce 缝合后的卷全本
└── story_full.md # L5 Finalize 全书最终稿
| Tier | 默认模型 | 使用场景 |
|---|---|---|
CRITIC |
deepseek-chat | 大纲规划、章节分析、AnalystRoom、事实提取、接缝修复 |
ACTOR |
deepseek-chat | 角色表演、场景润色 |
SCHEDULER |
deepseek-chat | 导演决策、章节编排 |
三个 Tier 可以独立配置不同的 API Key、Base URL 和模型名,自由组合如 Claude(表演)+ DeepSeek-R1(规划)+ Gemini Flash(调度)。
服务运行在 http://localhost:3000,前端静态文件来自 /public/。
| 方法 | 路径 | 说明 |
|---|---|---|
POST |
/api/story/chat-draft |
用户与 Architect 聊天,修改大纲 |
GET |
/api/story/draft-state |
获取当前大纲 JSON |
| 方法 | 路径 | 说明 |
|---|---|---|
POST |
/api/story/start-planning |
启动 AnalystRoom,创建 Volume Analysts |
GET |
/api/story/analyst-room-status |
获取分析师状态和消息历史 |
POST |
/api/story/inject |
用户注入消息到 P2P 总线 |
| 方法 | 路径 | 说明 |
|---|---|---|
POST |
/api/story/confirm |
确认计划,初始化 StoryState 并创建目录 |
POST |
/api/story/generate-chapter |
生成指定章节的 Raw Transcript(可附 godInstruction) |
POST |
/api/story/stop |
立即中止当前章节生成 |
| 方法 | 路径 | 说明 |
|---|---|---|
POST |
/api/story/render-chapter-draft |
[Map] 将生肉剧格转化为散文初稿 |
POST |
/api/story/stitch-volume |
[Reduce] 滚动缝合全卷章节,产出 vol_full.md |
POST |
/api/story/stitch-story |
[Finalize] 跨卷装帧,产出 story_full.md |
| 方法 | 路径 | 说明 |
|---|---|---|
GET |
/api/story/stream |
SSE 实时事件流 |
GET |
/api/story/state |
获取当前完整故事状态 |
在 /api/story/generate-chapter 请求中附加 godInstruction 字段,可在当前章节级别强制覆盖叙事流向:
{
"godInstruction": "这一章必须让主角与反派达成临时同盟"
}| 事件类型 | 触发时机 |
|---|---|
P2P_MESSAGE |
P2P 消息(sender → receiver) |
ANALYST_CREATED |
分析师被创建 |
ANALYST_FINISHED |
分析师提交规划 |
ANALYST_UNFINISHED |
分析师被解挂(收到新消息) |
ALL_ANALYSTS_FINISHED |
所有分析师完成 |
ARCHITECT_APPROVED |
架构师批准所有规划 |
| 事件类型 | 触发时机 |
|---|---|
SCENE_START |
场景开始 |
ACTOR_ACTION |
角色完成表演 |
BEAT_COMPLETE |
单个 Beat 执行完毕 |
INSTRUCTION_MISMATCH |
角色抗命,触发重试 |
GENERATION_ABORTED |
用户中止生成 |
| 事件类型 | 触发时机 |
|---|---|
DRAFT_START |
开始生成单章初稿 |
DRAFT_COMPLETE |
单章初稿生成完成 |
STITCH_PROGRESS |
卷内滚动缝合进度更新 |
VOLUME_STITCH_COMPLETE |
全卷缝合完成 |
STITCH_STORY_PROGRESS |
全书缝合进度更新 |
STITCH_STORY_COMPLETE |
全书装帧完成 |
src/
├── core/
│ ├── macro/macro.ts # L1 Architect
│ ├── meso/
│ │ └── analyst_room.ts # L2 AnalystRoom(P2P 协作规划)
│ ├── micro/micro.ts # L3 Director + L4 Actor + 指令校验
│ └── render/
│ └── polisher.ts # L5 Map-Reduce-Finalize 渲染管线
├── engine/
│ ├── bus/message_bus.ts # Pub/Sub 消息总线
│ ├── factory/agent_factory.ts # Agent 工厂(四模块 Prompt 组装)
│ ├── feedback/feedback_loop.ts# 向上反馈循环
│ ├── memory/factory.ts # FACTTRACK 双轨记忆
│ ├── tools/outline_tools.ts # 大纲修改工具定义
│ └── prompting/prompt_engine.ts # 提示词组装引擎
├── types/index.ts # 全局类型定义
├── utils/
│ ├── llm.ts # LLM 调用层(三 Tier + 重试 + 工具调用)
│ ├── logger.ts # NarrativeLogger(SSE 事件总线)
│ └── storage.ts # StateStore(目录式持久化)
├── server.ts # Express Web 服务器
└── index.ts # CLI 入口
MIT