plugin-memory-mongodb — MongoDB 记忆存储
包名: @aalis/plugin-memory-mongodb
源码: packages/plugin-memory-mongodb/src/index.ts
概述
基于 MongoDB 的 MemoryService 实现,需要一个可访问的 MongoDB 服务。以 priority: 5 注册 memory 服务(plugin-memory-sqlite 为 10,plugin-memory-inmemory 为 -100),与 sqlite 同时启用时默认选用 sqlite;如需改用本插件,可在顶层配置 servicePreferences 中显式指定。
插件声明
typescript
export default definePlugin({
name: '@aalis/plugin-memory-mongodb',
reusable: true,
provides: [memory],
apply(caps) { /* 见源码 */ },
uses: {
logger,
config,
lifecycle,
provide,
paths: optional(hostPaths), // 数据目录:派生库名、核对库的归属
},
});配置
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
uri | string | 'mongodb://localhost:27017' | MongoDB URI:MongoDB 连接字符串(secret:连接串可能带账号密码,值存进 secrets.yaml,WebUI 不回显)。默认值不写进配置文件,没填时插件按默认值连接;引用的键不在 secrets.yaml 里时激活失败,不回落到默认的本机库 |
database | string | '' | 数据库名:存储消息历史与元数据的数据库。留空按数据目录与实例派生,见下 |
claimFrom | string | '' | 认领库的归属:平时留空。库已归另一个数据目录(例如数据目录搬了位置)时,先停掉那个数据目录的实例,再填它的路径;库里已有数据却还没有归属记录时填 unowned。启动一次就把归属改到这个数据目录,认领后删掉这一项:留着的 unowned 会让以后指向的任何库都跳过「已有数据」的检查 |
collection | string | 'messages' | 集合名:消息集合名称 |
connectTimeoutMs | number | 5000 | 连接超时(毫秒):建立连接与服务器选择(serverSelection)的超时时间,两者共用此值 |
rangeQueryLimit | number | 500 | 范围查询返回上限:区间消息查询(向量召回的上下文窗口扩展等)单次返回的最大条数。命中上限会静默截断 |
crossSessionMaxLimit | number | 1000 | 跨会话查询返回上限:跨会话最近消息查询允许的最大条数;调用方请求超过此值会被收窄到此上限 |
特性
apply为异步函数,启动时连接数据库(停用或停机时关闭客户端,中止连接与建索引),在消息集合上创建{ sessionId: 1, timestamp: 1 }、{ archived: 1, timestamp: -1 }、{ 'metadata.platform': 1, timestamp: -1 }三个索引;结构化元数据存放在同库固定名为metadata的集合中(不受collection配置影响),并建有{ namespace: 1, key: 1 }唯一索引- 完整实现
MemoryService:消息读写(saveMessage、getHistory、countHistory、getFullHistory、clearSession、clearAll,其中clearAll只清空消息集合,metadata集合由各命名空间的归属插件经memory:clear自行清理;getHistory的条数与countHistory只数对话消息,按kind排除事件标记(CONTROL_KINDS),getHistory先找到第 N+1 新的那条对话消息,再取它之后的全部)、区间与跨会话查询(getMessagesBySessionRange受rangeQueryLimit限制,getRecentMessagesAcrossSessions受crossSessionMaxLimit限制)、archiveSpan(将最早的若干条未归档消息标记为archived,其中调用方列出的位置不归档,不删除;与getHistory同按timestamp、_id排序,同毫秒按写入序;一次updateMany,不是事务)、updateMessageContent、deleteMessagesByTimestamps,以及结构化元数据(saveMetadata/getMetadata/listMetadata/listMetadataKeys/commitMetadata/deleteMetadata) commitMetadata用有序bulkWrite批量写入,不是事务:遇错即停,失败点之前的写已生效listMetadata逐条检查:data不是对象的文档跳过并记一条 warn,点名命名空间与键;缺少updatedAt或它不是日期的文档照常返回,updatedAt记为 0。这两种文档只会来自手工导入等外部写入,本插件写入时总会带上这两个字段。listMetadataKeys只取键、不读data,跳过的文档也在其中:各插件经/clear整体清空命名空间时连同它们一并删除- 可多实例(
@aalis/plugin-memory-mongodb:<后缀>)。database留空时按数据目录与实例派生:主数据目录用aalis,profile 数据目录(部署目录下的data-<profile>/)用aalis_<profile>,带后缀的实例再加后缀(如:b实例用aalis-b、aalis_<profile>_b);配置里写明的库名照用。库名超过 MongoDB 63 字节的上限时激活失败,报一行ConfigError。metadata集合与消息集合同库,所以同一连接串(按字面比较)下两个实例用同一个库时,即使collection不同也算共用:后激活的那个激活失败,报一行ConfigError,点名占用该库的实例,不发起连接 - 库的归属:宿主提供
host-paths时,库里metadata集合记着它归哪个数据目录与哪个实例(命名空间aalis.data、键owner)。不同数据目录连同一个 MongoDB、派生出同一个库名时(例如两个部署目录各自的data/,或复制出来的测试实例),后来的那个激活失败,报一行ConfigError,点名库归属的数据目录与实例,不会共写。- 归属按数据目录的真实路径认。数据目录搬了位置、换了用户名或从备份恢复到别处,也会报这个错;确认是同一份数据后,先停掉原数据目录的实例,再在配置里写
claimFrom: <原数据目录>启动一次认领。归属只在激活时核对,认领之后原数据目录里还在跑的实例不会察觉,两边会一起写。 - 路径只在一台机器上有意义:两台机器或两个容器的数据目录路径相同(例如同一个镜像起的容器都用
/root/.aalis)、又连同一个 MongoDB 时认不出来,要各自写明不同的database。 - 新库(没有记录、任何集合里也没有数据)直接记下本数据目录。库里已有数据却没有记录时不默认认走,要写
claimFrom: unowned;记录坏了(缺数据目录或实例)时同样报错,确认后写claimFrom: unowned改写。 - 宿主不提供
host-paths(嵌入式宿主、测试夹具)时不做这项检查
- 归属按数据目录的真实路径认。数据目录搬了位置、换了用户名或从备份恢复到别处,也会报这个错;确认是同一份数据后,先停掉原数据目录的实例,再在配置里写
- dispose 时关闭 MongoDB 连接
- 连接或建索引失败时关闭客户端,并抛出
MongoDB 连接失败(确认 MongoDB 在运行、配置项 uri 写对):<原因>。激活失败(连接失败或归属不对)时本插件不提供memory服务;同时启用了@aalis/plugin-memory-sqlite或@aalis/plugin-memory-inmemory的,记忆改由它们提供,看起来像历史丢了、新消息也写到了那里。只用 MongoDB 的,把这两个插件写进disabledPlugins - 日志中打印的 URI 会隐去密码段(
mongodb://user:***@host)