AI小说工厂(二):让AI写出200章不崩的记忆架构

  • ~5.16K 字

AI 写长篇最大的敌人不是文笔,是遗忘。写到第 80 章,主角的枪在第 12 章就丢了但它还在开火;第三章埋的伏笔没人记得收。这篇讲我的记忆架构设计:七字段蓝图卡、滚动窗口、三重记忆、确定性检索——一套让 200 章不崩的工程方案。

问题定义:LLM 的上下文装不下一本书

一本 200 章 × 3000 字的小说是 60 万字,任何模型的上下文都塞不下,塞得下也塞不起(每章都带全书 = 每章的天价 token)。所以长篇生成的本质问题是:第 N 章生成时,上下文里该放什么?

放多了贵且噪声大,放少了前后矛盾。我的答案是一套分层记忆系统,每层负责一个时间尺度:

全书尺度 → 小说架构(雪花四步的产物,永久有效)
弧尺度 → 卷→弧分层大纲 + 弧检查点(长篇专用)
近章尺度 → 全局滚动摘要 + 前章结尾 800 字 + 近 3 章滚动摘要
事实尺度 → 章节事实卡 + 活跃伏笔账本(结构化,非自然语言)
远章尺度 → 确定性检索:把"可能相关的远章片段"按需召回
本章尺度 → 当前章 + 下一章的蓝图卡(保衔接)

蓝图卡:每章的”施工图”

七字段卡是 AI_NovelGenerator 验证过的设计,我把它做成了 LLM 直接输出 JSON(比原版正则解析文本稳一个量级):

章节定位 / 核心作用 / 悬念密度 / 伏笔操作(埋设→强化→回收) / 认知颠覆 / 简述 / 标题

两个关键用法:

  1. 当前章和下一章的卡同时注入。写作模型不只知道自己这章要干什么,还知道下一章要去哪——衔接是”演”出来的不是”凑”出来的
  2. 伏笔操作字段进卡。第 5 章埋、第 9 章强化、第 14 章回收,在蓝图层面就规划好,配合后面讲的事实卡账本形成闭环

蓝图生成的提示词里还有节奏设计的显式约束(每 3-5 章一个悬念单元、单元之间”认知过山车”连续 2 章紧张→1 章缓冲),这是从成熟网文的节奏规律里提炼的。

超过 25 章的项目蓝图不能一次生成——LLM 规划 100 章的节奏分布必然后程崩坏(重复、平淡、烂尾式赶工)。解法是滚动窗口:先只生成前 25 章,写作推进到第 20 章左右时,带着”已完成章的滚动摘要 + 伏笔账本 + 弧目标”生成下一个窗口的蓝图。每个窗口都踩在真实进度上,规划永远新鲜。

三重记忆:写作模型的上下文组装

直接看第 N 章草稿生成的真实组装代码:

def _draft(pid: str, no: int, card: dict, meta: dict, sys_w: str, job) -> str:
words = int(meta.get("words_per_chapter", 2000))
arch = store.get_architecture(pid)["architecture"] or "(无)"
g = prompts.guidance_block(meta.get("guidance", ""))
# 同人/续写项目:注入原著梗概+沿用角色言行锚点(OOC 防护)
# 多世界:按本章蓝图卡的 world 匹配世界源,注入对应世界的包
sb = ""
if meta.get("mode") in ("fanfic", "continue"):
... # 原著 source_pack 注入
kb = _knowledge_block(pid, card)
# 近章事实卡 + 活跃伏笔账本(时间线/状态/伏笔现状注入)
from . import facts as factsmod
recent_facts = factsmod.get_facts(pid)
facts_txt = prompts.facts_block(recent_facts)
if no == 1:
user_p = prompts.FIRST_CHAPTER.format(
card=prompts._card_block(card, 1), architecture=arch[:24000],
guidance=g, words=words, source_block=sb, knowledge_block=kb)
else:
prev = store.get_chapter(pid, no - 1)
prev_ending = (prev.get("text", "") or "")[-800:]
recent_summary = _recent_summary(pid, no, card, job)
retrieved = retrieval.retrieve(pid, no, card)
...

第 1 章特殊处理:注入全量架构(这是全书基调章,值得花 token)。之后每章的注入是:

  • 前章结尾 800 字——不是前章全文!写作最需要的衔接感来自”上一秒发生了什么”,800 字够用,省下的预算给记忆
  • 全局滚动摘要(≤2000 字,定稿时增量更新)——“到目前为止故事讲到了哪”
  • 近 3 章滚动摘要——比全局摘要细一档的短期记忆
  • 角色状态树——每个角色当前的物品/能力/关系,定稿时增量维护
  • 事实卡与活跃伏笔——结构化的事实清单(第三篇细讲)
  • 远章检索片段——按需召回,下面细讲

