ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Dify机器人记性差?用mem0实现跨会话长期记忆,记住用户饮食偏好

Dify机器人记性差?用mem0实现跨会话长期记忆,记住用户饮食偏好 前两天我同事在我搭的Dify机器人里连续问了好几天的晚饭搭配机器人很贴心地记住了“不吃香菜、最近在减脂、晚餐要低碳水”。结果到了第二天下午他在一个新会话里打开同一个机器人只问了一句“今天晚饭给个建议”机器人直接把前一晚的推荐原封不动复制了过来——香菜和碳水全踩雷了。问题出在哪说白了Dify默认的会话记忆只在同一个会话内有效会话一关、或者你开启一个新对话之前的聊天记录就归零了。它不具备“跨会话的用户级长期记忆”。而饮食习惯这种东西恰恰是最需要被长期记住的你可能今天不提香菜明天不提减脂但你要的是AI在下一次对话里仍然记得。这个问题靠Dify原生能力解决不了得给它加一层专门的记忆组件——mem0。这篇文章从零开始完整跑一遍怎么把mem0用FastAPI封装成一个独立服务怎么在Dify里注册成自定义工具怎么编排一个“查记忆—注入提示词—写回记忆”的工作流最后落到饮食习惯这个真实场景里看效果。全程提供可直接复制的Python代码、OpenAPI Schema和提示词模板我用的是Dify 1.17.x社区版和mem0的Python库版本差别不影响核心思路照着做就能跑通。1. 为什么需要mem0从“会话记忆”升级到“用户长期记忆”1.1 Dify自带记忆的局限Dify本身并不是没有记忆能力。你在应用编排里开启“对话记忆”它就能在同一个conversation_id下记住上下文实现多轮对话。但这种记忆的本质是“短期工作记忆”和用户长期偏好完全是两码事。举个例子同一个用户昨天问了“减脂期晚饭吃什么”今天新开一个会话问“中午吃食堂怎么选”对Dify来说这是两个完全独立的会话上一轮的信息一点都带不过来。你就算手动上“对话历史变量”也只能在单会话内做文章。还有不少人会把“记忆”和“知识库”混在一起。Dify的知识库是静态文档你把菜谱、营养指南导入进去它就能基于这些内容回答。但用户今天说了一句“我不吃香菜”这是动态产生的个人偏好不会自动进知识库也没法被后续会话检索到。要让AI记住这类信息必须有一个专门的记忆层来处理。1.2 mem0的工作机制mem0是一个开源的长周期记忆组件官网给自己的定位是“Memory Layer for AI Agents”。它的工作方式分成三步第一步是提取extraction。你扔给它一段非结构化文本比如“我不吃香菜下次点餐帮我注意一下”它调用底层LLM把这句对话提炼成一条干净的事实记忆“用户不喜欢香菜”。第二步是存储storage。提炼出的记忆会做embedding向量化然后写入向量数据库同时保留原始文本、用户ID、时间等信息。第三步是检索retrieval。当用户再次提问时应用根据当前问题去向量库里做语义检索把相关度最高的记忆捞出来注入到这次对话的上下文里。这整个流程不是“全文照抄”而是抽取事实。mem0内部会判断哪些话是值得长期记住的偏好哪些只是临时指令。比如“帮我查一下明天的天气”就不会被存成长期记忆但“我早餐喜欢喝燕麦粥不加糖”会被沉淀下来。1.3 整体架构选型为什么包一层FastAPI服务在开始写代码之前先想清楚架构。Dify的自定义工具本质上是“通过HTTP调用外部API”它不关心API背后的实现语言是什么。而mem0是一个Python库不能直接在Dify的界面里跑起来。所以我们需要用FastAPI把mem0包成一个独立的HTTP服务提供两个最核心的接口查询记忆GET /memories?user_idxxx返回该用户的历史偏好列表写入记忆POST /memories/add接收text和user_id调用mem0提取并保存新偏好有人可能会问为什么不用Dify的代码节点直接import mem0Dify工作流里的代码节点是沙箱环境能用的第三方包很有限直接在代码节点里跑mem0这种带向量库和LLM调用的重型组件既不灵活也不稳定。独立服务的好处是职责清晰mem0只管记忆存取Dify只管编排和交互两边通过HTTP解耦后续想换向量库、换模型、加接口都很方便。整体数据流是这样的用户在Dify对话界面输入内容工作流先把用户ID传给mem0服务拉取历史偏好再把偏好拼进系统提示词让大模型带着记忆生成回答回答结束后把用户这次的输入再次传给mem0服务由它异步提取潜在的长期偏好并存储。用户的每次新会话都能从记忆层“重新想起来”之前聊过的饮食习惯。2. 环境准备与mem0服务端部署2.1 依赖清单和前置条件先准备环境。建议Python 3.10及以上版本因为mem0对Python版本有要求太老的版本装不上。创建一个项目目录放一个requirements.txtfastapi uvicorn pydantic mem0ai然后执行pip install -r requirements.txt装完之后需要准备一个可用的LLM API和一个Embedding API。mem0在做记忆提取的时候要调LLM在做向量化的时候要调Embedding模型。最简单的方案是用一个OpenAI兼容接口现在很多国产大模型也都提供OpenAI兼容的endpoint设置api_base就行。如果你本地有Ollama和Qwen这类模型也可以让mem0完全离线跑后面我会给离线配置片段。2.2 用FastAPI封装mem0服务完整代码下面这个文件我命名为 mem0_server.py它把mem0的核心功能封装成了两个接口查询用户全部记忆、追加新记忆并自动提取偏好。完整代码如下import os from fastapi import FastAPI from pydantic import BaseModel from mem0 import Memory app FastAPI(titleMem0 Memory Service) # OpenAI 兼容接口配置通过环境变量控制 # 如果你的模型服务兼容 OpenAI 格式直接改 api_base 即可 memory Memory.from_config({ llm: { provider: openai, config: { model: os.getenv(LLM_MODEL, gpt-4o-mini), api_key: os.getenv(OPENAI_API_KEY), api_base: os.getenv(OPENAI_API_BASE, https://api.openai.com/v1), } }, embedder: { provider: openai, config: { model: os.getenv(EMBEDDER_MODEL, text-embedding-3-small), api_key: os.getenv(OPENAI_API_KEY), api_base: os.getenv(OPENAI_API_BASE, https://api.openai.com/v1), } }, # 轻量测试用 sqlite生产环境建议换 qdrant vector_store: { provider: sqlite, config: { collection_name: mem0_diet } } }) class AddRequest(BaseModel): text: str user_id: str class AddResponse(BaseModel): status: str memories: list[str] [] app.post(/memories/add) def add_memory(req: AddRequest): 写入一条新的对话内容mem0 内部会调用 LLM 自动抽取长期记忆。 返回抽取出的记忆列表方便排查。 try: result memory.add(req.text, user_idreq.user_id) extracted [] for item in result.get(results, []): if isinstance(item, dict) and item.get(memory): extracted.append(item[memory]) return AddResponse(statusok, memoriesextracted) except Exception as e: # 实际生产环境可以把异常细节打到日志这里直接返回字符串 return {status: error, message: str(e)} app.get(/memories) def get_memories(user_id: str): 查询某个用户的全部长期记忆以字符串数组返回。 这样 Dify 那边拼接提示词非常方便。 data memory.get_all(user_iduser_id) results data.get(results, []) if isinstance(data, dict) else [] memories [] for item in results: if isinstance(item, dict) and item.get(memory): memories.append(item[memory]) return { user_id: user_id, memories: memories } if __name__ __main__: import uvicorn uvicorn.run(app, host0.0.0.0, port8000)如果你想把embedding和LLM都换成Ollama本地模型只需要把Memory.from_config里的llm和embedder两块改成下面这样memory Memory.from_config({ llm: { provider: ollama, config: { model: qwen2.5:7b, # 负责记忆提取的对话模型 ollama_base_url: http://localhost:11434 } }, embedder: { provider: ollama, config: { model: nomic-embed-text, # 负责向量化的嵌入模型 ollama_base_url: http://localhost:11434 } }, vector_store: { provider: sqlite, config: { collection_name: mem0_diet } } })这里有个经验之谈记忆提取的质量完全取决于你配的这个LLM。用gpt-4o-mini级别的模型在中文场景下表现比较稳如果换成太小的模型提取出的记忆可能会有明显语病或信息缺失。embedding模型也一样选中文支持不好的模型后面检索准确率会很难看。2.3 启动服务并验证接口在项目目录下执行export OPENAI_API_KEYsk-xxxx export OPENAI_API_BASEhttps://your-endpoint/v1 python mem0_server.py服务默认跑在8000端口。启动后先用curl验证写入接口curl -X POST http://localhost:8000/memories/add \ -H Content-Type: application/json \ -d {text: 我不吃香菜点餐的时候注意一下, user_id: zhangsan}正常会返回类似这样的结果memories数组里就是抽取出的记忆{status: ok, memories: [用户不喜欢香菜]}再验证查询接口curl http://localhost:8000/memories?user_idzhangsan返回{user_id: zhangsan, memories: [用户不喜欢香菜]}能走到这一步说明mem0服务本身已经通了。如果调用时报401、连接超时之类先检查OPENAI_API_KEY和OPENAI_API_BASE是否正确再确认embedding模型名是否被你的API服务支持。3. 在Dify平台中配置mem0自定义工具3.1 自定义工具的入口和操作路径Dify对这类外部API的统一封装方式叫“自定义工具”。在Dify 1.17.x社区版里顶部菜单栏找到“工具”点进去后选“创建自定义工具”然后选择“OpenAPI Schema”模式。这里说一下不同版本叫法可能略有差异老版本里可能叫“插件”新版本把插件市场和自定义工具合并到了“工具”入口下。位置变了但本质一样Dify会按你提供的OpenAPI规格把HTTP接口包装成工作流和Agent里可以直接拖拽使用的工具节点。OpenAPI Schema本质上就是一份描述HTTP接口的YAML或JSON文件。Dify读这份文件就知道有哪些接口、每个接口要传什么参数、返回什么结构。我们只需要把mem0服务封装成两个操作查询记忆和写入记忆。3.2 完整OpenAPI Schema代码下面是完整的YAML配置直接复制到Dify的OpenAPI Schema输入框里即可openapi: 3.1.0 info: title: Mem0 Memory API description: 用户长期记忆服务提供记忆查询和记忆写入 version: 1.0.0 servers: - url: http://192.168.1.100:8000 paths: /memories: get: operationId: memory_query summary: 查询用户历史记忆 parameters: - name: user_id in: query required: true description: 唯一用户ID用于隔离不同用户的记忆 schema: type: string responses: 200: description: 查询成功返回该用户的记忆数组 content: application/json: schema: type: object properties: user_id: type: string memories: type: array items: type: string /memories/add: post: operationId: memory_add summary: 写入用户新的对话内容并提取长期记忆 requestBody: required: true content: application/json: schema: type: object required: - text - user_id properties: text: type: string description: 用户本次输入的原始文本 user_id: type: string description: 唯一用户ID responses: 200: description: 写入成功 content: application/json: schema: type: object properties: status: type: string memories: type: array items: type: string注意一个关键点servers.url不要填localhost或127.0.0.1。因为Dify如果跑在Docker容器里容器内的localhost指向的是Dify容器自己永远访问不到宿主机上的mem0服务。这里我用的是局域网IP示例你要替换成mem0服务实际所在机器的IP。如果Dify跑在Docker Desktop的Mac或Windows上可以试http://host.docker.internal:8000Linux环境就直接填宿主机局域网IP。3.3 测试工具连通性保存Schema后Dify会要求你做一次“测试”。选择一个操作比如memory_query输入一个user_id点击运行。如果返回了你在curl里看到的JSON格式数据说明Dify已经能正常访问mem0服务。如果测试超时或者报错优先排查三件事网络能不能通、服务有没有启动、URL填的对不对。可以先用命令行试一下宿主机能不能curl通mem0服务再确认Dify容器网络能不能通宿主机IP。整个链路是Dify容器 - 宿主机IP:8000 - mem0服务任何一环断掉都会导致测试失败。测试通过之后这个工具就出现在你的工具列表里了。接下来就可以在工作流里正式编排业务逻辑。4. 工作流编排让AI真正记住饮食习惯4.1 工作流节点设计与整体思路在Dify里实现“让AI带着记忆回答问题”有几种方案我直接说结论生产环境最稳的是“工作流显式编排”而不是把工具丢给Agent让它自己决定调用时机。Agent模式看着灵活实际有一个问题模型不一定每次都记得调用记忆查询工具。你问它“明天吃什么”它可能直接凭常识开答压根不去查记忆。一旦漏查用户体感就是“你把我忘了”体验非常割裂。工作流模式则是强制每一步都执行开场先查记忆把记忆注入提示词再回答最后写回新偏好。流程固定没有模型自主决策的随机性。工作流核心节点如下节点作用关键参数开始接收用户输入和用户标识query、user_id记忆查询工具调用mem0的GET /memoriesuser_idLLM带着记忆生成回答系统提示词拼接记忆记忆写入工具把本轮用户输入写入mem0textquery、user_id结束返回LLM的回答输出answer我的建议是先跑通这个最简闭环再根据你的业务需要加知识库检索、再加条件判断。4.2 创建开始节点和记忆查询节点新建一个空白工作流。开始节点里手动添加两个变量query文本类型表示用户这次输入的内容和user_id文本类型表示当前用户的唯一标识。这里有个实际经验user_id千万别省略也别让它空着。mem0是靠user_id区分不同用户记忆的如果你不传、或者每次都传同一个固定值所有人的记忆就会串在一起。前端接入时用登录用户ID、微信openid这类稳定标识作为user_id最合适。接着添加一个工具节点选择刚才创建的mem0工具操作选择memory_query参数user_id填开始节点里的{{#start.user_id#}}。这个节点会返回一个memories数组里面是该用户的全部长期记忆。4.3 LLM节点把记忆注入系统提示词重点来了。LLM节点的职责不是直接回答用户问题而是先“带着记忆”再回答。系统提示词写成这样你是用户的饮食营养助手擅长根据用户的口味偏好和个人目标提供建议。 以下是该用户长期记忆中的饮食习惯和偏好回答时必须优先遵守 {{#memory_query_tool.memories#}} 如果上面记忆里已经有了明确偏好不要再反复询问用户是否吃香菜、是否忌口直接默认遵守。 如果用户当前的问题和记忆没有任何关系正常回答即可。 用户当前输入{{#start.query#}}这是整个工作流最核心的注入点。记忆查询工具返回的数组在Dify里会自动拼接到提示词中。模型看到这些记忆回答时自然就会避开用户不吃的东西、贴合用户当前的饮食目标。注意Dify不同版本对变量引用的写法略有差异你在界面上用“变量引用”按钮选择对应节点输出即可不用手敲。拼完提示词后LLM节点的输出变量命名为answer方便后续引用。4.4 记忆写入节点让偏好沉淀下来回答完不是结束还要把用户这次说的话写回mem0。在LLM节点后面跟一个工具节点操作选择memory_add参数这样填text填{{#start.query#}}也就是用户这次的原始输入user_id填{{#start.user_id#}}保持和查询时一致有人会担心把用户每轮输入都写进去会不会存一堆垃圾mem0在这块的过滤能力其实还可以。它内部会调用LLM判断哪些内容值得长期存储像“帮我算一下热量”这种临时请求一般会被过滤掉而“我不吃香菜”“我在减脂”这类个人偏好会被提取入库。不过“直接写原文”是一种低成本方案它在准确性上会有一些损耗而且每轮都调用一次mem0.add会多消耗一次LLM API调用。如果你对成本和精度更敏感第6章我会给一个“先判断再写入”的进阶方案。把工具节点连起来之后整个工作流的路径是开始 - 记忆查询 - LLM - 记忆写入 - 结束。结束节点输出LLM的answer字段即可。4.5 饮食场景完整演示设计一个真实场景来跑通全流程。假设用户有三段跨会话输入会话用户输入mem0中沉淀的记忆后续回答的影响第一次我不吃香菜点餐注意一下用户不喜欢香菜之后所有推荐自动避开香菜第二次最近在减脂晚饭想吃清淡点牛肉还是鸡胸肉用户处于减脂期晚餐偏好清淡高蛋白推荐菜品时优先低脂高蛋白第三次周末是欺骗餐我想吃火锅用户周末允许吃欺骗餐喜欢吃火锅周末推荐时可以放开热量限制第三天新会话里用户只输入一句“今天晚饭给个建议”此时工作流先从mem0查询到这三条记忆拼进系统提示词LLM生成的回答就可能是这样按你平时的习惯今晚还是走减脂餐路线。我建议主食换成粗粮蛋白质选鸡胸肉或虾仁蔬菜可以多拿一点注意别碰香菜的配菜就好。明天就是周末欺骗餐了火锅店可以提前在大众点评上排个号。这个回答背后没有一句是用户当天输入的全部来自mem0里沉淀的跨会话记忆。这就是“用户级长期记忆”的价值不用每次重复自己的偏好AI自动就记得。5. 常见问题与排查技巧实录5.1 记忆查询总是返回空数组如果Dify工作流里查不到记忆先跳过Dify直接用curl调mem0服务用同一个user_id写入再查询确认服务本身没问题。服务正常的话问题多半出在user_id不一致上——写入和查询各用了不同的user_id或者user_id在接口传参时空值了。再有一个隐蔽问题你在Dify的工具参数里填的是{{#start.user_id#}}但工作流的开始节点里根本没定义这个变量运行时就变成了空字符串。建议在开始节点加一个默认值或者在工作流里先用一个“代码/赋值节点”把user_id处理干净再传给mem0。5.2 多个用户的记忆串在了一起典型的user_id没有传对比如所有用户共用了一个固定字符串或者前端没有把真正的用户标识传进来。排查方法很简单在mem0服务的查询接口里把返回的user_id和每条memory的user_id字段打出来看看是不是被污染了。我习惯写一个小工具页面专门用来按user_id查看和删除mem0里的记忆排查串数据时非常有用。生产环境强烈建议做一层用户标识校验接口层就拒绝空user_id。5.3 中文检索不准、记忆召回率低中文场景下embedding模型的选择直接影响召回效果。text-embedding-3-small日常够用但如果你的用户群体有大量口语化表达或者专业术语很多建议换成中文效果更好的模型比如通义的text-embedding-v3、开源的bge-m3等。另外mem0默认的查询是语义检索如果用户说的是“明天中午吃啥”语义和“不喜欢香菜”这条记忆距离确实比较远召回不到也很正常。这时候两个思路一是常用偏好量不大直接拉全部记忆拼进去也就是上面我用的get_all方案二是如果你记忆库很大要用search接口让检索词更具体一些。5.4 工具调用超时或回答卡顿mem0.add内部要先调LLM提取记忆再调embedding向量化整个过程可能耗时几秒到十几秒。如果你的大模型API本身响应慢整条工作流跑完会明显感觉到卡顿。解决策略有两个。第一模型选快一点的比如gp-4o-mini类的小模型足够干提取记忆这种活。第二改变写入时机——不要让“写入记忆”阻塞回答Dify工作流支持把记忆写入节点放到回复输出之后异步执行或者只在你判断出用户确实说了新偏好时才触发写入。成本高这个问题其实就是靠这种方式治本的。5.5 记忆越积越多但关键信息找不回mem0本身有去重机制但用户如果多次用不同说法表达同一个偏好依然可能存成多条相似记忆。时间一长检索结果里可能混入大量过时信息。治本的办法是定期做记忆清理。mem0的delete接口支持按memory_id删除单条记忆我封装服务时都会加一个DELETE /memories接口配合一个简单的管理页面定期进去看一眼哪些记忆过时了、哪些重复了手动清理。饮食场景还有一个特殊情况用户的偏好会变比如减脂期结束了、开始增肌了旧记忆如果不删新老偏好会打架。这时候需要在工作流里给用户一个主动“更新偏好”的入口明确告诉AI“之前的记忆作废按最新的来”。6. 进阶优化与实操心得6.1 别让Agent自由决定“要不要查记忆”我在前面提到不太建议直接把两个mem0工具丢给Agent节点让模型自己选。但如果你确实需要用Agent模式处理更复杂的场景可以这样约束它在Agent的系统提示词里明确写清楚工具的使用条件比如“遇到涉及用户偏好、忌口、目标、生活习惯的问题必须先调用memory_query当用户明显提出了新的个人偏好时才调用memory_add”。实测下来聪明的模型大部分时候能遵守但偶尔还是会漏。重要场景我仍然建议用工作流强制节点。如果选了工作流模式我推荐再加一个记忆写入的判断节点而不是每轮直接调add。实现方式是在LLM节点后面加一个“偏好判断”LLM节点让它输出一个JSON请判断用户这句话是否包含值得长期记住的个人偏好例如饮食口味、忌口、减脂增肌目标、作息习惯等。 如果是输出{has_preference: true, preference: 一句话概括这个偏好} 如果不是输出{has_preference: false} 只输出JSON不要解释。然后把“偏好判断”的输出接到条件分支节点只有has_preference为true时才把preference字段写入mem0。这样做的好处是大幅减少mem0.add的调用次数省成本、降延迟而且写入的记忆更加干净。这算是我自己在实际项目中踩过坑之后最推荐的做法。6.2 把“饮食习惯”和其他偏好分开存储mem0支持在add时传metadata这是一开始就要想好的设计。例如memory.add( 我最近在减脂晚饭要低碳水, user_idzhangsan, metadata{category: diet} )后面查询时就可以按category过滤比如查饮食相关的记忆过滤掉用户的工作偏好、娱乐偏好等其他信息。用mem0做更精细的智能化应用时用户画像一定是多维的越早分开存后面越省事。Dify工作流里也可以把“用户偏好类型”设计成一个变量让用户自己选择或让LLM自动打标签再把标签作为metadata传给mem0接口。这一步属于锦上添花但能让你的记忆体系在长周期使用中保持高信噪比。6.3 最后分享一个我的实际感受这套“Dify mem0”的组合我实际跑了一个多星期最大体会有两点第一记忆这层不要贪多沉得越久、越精准的记忆才越有价值宁可不写也不要乱写第二跨会话记忆一旦生效用户体验的提升是肉眼可见的用户不需要重复交代自己的忌口和偏好那种“被记住”的感觉会让整个机器人从“AI问答工具”变成“懂我的私人助手”。如果你也在Dify上被“记性差”困扰建议先照这篇文章把最小闭环跑通一个mem0服务两个API接口一个四节点工作流。整个链路大概一个下午就能搞定。跑通之后再慢慢加分类、加清理、加条件判断你的AI助手就能真正记住用户的每一次生活偏好。
返回列表