编程 LLM 只当演员,规则引擎当导演:heartmorrow 的架构与取舍

2026-08-27 00:05:49 views 6

LLM 只当演员,规则引擎当导演:heartmorrow 的架构与取舍

拿到一个 LLM 应用,我一般先不看它用什么模型,而是看两件事:状态放在哪里,以及"发生了什么"由谁决定。heartmorrow 就是一个很好的案例。这是 180+ stars 的开源项目(仓库 HMDSimDev/heartmorrow,源码:https://github.com/HMDSimDev/heartmorrow),TypeScript/Node 实现,Unlicense 协议,表面上是恋爱模拟游戏,骨子里是一个本地优先、由规则引擎主导的 LLM 应用。

为什么浏览器不直接连模型

部署形态是这样的:

浏览器
  → 本地 Node 小服务器(持有状态与规则引擎)
      → OpenAI-API 兼容端点:LM Studio / Ollama / llama.cpp / vLLM
      → 原生端点:LM Studio / Ollama / KoboldCpp
      → 云端:Anthropic(带 key)

浏览器不直连模型,这个中间层解决的不只是"封装一下 API",而是几个实打实的工程问题:

  • 角色、存档、图片、API 密钥全部留在本机,密钥不出网。
  • 接本地模型时可以拔网线玩,整条链路无外呼。
  • 换模型、换底座、换量化版本,客户端无感知。
  • 对话状态和游戏状态有唯一归属方(本地服务器),而不是散落在浏览器标签页里。

代价也很明确:使用者得自己起一个模型服务。没有本地 GPU 就只能带 key 走云端;本地 7B 级模型和云端大模型在角色扮演上的效果差距,需要自己实测——具体运行命令我未实测,但仓库走的是标准 Node 部署流程。

这个形态决定了它面向的是能接受本地起服务的开发者,而不是点开即玩的泛大众玩家。

状态是服务器端真实状态

"对话不会丢"在 heartmorrow 里不是浏览器存储的功劳,而是架构直接决定的结果:刷新标签页、切换窗口,对话自动恢复。项目里管这个叫 server-truth——对话的真实状态在服务器端,浏览器只是渲染终端。

LLM 本身是无状态的,所有可持续的状态都放在本地服务器里。游戏内 112 天一年、4 季 × 28 天、每季从周一开始、每天 3 行动点(周末 4)、天气联动角色每日心情——这些全局时钟都由规则引擎持有,模型输出只是这些状态之上的文本投影。

这里的核心决策不是"把状态放后端",而是"状态由什么决定"。heartmorrow 的做法是:规则引擎决定游戏事实,LLM 只负责把这些事实讲出来,不直接改数值。

记账不交给对话流

很多人做 AI 游戏时,让 LLM 在对话里直接给出数值变化。heartmorrow 明确不做这件事,它用了独立、经过校验的结算 pass:

  • 约会结束时,一次性把整晚对话转化为关系数值、心情、记忆。
  • 平庸或伤人的约会不加分;开了约会不说话就不算数。
  • 这套设计叫 save-safe scoring,用来避免 LLM 即时打分导致的数值漂移。

为什么会有漂移?因为模型在对话上下文里做即时判断,受最近几句影响很大,同一句话放在不同上下文里会评出不同分数。而逐句改数值会让关系系统充满噪音、不可复现,最终玩家会觉得"这数值不讲道理"。独立结算 pass 把整晚所有交互放进同一个确定性过程里结算,关系和社交链的变化在同一个事务里生效。

代价是实时关系进度条不会实时跳动,玩家在对话中只能看到文本和表情反馈。这里做了明显取舍:数值稳定性优先于反馈即时性。

同样的思路也体现在意图标签和难度上:

  • Flirt / Tease / Open Up(紧张时 Reassure / Apologize)只影响这句话"落地的感觉",本身不动数值。误读会适得其反,但不会污染数据。
  • Gentle / Normal / Harsh 只缩放后果的幅度,不改变角色台词和约会开场。难度被做成纯数值系数,和文本内容解耦。

这三个设计合在一起是对齐的:把模型限制在"生成内容"这个环里,把记账留给确定性代码。对比纯 prompt 聊天,heartmorrow 的世界状态变成了游戏事实,AI 在给这个事实配音,而不是在对话中现编一个事实。这正是它像"游戏"而不像"聊天窗"的原因。

关系的所有权被拆开

关系推进上有一个很有意思的划分:玩家是发起方,角色是裁决方。

玩家主动推进关系阶段(约会 → 专一 → 同居),但角色自己决定接不接受,时机不对会反效果。分手留疤,可以复合但每次分手都留疤。这意味着玩家有操作权,而没有决定权。

这套系统能成立,依赖的是世界记忆和社交模拟:约会记忆滚成关系编年史、角色会引用过往对话、社交图(朋友、对手、前任、家人、伴侣)让 NPC 互相认识、八卦会传、伴侣会发现出轨。没有这些,角色自主决定就是随机数;有了这些,角色的拒绝或接受才是有依据的判定。

这同时也划清了它的适用边界。想要强剧情、要求每一段叙事都完全可控的人,会被这个系统直接顶回来——角色的决策不完全由玩家控制,剧情不可控是这个项目的默认属性。更适合它的是把"关系涌现"当乐趣的人。无固定结局(同居 + 恋人 + 平稳后可解锁 epilogue,但仍可继续玩)也在强化这一点:它更像一个仍在运行的系统,而不是一本已经写完的小说。

AI 面是可裁剪的

资源消耗可以被显式配置。经济系统(财产、股市、赌场)默认全关,Mail / Faces 这类 LLM 生成功能也可以关闭以省 token。

更重要的一点:没有模型也能玩。装好后不连 LLM 也能建角色、上传立绘、写世界设定、逛完整 UI,只有真正对话和世界模拟才需要模型。这让 UI、规则引擎和生成面可以分开测试,也让项目的 CI 和本地试错成本很低。

记录一下

我不想给这个项目下"推荐"或"不推荐"的结论。它做了几个很清楚的取舍:

  • LLM 在最后一位被调用,而不是所有游戏逻辑的起点;
  • 数值变化来自确定性结算 pass,不来自对话流;
  • 状态是服务器端的真实文件,不是浏览器里的临时内存;
  • 玩家负责发起,规则引擎负责裁决,模型只负责演出。

这些模式放到任何 LLM 驱动的内容型应用里,都值得借鉴。这也是我写这篇文章的原因。

项目地址:https://github.com/HMDSimDev/heartmorrow

复制全文 生成海报 LLM 架构 开源 Node.js 本地部署 AI应用

推荐文章

程序员茄子在线接单