
AI Agent 的记忆问题,你遇到过吗
最近我在搭一个多轮对话的 Agent,有个需求是让它记住每个用户的历史偏好,下次对话时能直接调用。听起来很简单,真做起来发现事情不小。
第一个冲动是上 Chroma 或者 Milvus。但问题来了:本地开发要单独跑一个向量库进程,测试环境要多维护一个服务,上生产还要考虑高可用。对于一个中型应用来说,这个开销有点不成比例——业务核心不在这里,但为了存几万条记忆,我得多养一个数据库?
退而求其次,有人说用 Redis 存 JSON + 全文检索。勉强能用,但语义检索就别想了,用户说「上次聊的那个界面风格问题」,关键词搜不到任何东西。
这个问题肯定不止我一个人遇到过。
RogueMap 是什么
RogueMap 是一个 Java 嵌入式存储引擎,作者 bryan31,就是 LiteFlow 那个人。
LiteFlow 在国内 Java 规则引擎这块有点名气,这个背景我觉得是加分项,至少不是野路子项目。
它的核心思路是:数据不放 JVM 堆,走 mmap(内存映射文件)。mmap 是操作系统级别的文件映射机制,读写数据直接操作内存地址,OS 负责和磁盘同步,绕过了 JVM 的 GC 管辖范围。
这个设计带来几个直接结果:
• JVM 堆占用几乎不涨,Full GC 压力消失 • 进程重启后数据还在,因为底层是文件 • 可以存比物理内存大得多的数据集(只要磁盘够)
它暴露的 API 是四种数据结构:RogueMap(键值)、RogueList(双向链表)、RogueSet(集合)、RogueQueue(队列),写法和 Java 标准集合很像,迁移成本低。
1.0 版本发出来之后,社区里问 AI 相关需求的声音挺多,Agent 记忆和 RAG 这两块尤其集中。1.1.0 这次更新,基本上就是冲着这块来的。

