一.mem0是什么?
1.概念
Mem0 是为 AI 应用和 Agent 提供的智能记忆层,负责从对话中识别、保存、更新、隔离和召回有价值的上下文,使无状态模型能够跨会话保持连续性。
Mem0(读作“mem-zero”)是给AI 助手/Agent加的一层持久化语义记忆层**。它解决的核心问题是:
AI是无状态的,每次对话都从零开始,而Mem0让它能跨会话记住用户偏好、项目决策、学习经验等。
核心能力:
- 三层记忆作用域:
user_id(用户级)、agent_id(Agent 级)、run_id(会话级) - 自动事实提取:丢一段对话进去,
LLM自动从中抽取关键事实 - 多信号检索:语义搜索 +
BM25关键词 + 知识图谱,三路融合后去重,重排。 - 实体链接:自动提取实体(人名、项目名等),跨记忆关联
- 时序推理:时间感知检索,区分"当前状态"和"过去事件"
在 aiAgent 里面 mem0 角色
使用 Mem0 之前:
用户提问后,系统并行调用
Milvus进行语义检索、BM25进行关键词检索,并访问知识图谱进行关系检索;三路分别返回结果后,业务层需要完成去重、排序、加权融合和上下文整理,再将筛选后的内容注入提示词,交给大模型生成回答。
使用 Mem0 之后:
用户提问进入应用后,由
Mem0统一管理相关记忆的召回、过滤、排序和整理;应用取得整理后的相关记忆后,再交给大模型。也就是说mem0将原来的向量检索,知识图谱,关键词搜索,去重,重排等一系列复杂的事情全部封装好了你只需要将mem0返回的结果交给大模型处理就好了。
上图非常像RAG的处理流程图,但是这个处理的是会话上下文。RAG 负责“企业文档知识”,他的搜索依旧需要原来的解决方案,不能使用mem0做简化,因为mem0的优势是用户记忆,会话记忆,agent的配置记忆。
- 用户记忆 (User Memory)
绑定userld后,存的是使用人的:姓名、城市、偏好、习惯等。
换会话、换Agent也能读到,适合个性化和跨场景复用。
- 会话记忆(Session Memory)
绑定userld+runld,存的是当前这次对话里的任务上下文:这次要做什么、讨论到哪- 步、临时目标等。 会话结束后可以清理,不会和别的会话混在一起。
- Agent 记忆 (Agent Memory)
绑定agent_id,agent_id是Agent 上下文的隔离与归属标识,存的是Agent自己的设定:角色、语气、回答方式、专业领域等。
同一个用户换不同Agent,各自保持独立人格和风格。
就是说换个大模型以后,我设定的机器人是男的还是女的,声音,回答方式,你的角色是旅游博主等等,都不会随agent的切换而改变。
二.mem0的使用
mem0有2个比较容易混淆的类,如下所示:
MemoryClient 把数据发给 Mem0 托管服务;Memory 在你自己的基础设施中运行。
MemoryClient的创建方式
使用MemoryClient创建的memory,他的数据是存放在app.mem0.ai/dashboard/r… 里面的。mem0是第三方数据管理平台。
它不但帮我们托管数据,还帮我完成会话向量检索,知识图谱搜索,关键词搜索。
安装
pnpm install mem0ai
获取MEM0_API_KEY,登录:app.mem0.ai/login 先要注册用户,才能获取。
import MemoryClient from "mem0ai";
import "dotenv/config";
const client = new MemoryClient({
apiKey: process.env.MEM0_API_KEY,
});
使用Memory创建的memory,他的数据是存放在自己的数据库里面。他会和milvus,BM 25,知识图谱配合完成
完成会话向量检索,知识图谱搜索,关键词搜索,去重,重排等繁琐的事情。
Memory的创建方式
import { Memory } from "mem0ai/oss";
const memory = new Memory({
llm: {
provider: "openai",
config: {
apiKey: process.env.OPENAI_API_KEY,
model: "gpt-5-mini",
},
},
embedder: {
provider: "openai",
config: {
apiKey: process.env.OPENAI_API_KEY,
model: "text-embedding-3-small",
},
},
vectorStore: {
provider: "qdrant",
config: {
host: "localhost",
port: 6333,
collectionName: "memories",
},
},
});
MemoryClient和Memory的区别
如何选择MemoryClient和Memory
- 数据必须完全留在企业内部:选
Memory - 希望快速上线、降低基础设施负担:选
MemoryClient - 需要 Graph Memory、Webhooks 等平台能力:选
MemoryClient - 需要深度改造记忆链路:选
Memory
有数据私有化和平台运维能力的企业,更可能选择 Memory;重视交付速度、成本和托管能力的企业,更可能选择 MemoryClient。
数据必须留在自有基础设施,并且企业具备模型与数据库运维能力时,选
Memory;其余大多数项目,尤其是产品验证和一般业务系统,优先选MemoryClient。MemoryClient 的优势是把基础设施和运维责任交给 Mem0 托管平台,因此交付速度更快、基础设施效率更高
MemoryClient使用案例
import MemoryClient from "mem0ai";
import "dotenv/config";
const client = new MemoryClient({
apiKey: process.env.MEM0_API_KEY,
});
const userId = "user-123";
async function main() {
/* ========== 1. 新增记忆 ========== */
const messages = [
{
role: "user",
content: "我喜欢弹钢琴,我每周都要弹三次,每次一小时,我很喜欢这个过程。",
},
{
role: "assistant",
content: "好的,我已经记住你的喜好。",
},
];
const addResult = await client.add(messages, {
userId,
metadata: {
category: "travel_preference",
source: "chat",
},
});
console.log("新增结果:", JSON.stringify(addResult));
/* ========== 2. 查询相关记忆 ========== */
const searchResult = await client.search("推荐北京餐厅", {
filters: {
user_id: userId,
},
topK: 10,
});
console.log("搜索结果:", JSON.stringify(searchResult, null, 2));
/* ========== 3. 查询当前用户的所有记忆 ========== */
const allMemories = await client.getAll({
filters: {
user_id: userId,
},
});
console.log("用户全部记忆:", JSON.stringify(allMemories, null, 2));
/* ========== 4. 根据 ID 查询单条记忆 ========== */
const memoryId = "f6ae414a-aa19-4859-8853-a5ad576caeba";
const singleMemory = await client.get(memoryId);
console.log("单条记忆:", JSON.stringify(singleMemory, null, 2));
/* ========== 5. 修改记忆 ========== */
// const updateResult = await client.update(memoryId, {
// text: "用户偏好川菜,计划下周三前往北京出差,预算人均200元。",
// metadata: {
// category: "travel_preference",
// updated: true,
// },
// });
// console.log("修改结果:", JSON.stringify(updateResult, null, 2));
/* ========== 6. 删除单条记忆 ========== */
// const deleteResult = await client.delete(memoryId);
// console.log("删除结果:", JSON.stringify(deleteResult, null, 2));
// /* ========== 7. 删除当前用户的所有记忆 ========== */
// // 注意:该操作会删除指定用户下的全部记忆,生产环境必须谨慎使用
// const deleteAllResult = await client.deleteAll({
// userId,
// });
// console.log("删除用户全部记忆:", JSON.stringify(deleteAllResult, null, 2));
}
main().catch((error) => {
console.error("Mem0 操作失败:", error);
});
查看 mem0 的数据
语义检索,我每周都要干嘛?他回复我每周喜欢弹钢琴,一周三次。
这个东西的好处是,他帮我们做好了会话上下文的图谱检索,关键词搜索,语义检索,就不需要我们手动再做。不过这个东西是需要掏钱的。每天都有一些额度你可以使用。
利用redis和mem0存储会话
利用redis做短期记忆,压缩记忆本次会话都说了些什么东西。利用mem0做长期记忆,记录用户信息,agent喜好等。
具体实现代码如下:
代码里面用RedisMessageStore类来装redis的存信息,取信息,清空信息的方法。
Mem0MemoryStore类用来存mem0存取数据的方法。
invokeWithMemory是用来加载对话的,不管是进入长期会话还是进入短期会话,都是靠大模型自己来判断加入的。
-
- 从 Redis 加载当前会话历史
-
- 从 Mem0 检索用户长期记忆和会话记忆
-
- 将 Mem0 记忆转换为 SystemMessage
-
- 调用 Agent
-
- 将新对话写入 Redis
-
- 判断哪些内容需要写入 Mem0
-
- 返回模型结果和统计信息
invokeWithMemory 负责“取记忆—组上下文—调用模型—存会话—筛选长期事实—写回 Mem0”的完整闭环,是整个应用把 Redis 短期会话与 Mem0 长期记忆连接起来的核心方法。
/**
* Redis 记这轮聊天,Mem0 记值得长期留着的事。
*
* 用户层 = 换天聊还认得你;会话层 = 只管当前这个聊天窗口。
*
* docker compose up -d redis 后 pnpm agent
* :clear 清 Redis | :clear-mem0 清 Mem0 | exit/:q 退出
*/
import "dotenv/config";
import Redis from "ioredis";
import * as readline from "node:readline/promises";
import { stdin, stdout } from "node:process";
import { z } from "zod";
import { MemoryClient } from "mem0ai";
import { ChatOpenAI } from "@langchain/openai";
import {
SystemMessage,
SystemMessageChunk,
HumanMessage,
mapChatMessagesToStoredMessages,
mapStoredMessagesToChatMessages,
} from "@langchain/core/messages";
import { createAgent, summarizationMiddleware } from "langchain";
const REDIS_HOST = process.env.REDIS_HOST ?? "localhost";
const REDIS_PORT = Number(process.env.REDIS_PORT ?? 6379);
const REDIS_DB = Number(process.env.REDIS_DB ?? 0);
const MEMORY_TTL = Number(process.env.MEMORY_TTL_SECONDS ?? 1800);
const KEY_PREFIX = process.env.MEMORY_KEY_PREFIX ?? "agent:short_memory";
const USER_ID = process.env.MEM0_USER_ID ?? "demo_user_001";
const SESSION_ID = "session_002";
const MEM0_TOP_K = Number(process.env.MEM0_TOP_K ?? 5);
const memorySchema = z.object({
write_user: z
.boolean()
.describe(
"写入用户层:换一个新会话仍应保留的长期事实(身份、居住地、长期爱好、饮食禁忌、持久偏好)。不含仅本轮任务。",
),
write_session: z
.boolean()
.describe(
"写入会话层:仅当前会话/thread 有效的任务、大纲、进度、待办、临时决策(如「这次先写…」「数据部分明天补」)。",
),
reason: z.string().describe("分类理由,一句话"),
});
const CLASSIFIER_PROMPT = `你是记忆分层分类器。判断本轮对话是否有「新事实」需写入 Mem0,并分到正确层级。
## user 层(跨会话长期)
- 用户身份与画像:姓名、职业、居住地、长期爱好
- 长期偏好与约束:饮食过敏、回答风格、常用技术栈
- 持续数周以上的个人背景(非单次任务)
## session 层(仅当前会话)
- 当前正在做的任务、目标、文档大纲、方案草稿
- 本会话内的进度、决策、待办、临时约定
- 用户明确用「这次」「本轮」「当前会话」描述的工作上下文
## 均不写入
- 寒暄、致谢、纯确认
- 助手生成的通用内容(攻略、示例代码、建议清单),用户未明确采纳为新事实
- 无信息增量的复述
## 决策原则
1. 「这次我们先写 Q1 总结」「当前在排查 XX」→ 优先 session,不要标成 user
2. user 与 session 可同时为 true(如同时说职业+当前任务),但勿把纯会话任务只标 user
3. 一次性请求(如「帮我做旅行攻略」)且未产生需跨轮记住的约定 → 均为 false`;
const summaryPrompt = `你是对话摘要助手。用中文简洁总结:话题、会话内进度/报错/待办。
用户级长期偏好由外部记忆维护,摘要勿重复堆砌。不要编造。
待摘要的对话:
{messages}
摘要:`;
/** Mem0 注入的 SystemMessage 不写回 Redis */
function messagesForRedis(messages) {
return messages.filter(
(m) => !SystemMessage.isInstance(m) && !SystemMessageChunk.isInstance(m),
);
}
class RedisMessageStore {
constructor({ redis, keyPrefix, ttlSeconds }) {
this.redis = redis;
this.keyPrefix = keyPrefix;
this.ttlSeconds = ttlSeconds;
}
messagesKey(sessionId) {
return `${this.keyPrefix}:${sessionId}:messages`;
}
async loadMessages(sessionId) {
const raw = await this.redis.get(this.messagesKey(sessionId));
if (!raw) return [];
return mapStoredMessagesToChatMessages(JSON.parse(raw));
}
async saveMessages(sessionId, messages) {
const payload = JSON.stringify(mapChatMessagesToStoredMessages(messages));
await this.redis.set(this.messagesKey(sessionId), payload, "EX", this.ttlSeconds);
}
async clear(sessionId) {
await this.redis.del(this.messagesKey(sessionId));
}
async ttl(sessionId) {
return this.redis.ttl(this.messagesKey(sessionId));
}
}
class Mem0MemoryStore {
constructor({ client, userId, sessionId, topK, classifier }) {
this.client = client;
this.userId = userId;
this.sessionId = sessionId;
this.topK = topK;
this.classifier = classifier;
}
async search(query) {
const [userRes, sessionRes] = await Promise.all([
this.client.search(query, {
filters: { user_id: this.userId },
topK: this.topK,
}),
this.client.search(query, {
filters: {
AND: [{ user_id: this.userId }, { run_id: this.sessionId }],
},
topK: this.topK,
}),
]);
return {
user: userRes.results ?? [],
session: sessionRes.results ?? [],
};
}
buildSystemMessage({ user, session }) {
const blocks = [];
if (user.length) {
blocks.push(`【用户长期记忆】\n${user.map((m) => `- ${m.memory}`).join("\n")}`);
}
if (session.length) {
blocks.push(`【当前会话记忆】\n${session.map((m) => `- ${m.memory}`).join("\n")}`);
}
if (!blocks.length) return null;
return new SystemMessage(`${blocks.join("\n\n")}\n\n请结合以上记忆回答,勿编造。`);
}
async classifyAndPersist(userText, assistantText) {
const turn = [
{ role: "user", content: userText },
{ role: "assistant", content: assistantText },
];
const { write_user, write_session, reason } = await this.classifier.invoke([
new SystemMessage(CLASSIFIER_PROMPT),
new HumanMessage(`用户:${userText}\n助手:${assistantText}`),
]);
const written = [];
if (write_user) {
await this.client.add(turn, { userId: this.userId });
written.push("user");
}
if (write_session) {
await this.client.add(turn, { userId: this.userId, runId: this.sessionId });
written.push("session");
}
return { written, reason };
}
async clear() {
await this.client.deleteAll({ userId: this.userId });
await this.client.deleteAll({ userId: this.userId, runId: this.sessionId });
}
}
async function invokeWithMemory(agent, redisStore, mem0Store, sessionId, userText) {
const history = await redisStore.loadMessages(sessionId);
console.log(` ↳ Redis 加载 ${history.length} 条历史`);
const mem = await mem0Store.search(userText);
if (mem.user.length) console.log(` ↳ Mem0 用户层 ${mem.user.length} 条`);
if (mem.session.length) console.log(` ↳ Mem0 会话层 ${mem.session.length} 条`);
const memoryMsg = mem0Store.buildSystemMessage(mem);
const invokeMessages = [
...(memoryMsg ? [memoryMsg] : []),
...history,
new HumanMessage(userText),
];
const result = await agent.invoke(
{ messages: invokeMessages },
{ recursionLimit: 30 },
);
const redisMessages = messagesForRedis(result.messages);
const dropped = result.messages.length - redisMessages.length;
await redisStore.saveMessages(sessionId, redisMessages);
const ttl = await redisStore.ttl(sessionId);
console.log(
` ↳ Redis 写回 ${redisMessages.length} 条` +
(dropped ? `(过滤 ${dropped} 条 SystemMessage)` : "") +
` (TTL ${ttl}s)`,
);
const assistantText = String(result.messages.at(-1)?.content ?? "");
const { written, reason } = await mem0Store.classifyAndPersist(userText, assistantText);
console.log(` ↳ 分类: ${reason}`);
console.log(written.length ? ` ↳ Mem0 写入: ${written.join(", ")}` : " ↳ Mem0 未写入");
return { messages: result.messages, redisMessages, assistantText };
}
if (!process.env.MEM0_API_KEY || !process.env.OPENAI_API_KEY) {
console.error("需要 MEM0_API_KEY 与 OPENAI_API_KEY");
process.exit(1);
}
const redis = new Redis({ host: REDIS_HOST, port: REDIS_PORT, db: REDIS_DB });
const mem0 = new MemoryClient({ apiKey: process.env.MEM0_API_KEY });
redis.on("connect", () => console.log("✅ Redis 已连接"));
redis.on("error", (err) => console.error("❌ Redis 错误:", err.message));
try {
await redis.ping();
} catch {
console.error("Redis 未连接,请先执行: docker compose up -d redis");
process.exit(1);
}
const redisStore = new RedisMessageStore({
redis,
keyPrefix: KEY_PREFIX,
ttlSeconds: MEMORY_TTL,
});
const llmOpts = {
apiKey: process.env.OPENAI_API_KEY,
configuration: { baseURL: process.env.OPENAI_BASE_URL },
temperature: 0,
};
const model = new ChatOpenAI({ model: process.env.MODEL_NAME, ...llmOpts });
const classifier = new ChatOpenAI({
model: process.env.MODEL_NAME,
...llmOpts,
}).withStructuredOutput(memorySchema);
const mem0Store = new Mem0MemoryStore({
client: mem0,
userId: USER_ID,
sessionId: SESSION_ID,
topK: MEM0_TOP_K,
classifier,
});
const agent = createAgent({
model,
tools: [],
systemPrompt:
"你是会话助手。结合系统消息中的长期/会话记忆回答,中文简短。有对话摘要则据此继续。",
middleware: [
summarizationMiddleware({
model,
summaryPrompt,
trigger: { messages: 8 },
keep: { messages: 4 },
}),
],
});
console.log(`用户 ${USER_ID} | 会话 ${SESSION_ID}`);
console.log("输入 exit / quit / :q 退出;:clear 清空 Redis;:clear-mem0 清空 Mem0\n");
const rl = readline.createInterface({ input: stdin, output: stdout });
let prevCount = (await redisStore.loadMessages(SESSION_ID)).length;
try {
while (true) {
const userText = (await rl.question("你: ")).trim();
if (!userText) continue;
if (["exit", "quit", ":q"].includes(userText.toLowerCase())) break;
if (userText === ":clear") {
await redisStore.clear(SESSION_ID);
prevCount = 0;
console.log("已清空 Redis 短期记忆\n");
continue;
}
if (userText === ":clear-mem0") {
await mem0Store.clear();
console.log("已清空 Mem0 用户层与当前会话层\n");
continue;
}
const { redisMessages, assistantText } = await invokeWithMemory(
agent,
redisStore,
mem0Store,
SESSION_ID,
userText,
);
console.log("\n助手:", assistantText);
console.log(`Redis 消息数: ${redisMessages.length}`);
if (redisMessages.length < prevCount + 2) {
console.log(" ⚡ 已触发压缩");
}
prevCount = redisMessages.length;
console.log();
}
} finally {
rl.close();
}
await redis.quit();
/*
* 测试对话(复制进终端,先来 :clear-mem0 和 :clear)
*
* 一、寒暄
* 你好 / 在吗 / 谢谢
* → 纯客套,Mem0 不用记。
*
* 二、自我介绍
* 我叫小明,住在杭州,平时喜欢骑行和摄影。
* 我对海鲜过敏,出差尽量别安排沿海城市。
* → 换天聊还得知道的事,写 user 层。
*
* 三、这会儿在干嘛
* 这次我们先写 Q1 季度总结,大纲分三块:项目复盘、数据指标、下季度计划。
* 项目复盘里重点写 order-service 的 500 错误排查过程。
* → 只管这次聊天的事,写 session 层。
*
* 四、长期背景 + 手头活
* 我长期做后端开发,这次会话的任务是排查 payment-api 超时,先从 P99 日志看起。
* 另外我之后技术回答都希望带代码示例,这个一直记住。
* → 职业和当前任务可能两层都写,偏好那条走 user。
*
* 五、Redis 和 Mem0 各管啥
* 刚才说的 payment-api,超时阈值先假设 3 秒。
* 上一句我说的阈值是多少?
* → 刚说过的话 Redis 兜得住,不用等 Mem0。
*
* 重启 agent(别清 mem0)再问:我是谁?有什么过敏?
* → 新会话 Redis 是空的,user 层还能认出你。
*
* 六、聊多了会压缩(可选,连聊 8 轮以上)
* 继续完善 Q1 总结 / 把第二段改短 / 加个标题……
* → 终端会出现「已触发压缩」,老消息变摘要。
*
* 推荐顺序:清空 → 寒暄 → 自我介绍 → 当前任务 → 重启验 user → 清 mem0 验 session 没了
*/
三.总结
Mem0 是一层智能记忆系统,通过
user_id、run_id和agent_id管理用户、会话与 Agent 上下文;MemoryClient使用 Mem0 托管服务,Memory在自有基础设施运行;企业文档知识仍由 RAG 负责。
- Mem0 解决 AI 无状态导致的上下文丢失问题,使应用和 Agent 能跨会话记住相关信息。
- 用户记忆绑定
user_id,保存长期有效的事实和偏好。 - 会话记忆绑定
run_id,保存当前任务、进度和临时决策。 - Agent 记忆绑定
agent_id,用于隔离不同 Agent 的上下文,而不是保存角色提示词。 MemoryClient使用托管平台,Memory在自有基础设施运行。- Mem0 负责用户和 Agent 记忆,RAG 负责企业文档知识,两者可以组合使用。
- 生产项目还需增加授权、脱敏、审计、删除、权限控制和数据留存策略。
Mem0 不是通用文档 RAG 的替代品,也不是 Agent 配置中心,而是位于业务应用与模型之间的智能记忆层。它负责事实抽取、记忆存储、更新、冲突处理、范围隔离、检索和结果整理;企业文档检索仍应由专门的 RAG 系统承担。