完整的全栈项目案例:集成多个LLM供应商、RAG向量检索、有状态的记忆管理和分支叙事逻辑,展示现代AI应用的架构设计。
小时候,我特别爱看那种篇幅短小、节奏明快、充满分支的恐怖读物——《鸡皮疙瘩》时代那类“选择你自己的噩梦”故事,结局取决于你翻到了哪一页。多年以后,做了将近十年的全栈工程师,我开始思考一个问题。
AI 能不能生成一部真正记得一切的互动小说?
不是那种预先写好分支的“选择你自己的冒险”,而是一个活着的故事——它记得你五十页前做过什么,会怀恨在心,并等待合适的时机给你致命一击。
这个问题催生了 Twistloom。一个最初源于好奇心的项目,最终变成了一套分布式 AI 编排系统:横跨 9 家 LLM Provider,采用基于 RAG 的语义记忆,并拥有一个拒绝遗忘的结构化故事引擎。

通常,人们想象中的 AI 小说创作是这样的:
Write me a horror story...
但在构建 Twistloom 之后,我意识到,最难的部分从来不是生成文本。LLM 在这方面的能力强得惊人。真正的难题,是在数百页的篇幅中始终保持一致,同时每位读者还在不断改变故事的发展方向。
一个角色不应该突然性情大变。一把丢失的钥匙不应该凭空重新出现。五十页前已经死去的人,不应该若无其事地走回场景。生成的每一页都必须理解此前发生的一切——事实证明,这个问题远比生成文本本身更有意思。
对读者来说,Twistloom 看上去简单得有些迷惑性。
你只需要输入一个故事构想:
一名侦探身处一座永远下着雨的城市,开始收到来自尚未死去的受害者的信。
几分钟内,一部拥有完整分支的心理惊悚小说便会出现。每一页结尾都有选项——由 AI 生成,并根据角色当前的处境量身定制。选择其中一个,整个世界就会作出回应。
有时,AI 甚至根本不会提供你想选的行动。这时你可以自己输入——任何自定义行动都可以。系统会检查它是否合理,为它评分,然后照样生成对应的页面。你想撬开那扇锁着的门?系统会写出你这么做之后发生的事情。
故事共有三种形态:
最后一种往往让人一开始难以置信。在 Multiverse Mode 中,两名作出完全相同选择的读者,最终仍可能经历不同的故事。系统会持续在后台生成候选分支,因此剧透实际上已经失效。不会有两次完全相同的游玩过程。
你的选择会在整个故事中不断回响:角色会记得你的善意与背叛;你的理智值会随着紧张程度升降;被你错过的线索会继续深埋;最终结局会根据你真正做过的事情调整,而不是遵循作者预先设计的路线。
使用 LLM 写作有一个问题:一次文本生成调用,除了你塞进 context window 的内容之外,对其他一切都毫无记忆。如果想创作一部长达 300 页、情节连贯的分支小说,你不可能每次都把整本书粘贴进去。

因此,Twistloom 根本不会把故事视为扁平文本。每个页面都携带一份结构化的叙事状态快照。在幕后,后端会追踪:
角色心理、原型与稳定程度
人际关系与互动历史
已发现的线索与尚未解开的谜团
地点、位置及其氛围
事实历史——每个实体持久化的事实演变
世界时钟、情绪、天气、日期与时间
场景动势与叙事线索
隐藏状态——真相层级、威胁距离、现实稳定性以及计划中的结局
AI 在创作下一页时,拿到的不只是上一段文字,而是整个世界的状态。于是,一致性不再只是一种期望,而成为了一种数据结构。
而且,需要维护的状态非常多。为了加快状态重建,我构建了一套 delta + checkpoint 混合架构:每隔 5 页持久化一次快照,中间的一切则以增量 delta 的形式存储。与从头重放每一页相比,重建故事状态的速度提升了大约 90%。
即使有了结构化状态,容量仍然存在上限。角色背景、世界观设定、三十章前发现的线索——你不可能每次都把所有这些内容发送给模型。
这正是 Retrieval-Augmented Generation 发挥作用的地方。
Twistloom 使用 Jina AI embeddings 对叙事记忆进行 embedding,并借助 pgvector 将其直接存储在 PostgreSQL 中。系统不会向模型重放整部小说,而是在生成时只检索与当前场景最相关的记忆。
我把语义记忆拆分到五张专用的 embedding 表中,每条向量均为 1024 维,并通过 HNSW 建立索引,以实现快速 ANN 搜索:
page_embeddings — the narrative prose itself
character_embeddings — who everyone is, and how they feel
place_embeddings — where scenes happen
future_note_embeddings — deferred payoffs scheduled by the world clock
clue_embeddings — the trail of breadcrumbs readers might miss
准备创作一个页面时,引擎会对当前上下文进行 embedding,查询这些表中距离最近的记忆,并且只把相关片段注入 prompt。每个页面持久化后,系统会以 fire-and-forget 的方式插入 embeddings——这个过程绝不会发生在 delta chain 重放期间,因此语义记忆不会拖慢状态重建。
最终得到的是一个故事引擎:它能记住远超任何 context window 容量的信息,同时不会让 prompt 的体积失控。
大多数 AI 应用都依赖单一 Provider。这确实可行——直到它突然失效。
限流。区域性故障。模型下线。临时错误。对于一位正读到场景中间、等待下一页出现的读者来说,Provider 故障不只是一条错误日志——它会毁掉整段阅读体验。
因此,Twistloom 将所有 Provider 都抽象到一个通用接口之后,并以瀑布式策略编排九家不同的 LLM Provider:
gemini → github → cohere → mistral → groq → cerebras → nvidia → openrouter → cloudflare
如果某家 Provider 遭遇限流、性能下降或服务中断,请求就会自动落到下一家。业务逻辑完全不关心页面由哪家 Provider 生成——各个 Provider 只是同一份契约的可互换实现。无论任何一家供应商发生什么,读者的体验都能继续。
有一个认知影响了整个系统的设计:弹性不是开发到最后再外挂上去的一项功能,而是第一天定义 Provider 接口时就必须作出的架构决策。如果代码直接绑定某个 SDK,那么以后接入第二家 Provider 就意味着重写。要是代码面向契约编写,那就只需要修改配置。
我很快还学到了另一件事:LLM 有时能写出出人意料的好故事——有时也会生成彻头彻尾的胡言乱语。
系统不会直接信任单次响应,每次生成都要经过一条包含两次调用的 pipeline:
第一次调用负责生成页面、行动选项和状态更新。
第二次调用使用不同的模型与不同的 prompt,充当评估裁判。在内容到达读者面前之前,它会从质量、连贯性和正确性等方面进行评分。