1.1.0 版本一共更新了哪些东西
这次不是小打小闹。除了新增 AI 记忆层,基础设施层面也有几个改动,而且连依赖结构都拆重新组了。列一下:
| RogueMemory AI 记忆层 | |
| TTL 过期 | |
| 自动检查点 | |
| 自动扩容 | |
| 超低堆内存索引 | |
| 复杂泛型支持 | List<User> 这类带泛型的类型序列化不再出错 |
我自己用下来感受是:TTL 这个功能其实等了挺久,之前 RogueMap 没有 TTL,做缓存场景必须搭 Redis,这下可以省掉了。自动扩容对 AI 记忆来说也很实用,记忆条数很难提前估,老版本写满直接抛异常,那体验实在不好。
RogueMemory 到底怎么工作
这块值得多说几句。
RogueMemory 是 1.1.0 新增的模块,Maven 坐标是 roguemap-memory,引入之后会自动把 roguemap-embedding 也带进来。它做的事情就一件:把向量检索能力直接嵌进你的 Java 进程,不需要任何外部服务。
检索架构
内部走的是混合检索策略:
• 向量路:用 HNSW 算法做近似最近邻搜索(ANN),对语义相似度敏感 • 关键词路:用 BM25 算法,对精确词汇匹配更准 • 融合:两路结果通过 RRF(Reciprocal Rank Fusion,倒数排名融合)合并排序
这个组合不算新鲜——Elasticsearch 8.x 的混合检索也是类似思路——但能在一个嵌入式 Java 库里开箱即用,我觉得还是挺有价值的。
三种模式可以按需选:
• HYBRID:向量 + BM25 双路,RRF 融合,默认推荐• VECTOR_ONLY:纯语义向量检索• KEYWORD_ONLY:纯 BM25,这个模式下甚至不需要接 Embedding 服务
基本用法
RogueMemory mem = RogueMemory.mmap() .persistent("data/mem") .searchMode(SearchMode.HYBRID) .embeddingProvider(new UniversalEmbeddingProvider(apiKey)) .build();// 存一条记忆mem.add("用户XXX是一名Java开发者,生成代码的语言要采用Java而不是其他");// 语义检索,取最相关的 5 条List<MemoryResult> results = mem.search("用户偏好", 5);// 支持 namespace 做隔离,metadata 附加业务标签mem.add("上次对话读取了一个抢票业务逻辑", Map.of("session", "abc123"), "chat_history");mem.close();几个细节需要注意一下:
• 持久化路径 persistent("data/mem")指向本地文件夹,里面会生成 mmap 文件,进程重启后记忆完整保留• namespace参数可以做多租户或多会话的隔离,不同 namespace 的数据互不干扰• mem.close()要记得调,负责把内存中的索引刷到磁盘
自动检查点 + 自动扩容,AI 记忆场景都支持
这两个特性对 RogueMemory 来说特别有用。记忆条数很难预估,开启自动扩容之后空间不够会自动翻倍,业务代码完全感知不到:
RogueMemory mem = RogueMemory.mmap() .persistent("data/mem") .autoExpand(true) // 开启自动扩容 .autoCheckpoint(10, TimeUnit.MINUTES) // 每 10 分钟自动 checkpoint .autoCheckpoint(5_000) // 或每 5000 次写操作触发一次 .embeddingProvider(new UniversalEmbeddingProvider(apiKey)) .build();两个 autoCheckpoint 条件可以同时配,哪个先满足哪个先触发,后台守护线程跑,不阻塞业务。
Embedding 服务怎么接
RogueMemory 做向量化的模块叫 UniversalEmbeddingProvider,对接标准是 OpenAI 的 /v1/embeddings 接口。
这个选择我觉得很务实。OpenAI 这套接口已经成了事实标准,国内外大多数 Embedding 服务都实现了兼容,直接复用意味着开发者几乎零额外学习成本。而且实现上没有引任何第三方 HTTP 库,用的是原生 HttpURLConnection,零额外依赖。
下面是目前支持的服务全景:
| OpenAI | new UniversalEmbeddingProvider(apiKey) | |
| 阿里云百炼 | baseUrl + apiKey + model | |
| 智谱 GLM | ||
| Moonshot / Kimi | ||
| Mistral | ||
| Jina AI | ||
| Ollama | baseUrlhttp://localhost:11434/v1 | |
| vLLM / LocalAI | baseUrl 即可 |
切换服务只需要改三个参数:baseUrl、apiKey、model,向量维度不需要手动写,第一次调用时自动探测。
我个人倾向于在生产环境用国内服务(延迟更低、不用考虑网络问题),本地开发就跑 Ollama,这样开发环境完全离线,不消耗 token。两套配置来回切换的成本几乎可以忽略。
实战
接入阿里云百炼,从拿到 API Key 到跑通检索
我选百炼主要有两个原因:text-embedding-v3 这个模型的效果在中文语料上比 OpenAI 的 text-embedding-ada-002 好一些,而且价格便宜很多(0.0007 元 / 1k Token),新用户还有 100 万 Token 的免费额度可以用。
Step 1:获取 API Key
登录阿里云百炼控制台,在 API-KEY 管理页面创建一个 Key。
Step 2:Maven 依赖
<!-- 核心存储引擎 --><dependency> <groupId>com.yomahub</groupId> <artifactId>roguemap-core</artifactId> <version>1.1.0</version></dependency><!-- AI 记忆模块(会自动依赖 roguemap-embedding) --><dependency> <groupId>com.yomahub</groupId> <artifactId>roguemap-memory</artifactId> <version>1.1.0</version></dependency>Step 3:初始化 RogueMemory,指向百炼
import com.yomahub.roguemap.memory.RogueMemory;import com.yomahub.roguemap.memory.SearchMode;import com.yomahub.roguemap.embedding.UniversalEmbeddingProvider;public class AgentMemoryDemo { public static void main(String[] args) throws Exception { String apiKey = "sk-xxxxxxxxxxxxxxxxxxxxxxxx"; // 替换成你的百炼 API Key String baseUrl = "https://dashscope.aliyuncs.com/compatible-mode/v1"; String model = "text-embedding-v3"; RogueMemory mem = RogueMemory.mmap() .persistent("data/agent_memory") // 持久化目录,首次运行自动创建 .searchMode(SearchMode.HYBRID) // 混合检索模式 .autoExpand(true) // 记忆增长时自动扩容 .autoCheckpoint(5, TimeUnit.MINUTES) // 每 5 分钟刷一次磁盘 .embeddingProvider( new UniversalEmbeddingProvider(baseUrl, apiKey, model) ) .build(); // 写入几条记忆 mem.add("用户倾向于使用深色主题,讨厌自动播放的视频广告"); mem.add("用户上次会话询问了 Spring Boot 3 迁移的问题", Map.of("topic", "spring"), "tech_pref"); mem.add("用户反馈说目前的表单交互太繁琐,希望减少步骤"); // 语义检索:查跟「界面体验」相关的历史偏好 List<MemoryResult> results = mem.search("界面和交互体验", 3); for (MemoryResult r : results) { System.out.println("[score=" + r.getScore() + "] " + r.getContent()); } mem.close(); }}几个实际踩坑点
1. 模型维度
百炼的 text-embedding-v3 支持多档维度(1024 / 768 / 512 等),不填默认是 1024。UniversalEmbeddingProvider 会在第一次调用时自动探测维度,后续写入的向量全部对齐这个维度,中途不能换。如果要切换维度,需要清空并重建 data/agent_memory 目录。
2. 本地开发时的网络
百炼在国内访问没问题,但如果你本地有某些代理设置,注意 HttpURLConnection 会走系统代理,有时候会报超时。本地测试可以临时换成 Ollama,逻辑完全一样,只是 baseUrl 改成 http://localhost:11434/v1,model 改成 nomic-embed-text(Ollama 不校验 apiKey,填什么都行)。
3. namespace 的用法
如果你的应用要同时服务多个用户,强烈建议用 namespace 做隔离,按 userId 或 sessionId 来。不然不同用户的记忆会混在一起,检索结果会出现串数据。
// 按用户 ID 隔离mem.add("喜欢科幻类内容", Map.of(), "user_" + userId);List<MemoryResult> userResults = mem.search("内容偏好", 5, "user_" + userId);4. 关于费用
每次 mem.add() 都会调用一次 Embedding API,百万 Token 的免费额度大概够存 50~100 万条短记忆。生产环境要注意控制写入频率,避免每轮对话都写一堆冗余数据进去。
什么情况下别用它
这个我觉得还是得说清楚,不然就成了广告稿了。
如果你已经有独立的向量数据库:Milvus 或者 Qdrant 已经在跑了,RogueMemory 不会比它们更好,集群化、分布式查询、元数据过滤等高级特性是嵌入式方案做不到的。
如果你的记忆需要跨多实例共享:RogueMemory 是单进程嵌入式,数据文件绑定在某台机器上。多个 Java 实例要共享同一份记忆,不行。
如果你用 lowHeapIndex 开了超低内存索引:这个模式下事务操作不支持,对一致性要求高的场景要注意。
数据量极大的场景:HNSW 的构建和查询在亿级向量规模下性能会明显下降,这个体量还是应该上专业向量库。
我自己的判断是:中小型 AI 应用、原型项目、单机部署的 Agent 服务,这是 RogueMemory 最舒服的使用区间。
快速上手和资源汇总
最后汇一下依赖坐标和项目资源。
基础依赖(只用存储结构,不用 AI 记忆):
<dependency> <groupId>com.yomahub</groupId> <artifactId>roguemap-core</artifactId> <version>1.1.0</version></dependency>AI 记忆模块(会自动带上 roguemap-embedding):
<dependency> <groupId>com.yomahub</groupId> <artifactId>roguemap-memory</artifactId> <version>1.1.0</version></dependency>注意:1.1.0 的依赖结构和之前版本相比有变化,升级时先把旧依赖清掉再加新的,不然可能出现冲突。
开源地址:
https://github.com/bryan31/RogueMap
我有时候觉得,国内开源生态里其实不缺想法,缺的是把一个具体痛点做到够用的工程意志。RogueMap 这次更新的方向对了——不去和 Milvus 抢生态,只是把「Java 进程里就能跑的记忆层」这件事做扎实。对我这种不想为几万条记忆多维护一套服务的人来说,它刚好够用。
夜雨聆风