# ChatLuna livingmemory 记忆池别名器 · 安装与使用指南 > 让**同一个角色的多个预设**(私聊版 / 群聊版 / 语音版 / 旧版…)**共享同一个 livingmemory 记忆池**—— > 私聊里聊过的事,群聊里也记得。 > 做法是运行时给 livingmemory 的 presetId 套一层「别名映射」,**不改 livingmemory 任何文件**,停用即还原。 --- ## ⚠️ 先读这条:关于插件名 这个插件的 npm 名沿用了旧名 **`koishi-plugin-chatluna-scene-rules`**(它早期是个「场合判定」插件), 所以你在 koishi 控制台 / `koishi.yml` 里看到的配置块键也叫 **`chatluna-scene-rules`**。 **但它现在的功能已经完全是「livingmemory 记忆池别名器」**,和「场合判定 / `{scene_rules()}`」毫无关系。 本指南讲的就是这个别名器。名字看着别扭没关系,功能对就行;实在想改名见 §10。 --- ## 0. 它解决什么问题 `chatluna-livingmemory`(向量长期记忆插件)默认**按 presetId 给每个预设单独开一个记忆池**。 角色预设的 presetId 形如 `名字(Character)`(注意是**全角括号**,见 §3)。 于是会出现这种情况: ``` 你把「髭切」拆成两份预设方便分别调教: · 髭切-私聊版(Character) → 记忆池 A · 髭切-群聊版(Character) → 记忆池 B ← 和 A 互不相通 结果:私聊里告诉它的事,群聊里它完全不记得(反之亦然)。 ``` 本插件把这些 presetId **都映射到同一个「共享池 key」**: ``` 髭切-私聊版(Character) ┐ 髭切-群聊版(Character) ├─→ 髭切-通用版(Character) ← 同一个池,记忆打通 髭切-语音版(Character) ┘ ``` > 这是「**多份独立预设(各自调教) + 一个共享记忆池**」的组合:预设各管各的人设/规则, > 记忆是公共的。比「干脆只用一份预设」更灵活,又不丢记忆连续性。 它**不修改 chatluna-livingmemory 的任何文件**,而是在运行时接管它的方法、套一层映射; 插件一停用就自动把方法换回原样,零残留。 --- ## 1. 前置依赖(必须先装好并能跑通) 1. **Koishi** `^4.17`。 2. **ChatLuna**(`koishi-plugin-chatluna`)— 核心。 3. **chatluna-character**(角色对话 / 预设系统)— 跑你的角色预设。 (它不是本插件的 service 硬依赖,但你要做的是「角色 bot 共享记忆」,自然需要它。) 4. **chatluna-livingmemory**(`koishi-plugin-chatluna-livingmemory`)— **本插件真正的硬依赖**, 它提供 `chatluna_living_memory` 服务(记忆池就是它在管)。 - 必须**先让它单独跑通**:即「单个预设能正常记住对话」。本插件只是在它之上做「池的归并」, 它本身坏的话别名器也救不了。 - 它自己依赖的 embeddings / 向量库(如 `ollama/bge-m3`)是 livingmemory 的配置项,**与本插件无关**, 按 livingmemory 的文档配好即可。 > 一句话:**先有一个「记忆正常工作」的 livingmemory**,再上本插件做「多预设共享」。 --- ## 2. 安装本插件 把发布包里的 `koishi-plugin-chatluna-scene-rules/` **整个目录**放到你的 koishi app 根目录下的 `external/`: ``` <你的koishi-app>/ ├── koishi.yml ├── package.json └── external/ └── koishi-plugin-chatluna-scene-rules/ ├── index.js ├── package.json └── README.md ``` 让 koishi 能加载它,二选一: **方式 A · 本地 file 依赖(推荐)** ```bash # 在 koishi app 根目录 npm i ./external/koishi-plugin-chatluna-scene-rules # 或 yarn: yarn add koishi-plugin-chatluna-scene-rules@file:./external/koishi-plugin-chatluna-scene-rules ``` **方式 B · 手动软链(不想动 lockfile 时)** ```bash cd <你的koishi-app>/node_modules ln -sfn ../external/koishi-plugin-chatluna-scene-rules koishi-plugin-chatluna-scene-rules ``` 装好后,控制台「已安装」里能看到 `chatluna-scene-rules`。 --- ## 3. 关键概念:presetId 与共享池 key(务必先懂) ### presetId 是什么 livingmemory 用 **presetId** 区分记忆池。对**角色预设**,presetId = 预设名加一个固定后缀: ``` 预设名 + (Character) ← 后缀是全角括号,例:髭切-私聊版(Character) ``` > 这个后缀在 livingmemory 源码里写死为全角的 `(Character)`。所以你的别名映射**键**也必须用全角括号, > **半角 `(Character)` 不会命中**。这是最常见的踩坑点,务必用下面 §4.1 的 `debug` 抄准确字符串。 ### 共享池 key 是什么 就是你**自己指定的一个字符串**,当作「公共池的名字」。所有要共享记忆的预设,都映射到它。 - 它**不需要真的存在一个同名预设**——它只是个池名(key)。 - 习惯做法:用一个「XX-通用版(Character)」之类的名字当共享 key(可以是一个退役/专设的预设名, 那个池里就是合并后的公共记忆)。 - 同一个角色的几份预设映射到**同一个** key;不同角色用**不同** key,别串。 --- ## 4. 配置 koishi.yml(以「髭切」为例) 在 `koishi.yml` 的 `plugins:` 下加一个实例(实例名随意,这里用 `main`,**两个空格缩进**): ```yaml chatluna-scene-rules:main: debug: true # ★ 首次务必开,日志里抄准确的 presetId aliases: 髭切-本丸语音版(Character): 髭切-通用版(Character) 髭切-私聊版(Character): 髭切-通用版(Character) 髭切(Character): 髭切-通用版(Character) 髭切-本丸版(Character): 髭切-通用版(Character) ``` - **左边(键)** = 各预设的 presetId(全角括号);**右边(值)** = 共享池 key(你自定,这里用 `髭切-通用版(Character)`)。 - 命中的几份预设,记忆全部读写 `髭切-通用版(Character)` 这**一个**池。 - 没列进来的预设不受影响,照旧用自己的独立池。 也可以不写 yaml,直接在 koishi **控制台 → 该插件配置页**填 `aliases` 字典,效果一样。 ### 4.1 怎么拿到准确的 presetId(关键步骤,别跳) 别名键差一个字符(尤其全角/半角括号)就不命中,所以**先用 debug 抄,再填**: 1. 先随便填(甚至先留空),把 `debug: true`,**重启 koishi**。 2. 用这个角色**触发一次对话**。 3. 看 koishi 日志里来源是 `chatluna-scene-rules` 的行: ``` chatluna-scene-rules presetId 髭切-私聊版(Character)(未映射,原样使用) ``` 或命中后: ``` chatluna-scene-rules presetId 髭切-私聊版(Character) → 髭切-通用版(Character) ``` > 实际输出前面还会有日志等级/时间戳/颜色(如 `[I] 2026-… chatluna-scene-rules …`),不用在意; > **认准来源是 `chatluna-scene-rules`、内容是 `presetId …(未映射,原样使用)` 或 `presetId … → …` 这一行即可。** > 别把 livingmemory 自己打的 `chatluna-livingmemory … before-chat resolved: …presetId=…`(带 `presetId=` 等号)那行, > 当成本插件的命中行——那是 livingmemory 的 debug,不是本插件的。 4. 把日志里**原样**的 presetId 字符串(连全角括号一起)抄进 `aliases` 的左边,值填你的共享 key。 5. 重启,再触发,确认日志变成 `→ 共享key`。**核对无误后把 `debug` 关掉**。 ### 4.2 多角色 / 多 bot - **一个 bot 一个插件实例**就够(一个 `aliases` 字典里可以放多个角色的多条映射)。 - 多 bot 部署时,每个 bot 各自的 `koishi.yml` 各配各的(本插件无端口/网络配置,纯本地接管服务,简单)。 真实多角色例子(三只 bot 的实际用法,供参考): ```yaml # bot A(髭切) chatluna-scene-rules:main: aliases: 髭切-本丸语音版(Character): 髭切-通用版(Character) 髭切-私聊版(Character): 髭切-通用版(Character) 髭切(Character): 髭切-通用版(Character) 髭切-本丸版(Character): 髭切-通用版(Character) # bot B(膝丸) chatluna-scene-rules:main: aliases: 膝丸-本丸版(Character): 膝丸-通用版(Character) 膝丸-本丸语音版(Character): 膝丸-通用版(Character) 膝丸-私聊版(Character): 膝丸-通用版(Character) ``` --- ## 5. 验证是否生效 1. **debug 日志**:看到 `presetId 某预设(Character) → 共享key(Character)`,就说明映射命中了。 2. **端到端实测**:用「私聊版」预设告诉它一件只有这次说过的事;切到「群聊版」预设问它—— **能记得 = 池打通了**。(注意:得是配置生效**之后**新产生的记忆;见 §7 关于旧记忆。) --- ## 6. 工作原理(简述) - livingmemory 里**所有记忆操作**(召回 / 入库 / 快照 / 注入)都通过 `createScope(conversationId, presetId, userId, channelId, options)` 构造一个「记忆作用域」, 而池就是按这个 `presetId` 区分的。 - 角色预设这条路径**不走** `resolvePresetId`,而是直接拼 `名字(Character)` 再调 `createScope`。 所以本插件**主要 hook `createScope`**:在它真正执行前,把传入的 `presetId` 过一遍别名字典(命中就换成共享 key)。 - 同时**兜底 hook `resolvePresetId`**:少数旧路径仍经它(其结果之后也会进 `createScope`; 已经映射过的值不在别名键里,不会被二次映射)。 - 启用 `ctx.effect` 注册还原:**插件一停用,两个方法立刻换回原样**,livingmemory 行为完全恢复,零残留。 整个过程**不写 livingmemory 的任何文件**,只是运行时给它的两个方法包了一层。 --- ## 7. 注意 / 常见坑 - **★ 全角括号**:键名后缀是 `(Character)`(全角),不是 `(Character)`(半角)。差一个字符就静默不命中。**必用 §4.1 的 debug 核对**。 - **旧记忆不自动迁移**:本插件只让此后的读写落到同一个池。**配置生效之前**各预设旧池里的历史记忆, 不会被自动搬到共享池,也不会互相合并。需要迁移历史数据的话,那是 livingmemory 数据层的活,本插件不做。 - **共享 key 任意**:右边的值是个字符串池名,不要求真有一个同名预设。但同一角色各预设要**填得完全一致**,否则各进各池。 - **改源码要重启**:动了插件 `index.js` 必须重启 koishi。只改 `koishi.yml` 的 `aliases` 一般 reload 即可, 但保险起见重启最稳(尤其首次)。 - **与「记忆按聊天/会话种」的关系**:本插件改的是「池按哪个 key 分」,不改 livingmemory 其它行为 (召回策略、抽取、做梦等)——那些仍按你 livingmemory 的配置走。 --- ## 8. 回滚 / 卸载 - **临时停用**:`koishi.yml` 把 `chatluna-scene-rules:main` 改成 `~chatluna-scene-rules:main`(前缀 `~` = 禁用)。 插件停用会自动还原 `createScope` / `resolvePresetId`,各预设立刻回到各自独立池。 - **彻底卸载**:`npm rm koishi-plugin-chatluna-scene-rules`(或删软链),删掉 `koishi.yml` 里的实例段,删 `external/` 目录。 --- ## 9. 常见问题(FAQ) **Q:填了 aliases 但日志一直是「未映射,原样使用」。** A:键名没对上。九成是**全角/半角括号**问题,或预设名差了字符/空格。开 `debug`,把日志里**原样**的 presetId 抄进去(连 `(Character)` 一起)。 **Q:私聊版和群聊版还是不互通。** A:① 两者没映射到**同一个** key;② 共享 key 两处写得不一致(差一个字符也算两个池);③ 插件没启用 / 没重启; ④ livingmemory 本身没正常工作——先确认**单个预设能记住对话**,再谈共享。 **Q:共享池 key 要不要真有一个那个名字的预设?** A:不需要,它只是个字符串池名。常用一个「通用版」名字当公共池。 **Q:打通之后,之前两个池里的旧记忆会自动合并吗?** A:不会。本插件只负责「从此读写同一个池」,历史数据不迁移(见 §7)。 **Q:启动日志没有 `chatluna-scene-rules` 相关行。** A:① 没安装成功(查 §2,确认 `node_modules` 里有它);② 硬依赖没就绪——本插件 `inject: ['chatluna_living_memory']`, 必须 chatluna-livingmemory 已加载并提供该服务;③ koishi.yml 实例段缩进/语法有误。 --- ## 10. 想改个直白的名字(可选) 现名 `chatluna-scene-rules` 是旧名。若你想叫它 `koishi-plugin-chatluna-memory-alias`(或别的),改四处即可: > **先记住这条规则**:Koishi 里 `koishi.yml` 的**配置块键 = 包名去掉 `koishi-plugin-` 前缀**。 > 所以包名 `koishi-plugin-chatluna-memory-alias` 对应的配置键是 `chatluna-memory-alias`(**不是** `memory-alias`)。 > 包名和配置键必须这样对应,否则配置块匹配不到插件、改名后静默不加载且无报错。 以改成 `koishi-plugin-chatluna-memory-alias` 为例: 1. 插件目录里 `package.json` 的 `"name"` 改成 `koishi-plugin-chatluna-memory-alias`; 2. `external/` 下的目录名跟着改(改成 `koishi-plugin-chatluna-memory-alias`); 3. app 根 `package.json` 里那条 `file:` 依赖、以及 `node_modules` 里的链接/安装跟着改 (最简单:删旧的,重新 `npm i ./external/koishi-plugin-chatluna-memory-alias`); 4. `koishi.yml` 里配置块的键 `chatluna-scene-rules:main` 改成 **`chatluna-memory-alias:main`** (= 新包名去掉 `koishi-plugin-` 前缀)。 `exports.name`(`index.js` 第 3 行)是运行时显示名,可一并改;改完重启 koishi。 **注意**:本指南全篇按现名 `chatluna-scene-rules` 写,改名后请自行对应替换。 --- ## 附:发布包内容清单 ``` koishi记忆池别名器-发布包/ ├── 安装与使用指南.md ← 你正在看的这份 └── koishi-plugin-chatluna-scene-rules/ ← 插件本体(旧名,功能=记忆池别名器) ├── index.js ├── package.json └── README.md ``` 许可:MIT。本插件依赖 `chatluna_living_memory` 服务(由 chatluna-livingmemory 提供), 本身不存储记忆、不修改 livingmemory 任何文件,只在运行时做 presetId 别名映射。