除此之外,每个页面都会运行 canon validation(runCanonValidationPass()):引擎会检查时间线矛盾、角色知识越界、既有事实冲突以及性格不一致。如果某个页面破坏了 canon,系统会标记并重写它——问题会在生成时解决,而不是等到三章之后才被愤怒的读者发现。
自定义行动拥有自己的一套三道关卡 pipeline:
Gate 0(客户端):通过 regex 安全模式防范 prompt injection。
Gate 1(AI):进行合理性与剧情推进评分——这个行动能否推动故事继续发展?
Gate 2(生成):生成行动结果页面,并执行完整的 canon validation。
分支契约是在数据库层强制执行的,并非仅靠约定。Novel mode 每页允许一个行动和一个目标页面。Interactive 允许 2~6 个行动。Multiverse 允许 2~6 个行动以及不限数量的目标页面。后端会拒绝一切违反模式契约的内容,因此无论模型输出什么,故事图都能保持结构有效。
由于生成成本很高,书籍采用异步方式创建——GitHub Actions worker 负责繁重的生成工作,而你可以通过轮询实时查看进度。你可以启动一本书的生成,等它完成后再回来。
整套系统运行在我梦想中的 JAMstack 上:
Next.js 16(App Router、ISR、Server Components)+ React 19
全栈使用 TypeScript
Tailwind CSS v4 配合由紧张程度驱动的主题——随着故事越来越紧张,UI 的颜色真的会逐渐渗出更深的暗色
使用 next-intl 完整支持英语和印度尼西亚语 locale
TanStack Query 管理服务端状态,Zustand 管理客户端状态
NextAuth v5 提供基于 cookie 的 JWT 身份认证,TipTap 用作管理后台的富文本编辑器
Hono——速度极快且与 runtime 无关的 HTTP framework
Drizzle ORM 提供类型安全的 SQL
Neon PostgreSQL(serverless)配合 pgvector 实现语义记忆
GitHub Actions 作为异步生成 runner
Stripe 用于积分经济系统,ImageKit 用于媒体处理,Resend 用于邮件
9 家 LLM Provider,支持自动 fallback
Jina AI embeddings + pgvector RAG pipeline
结构化 JSON 生成、两次调用评估以及 canon validation
一切都部署在 Vercel serverless 上。公开页面采用 ISR 缓存(60~300 秒),阅读器页面也会缓存以实现快速加载;生成流程的 UX 经过专门设计,用户点击后 modal 会立即打开——甚至早于第一个网络请求完成——随着故事一块块组装起来,生成进度也会持续呈现。
刚开始时,我以为自己只是在构建一个分支恐怖故事生成器。
独自开发几个月之后,我才意识到,自己正在打造的东西远比想象中更大:一个 AI-native 互动小说平台。它不是文本生成器,而是一个让读者体验鲜活故事的平台;未来,作家也能与 AI 一起坐下来,把它当成创作伙伴,而不是自己的替代品。
我的长期愿景不是批量生产 AI 垃圾内容,而是为真正的作者提供一位 co-pilot,它能够:
理解他们笔下的角色
提出创意,同时不夺走作者手中的笔
AI 应该与你一起写作——永远不该替你写作。这是我最在意的区别。
目前,这个平台可以免费使用,因为我使用的是非商业用途的免费层 LLM API key,暂时还无法合法商业化。积分经济、订阅和游戏化系统都已经构建完毕,随时可用——只需修改一项配置,就能开启商业化。
这个项目让我领悟到了一件意料之外的事。
AI 故事创作中最难的问题不是 prompting,而是软件工程:构建具有弹性的编排系统,管理长期记忆,设计结构化 pipeline,维持生成世界内部的一致性,以及作为一名独立开发者,对这一切进行测试、优化并最终发布。
生成文本很容易。
生成一个可信的世界,要难得多得多。
如果你读到了这里,我真的很想听听你的反馈——无论你关注的是 AI 工程、互动小说,还是单纯喜欢构建不同寻常的东西。如果你想亲自感受一下 AI-native 惊悚故事究竟是什么体验,那就走进这台织机:
👉 https://twistloom-web.vercel.app
故事会记住一切。每个选择都会留下伤疤。🩸
如需采取进一步行动,你可以考虑屏蔽此人和/或举报滥用行为。