确定性检索:不上向量库的远章召回

写到第 80 章突然要提第 12 章的旧事,摘要里大概率没有这个粒度。传统解法是向量数据库,但我刻意没上——chromadb + embedding 模型是重依赖,而且中文小说场景里,”相关”往往是实体级的(同一个角色、同一个地点、同一件物品),确定性方案够用:

def retrieve(pid: str, no: int, card: dict, top_k: int = 4) -> str:
"""为第 no 章检索远章相关片段。近 3 章已有滚动摘要覆盖,跳过;
太老的章(全书 1/4 之前)只在命中实体时召回。"""
idx = store.chapters_index(pid)
final_nos = sorted(int(k) for k, v in idx.items() if v.get("status") == "final")
candidates_nos = [n for n in final_nos if n <= no - 3]
if not candidates_nos:
return ""

# 查询要素:出场角色名 + 地点 + 蓝图卡文本二元组
names = [n for n in store.character_names(pid) if n]
card_chars = [c for c in (card.get("chars") or []) if c]
entities = set(card_chars)
entities.add((card.get("location") or "").strip())
entities.discard("")
query_bg = _bigrams(" ".join([str(card.get(k, "")) for k in
("title", "role", "purpose", "foreshadow", "summary")]))

scored = []
for n in candidates_nos:
...
for para in paras:
para_entities = {e for e in entities if e and e in para}
names_hit = {m for m in names if m in para and m in set(card_chars)}
bg = _bigrams(para)
overlap = len(bg & query_bg)
score = 3 * len(para_entities) + 3 * len(names_hit) + min(overlap, 25)
if score >= 6:
scored.append((score, n, title, para))
if not scored:
return ""
scored.sort(key=lambda x: (-x[0], x[1]))

打分公式值得展开:实体命中权重是 bigram 重叠的三倍(3×实体 vs 1×bigram,且 bigram 封顶 25 分)——因为中文二元组(相邻两字组成的词对)召回宽泛,实体(角色名出现在这段里)才是”这段和我这章真的有关”的强信号。阈值 6 分起收,最多召回 4 段,宁缺毋滥。

两个设计纪律写在 docstring 里:近 3 章跳过(已有滚动摘要覆盖,重复注入浪费预算);太老的章只在命中实体时召回(全书 1/4 之前的段落,纯 bigram 重叠大概率是巧合)。

这套方案的代价是语义相关性弱于向量库(”枪”检索不到”武器”),但零依赖、零成本、结果可解释——检索为什么召回这段,你能逐项看分数。接口隔离在 retrieval.retrieve 一个函数里,将来要升级 embedding 就换这一个函数,管线不动。

定稿:记忆的写入时刻

草稿通过审查定稿时,记忆系统反向更新:全局摘要滚动重写(不是追加!追加会让摘要无限膨胀)、角色状态树增量更新、事实卡追加、伏笔账本记账。记忆的读和写是同一个事务的两面——定稿失败的记忆更新会导致后续章节读到脏数据,所以这些更新和定稿状态是原子落盘的。

手工改了正文怎么办?提供了”重新定稿”入口:按改后的正文重跑记忆更新链(摘要/状态/事实卡对齐),记忆页所有数据也全部可在线编辑——文件即数据库的架构下,人工干预永远有后门

踩坑小结

  • 长篇质量 = 记忆工程的完备度。审查发现的”前后矛盾”类问题,九成能追溯到某层记忆缺失或过期,而不是模型能力问题
  • 摘要要滚动重写不要追加。早期版本的全局摘要是逐章追加的,写到 30 章摘要本身膨胀成新的长篇,且前松后紧详略失衡。改成”带着旧摘要重写新摘要”后稳定在 2000 字内
  • 前章只注入结尾 800 字就够了,全书注入是奢侈品思维。省下的预算给检索片段和事实卡,性价比高一个数量级
  • 规划不能一次到位。100 章蓝图一次生成必然后程崩坏,滚动窗口让每个规划决策都建立在真实进度上
  • 向量库不是必需品。实体 + bigram 的确定性检索在中文小说场景覆盖了 90% 的召回需求,先跑起来,接口留好升级缝

小结

这套记忆架构跑下来的真实战绩:一个 300 章规划的科幻同人项目,写到近 50 章时一致性审查的冲突发现率稳定在低位,伏笔账本里 40+ 条活跃伏笔每条都有埋设章号和最近强化章号,随时可查。下一篇讲”编辑部”——记忆防的是遗忘,编辑防的是写得烂。

赞助喵
非常感谢您的喜欢!
赞助喵
分享这一刻
让朋友们也来瞅瞅!