一、引言:为什么"每次从零开始"的 Agent 走不远
先看三个真实的线上场景:
- 用户上个月刚在智能客服里报修过"家里宽带频繁掉线",这个月又来问"上次那个问题处理得怎么样了",Agent 一脸茫然:"您之前没有咨询过任何问题哦。"
- 销售助理 Agent 每天和同一批客户对话,却记不住客户是"价格敏感型"还是"看重服务",每次都要重新问一遍需求,客户体验像在跟一个失忆的人聊天。
- 数据分析 Agent 连续追问模式下,前几轮已经确认了"只看华东区 Q3 数据",第四轮问题稍微绕一点,它就开始把全国数据混进来算。
这三个问题的根源是同一个:主流 LLM 应用默认是"无状态"的------每次请求都是独立的,模型不记得上一轮说过什么,更不记得上周说过什么。我们在用 Dify 搭 Agent 时,默认拿到的是一个"金鱼记忆"的机器人。
但企业级 Agent 的核心价值恰恰在于连续性 :它需要记住用户身份与偏好、记住对话进度、记住历史决策,甚至把记忆沉淀成团队可复用的知识。本文就基于华为云 MaaS 平台的 DeepSeek-V3/R1 商用推理服务与 Flexus X 实例一键部署的 Dify 平台,完整演示如何构建一套分级记忆系统 :短期记忆(会话内)→ 中期记忆(会话变量)→ 长期记忆(向量化用户档案),并讲清楚 R1 这类推理模型在记忆抽取、总结、检索决策中不可替代的价值。
二、先建认知:Agent 记忆的分类学
动手之前,先把"记忆"这个词拆清楚。2025 年以来"记忆工程(Memory Engineering)"成为 AI 工程的热词,但很多文章把不同层次的东西混在一起讲。这里给出一个可落地的分类框架。
2.1 按时间尺度分:短期、中期、长期
| 记忆类型 | 时间尺度 | 典型载体 | 容量特征 | 丢失成本 |
|---|---|---|---|---|
| 工作记忆 | 单次推理内 | 模型上下文窗口 | 几十万 token 上限 | 低(本轮对话内) |
| 短期记忆 | 单次会话内 | Dify 会话历史、消息摘要 | 受上下文窗口约束 | 中(用户要重述) |
| 长期记忆 | 跨会话 | 会话变量、向量数据库、知识库 | 近乎无限 | 高(客户流失感) |
- 工作记忆(Working Memory):模型正在"思考"时占用的上下文,比如 R1 的思维链、当前轮次的所有输入。它随请求结束而清空。
- 短期记忆(Short-term Memory):同一会话内多轮对话的历史。Dify 默认会把对话历史拼进上下文,这就是最基础的短期记忆。
- 长期记忆(Long-term Memory):跨会话、跨用户会话持久化的信息,比如"用户是华东区大客户""用户偏好表格而非图表"。这是本文重点。
2.2 按存取方式分:显式与隐式
- 显式记忆(Explicit) :结构化存储、明确读写。比如会话变量里存一个 JSON 对象
{"region": "east", "budget_sensitive": true}。优点是确定性强、可审计、可更新。 - 隐式记忆(Implicit):以自然语言或向量形式存在,靠模型或检索"唤醒"。比如把历史对话做向量化存入数据库,下次用语义检索捞回相关片段。优点是覆盖全,缺点是可能检索不准。
成熟方案通常是两者结合:高频、关键、需要强一致性的信息走显式;长尾、模糊、开放式的经验走隐式。
2.3 记忆工程生态速览
- Mem0 :开源记忆层,核心是
add / search / update / delete四个操作,从对话里抽取事实性记忆,支持向量存储后端,有 Dify 插件生态; - Letta(原 MemGPT):把"记忆分层管理"做成代理框架,核心思想是让模型自己决定什么时候把信息写进记忆、什么时候遗忘;
- 腾讯 TencentDB Agent Memory:2025 年开源的 Agent 记忆系统,主打"让 AI 不再每次从零开始",Star 数增长极快,思路是把记忆做成独立的数据服务;
- Dify 原生能力:会话记忆、消息摘要、会话变量、知识库------本文的主线,零额外服务也能跑通。
这些生态的出现说明一个趋势:记忆正在从"模型能力"变成"工程组件"。对大多数业务场景,先用好 Dify 原生的四件套,再按需引入 Mem0 这类记忆层,是性价比最高的路径。
三、基础设施:Flexus X 上的一键部署与记忆选型
3.1 部署架构
整个体系仍然跑在 Flexus X 实例上,架构非常收敛:
┌────────────────────────── Flexus X 实例 ──────────────────────────┐
│ Docker Compose │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Dify (Agent 编排) │ │
│ │ · 会话记忆 / 消息摘要 / 会话变量 ← 短期+中期记忆 │ │
│ │ · 知识库(向量检索) ← 显式长期记忆(公共) │ │
│ │ · 用户档案库(向量 + 结构化) ← 显式长期记忆(个性化) │ │
│ └───────────────┬─────────────────────────────────────────────┘ │
│ │ DeepSeek-R1/V3 (华为云 MaaS 推理服务) │
│ ▼ │
│ R1: 记忆抽取 / 摘要压缩 / 检索决策 V3: 常规回复生成 │
└──────────────────────────────────────────────────────────────────┘
- Dify:通过华为云 Flexus X 一键部署方案拉起,负责工作流编排与记忆管理;
- DeepSeek-V3/R1 :华为云 MaaS 平台商用推理服务,按需计费。R1 承担"记忆写入侧"的重活 (抽取、总结、判断该不该记),V3 承担"记忆读取侧"的轻活(基于已有记忆生成回复),各取所长;
- 向量存储:起步阶段直接用 Dify 内置知识库的向量化能力;规模上来后,用户档案可单独落到自建向量库(如 pgvector / Milvus),Flexus X 上均有成熟部署方案;
- Nginx:统一入口,记忆数据仅内网可达,配合脱敏策略防止隐私外泄。
3.2 为什么记忆分层要"混合模型"
纯用 V3 也能做记忆抽取,但实测两个痛点:一是长对话里的隐含信息容易被漏 (比如用户没直说"预算有限",但从"有没有便宜点的方案"这类话里能推断出价格敏感);二是抽取出的记忆质量不稳定,该记的没记、不该记的记了一堆噪音。
R1 的强项恰好在这:推理型模型在"从对话中推断事实""判断信息是否值得长期保存""决定检索策略"这类需要权衡的任务上,明显优于直出型模型。而真正回复用户时,V3 的低延迟低成本优势又回来了。所以本文的默认分工是:
写入侧(记忆生成) : R1 → 抽取事实 / 压缩摘要 / 判断价值
存储侧(记忆管理) : 规则 + 会话变量 / 向量库
读取侧(记忆使用) : V3 → 结合记忆生成自然回复
这套分工把推理模型的"贵"花在刀刃上------记忆写入频率远低于回复生成频率,整体成本几乎不受影响。
四、Dify 原生记忆四件套:先把短期和中期记忆用起来
动手写工作流之前,先明确 Dify 开箱即用的四个记忆能力。
4.1 会话记忆(Chat History):默认的短期记忆
Dify 的 Chatflow 和 Agent 应用默认携带对话历史(sys.query 之外,系统会自动把前面的用户消息和助手回复作为上下文)。你什么都不用配,Agent 就能"记得"本轮聊过什么。
但要注意一个工程细节:对话历史是"全量拼接"的。聊到 30 轮时,前面 29 轮全进上下文,Token 开销线性上涨,R1 的推理成本更是按输入 token 计费------所以纯靠会话历史走不远,必须压缩。
4.2 消息摘要(Message Summary):对话压缩器
Dify 的"消息摘要"功能会在对话超过阈值轮数后,调用 LLM 把早期历史压缩成一段摘要,替代原始轮次进入上下文。这是短期记忆向"准中期"过渡的关键:既保留要点,又控制成本。
4.3 会话变量(Conversation Variables):真正的跨轮次状态
会话变量是 Dify 0.6+ 的核心能力:在同一个会话内,变量值可以被不同节点读写,并跨多轮对话保持。它天然适合存:
- 用户画像(地区、角色、偏好);
- 对话中间状态(已确认的筛选条件、当前步骤);
- 需要强一致性的业务字段(订单号、工单号)。
会话变量通过 变量赋值器(Variable Assigner) 节点写入,通过 #sys.conversation.xxx# 引用。它是本文"中期记忆"的基石。
4.4 知识库(Knowledge):公共长期记忆
知识库把文档切片向量化,检索时按语义召回。它解决的是"团队/企业的公共知识"这个层面的长期记忆------产品手册、政策文件、历史工单。这一层 Dify 原生支持得最好,也是之前系列文章反复讲过的能力,本文不再展开,重点放在它覆盖不到的"个性化记忆"上。
五、实战一:用会话变量实现用户画像长期记忆
场景:智能客服 Agent,要求记住每个会话内用户提到的关键信息(称呼、偏好、业务背景),并在后续回答中自然使用。
5.1 工作流设计
Chatflow 节点编排如下:
开始 → 知识检索(命中FAQ) → LLM1: 记忆抽取(R1) → 变量赋值器(写画像)
→ LLM2: 生成回复(V3, 读取画像) → 结束
关键节点说明:
- 记忆抽取节点(LLM1,模型选 R1):输入当前轮用户消息 + 现有画像(会话变量),输出"需要新增/更新的画像字段"。用结构化输出约束,保证变量可解析。
- 变量赋值器 :把 LLM1 输出的 JSON 合并进
user_profile会话变量。 - 回复生成节点(LLM2,模型选 V3) :Prompt 里引用
#sys.conversation.user_profile#,让回复带上个性化。
5.2 记忆抽取的 Prompt 模板
抽取节点的系统提示词是整套记忆系统的灵魂,给出一个经实战调优的模板:
你是一个用户画像抽取器。根据用户最新消息和现有画像,提取值得长期记住的信息。
现有画像(JSON):
{{#sys.conversation.user_profile#}}
用户消息:
{{#sys.query#}}
要求:
1. 只抽取"未来可能有用"的事实:称呼、身份、地区、偏好、预算倾向、业务背景、待办事项。
2. 不做主观评价,不记录情绪化表达。
3. 与现有画像重复的信息不要重复输出。
4. 输出严格 JSON:{"add": {"字段": "值"}, "update": {"已有字段": "新值"}, "remove": ["要删除的字段"]}
5. 没有新信息时输出:{"add": {}, "update": {}, "remove": []}
注意第 4 条:结构化输出是记忆系统的生命线。Dify 的"结构化输出"能力(JSON Schema 约束)在这里配合 R1 使用,能显著降低解析失败率------这也是上一期"结构化输出"系列文章的技术在记忆场景的复用。
5.3 变量合并的兜底逻辑
变量赋值器本身是"整体覆盖"语义,直接覆盖会丢历史字段。所以在赋值器前放一个 Code 节点 做合并:
import json
def main(profile_json: str, extraction_json: str) -> dict:
profile = json.loads(profile_json or "{}")
extract = json.loads(extraction_json)
for k, v in extract.get("add", {}).items():
profile[k] = v
for k, v in extract.get("update", {}).items():
profile[k] = v
for k in extract.get("remove", []):
profile.pop(k, None)
return {"merged_profile": json.dumps(profile, ensure_ascii=False)}
这个 Code 节点是所有记忆写入的"守门员",后面实战二、三的写入也复用它,只是数据源不同。
5.4 效果对比
| 维度 | 无记忆 | 有会话变量画像 |
|---|---|---|
| 用户重复说明背景 | 每次都要 | 一次都不用 |
| 个性化回复 | 无 | 称呼、偏好自然带入 |
| 追问澄清次数 | 多 | 显著减少 |
| Token 成本 | 每轮重复携带背景 | 只携带画像 JSON |
实测在客服场景下,启用画像后平均对话轮数下降约 25%,用户满意度(NPS 代理指标)提升明显------记忆的价值不只是"感觉聪明",而是实打实的业务指标。
六、实战二:R1 驱动的消息摘要与记忆沉淀
会话变量解决了"会话内"的状态保持,但会话结束、用户明天再来,画像就丢了------因为会话变量只活在单次会话里。要跨会话,得把记忆"沉淀"出去。这里给两条路径,先讲纯 Dify 路径。
6.1 纯 Dify 路径:知识库 + 档案写入工作流
思路:把"用户画像快照"作为一条文档,在会话结束时写入知识库,下次新会话先检索"这个用户的历史画像"。
实现方式:
- 会话结束钩子 :在 Chatflow 末尾加一个"归档"分支,把
user_profile变量序列化成结构化文本; - 写入知识库 :Dify 知识库支持通过 API 追加文档(
POST /datasets/{id}/document/create_by_text),用 HTTP 请求节点或外部定时任务把快照写入"用户档案库"; - 新会话检索:对话开始前,先用"知识检索"节点按用户 ID 检索档案库,命中后把历史画像注入上下文。
伪代码(外部同步脚本):
import requests
DIFY_BASE = "https://your-dify.example.com/v1"
API_KEY = os.environ["DIFY_DATASET_API_KEY"]
def archive_profile(user_id: str, profile: dict):
"""把用户画像快照写入 Dify 知识库(用户档案库)"""
content = f"用户ID: {user_id}\n画像快照时间: {now()}\n" + \
json.dumps(profile, ensure_ascii=False, indent=2)
requests.post(
f"{DIFY_BASE}/datasets/{DATASET_ID}/document/create_by_text",
headers={"Authorization": f"Bearer {API_KEY}"},
json={"name": f"profile-{user_id}-{now()}", "text": content}
)
优点:零额外基础设施,复用知识库的向量检索;缺点:画像更新会产生大量快照文档,检索噪音上升,需要按用户 ID 做元数据过滤(知识库检索支持 metadata 过滤,务必开启)。
6.2 引入记忆层的路径:Mem0 与 Dify 集成
如果画像更新频繁、检索精度要求高,建议引入 Mem0 这类专用记忆层。它的核心思想是把记忆当作有增删改查的数据库,而不是"文档堆"。
from mem0 import Memory
m = Memory.from_config({"llm": {"provider": "deepseek",
"config": {"model": "deepseek-chat",
"api_base": MaaS_ENDPOINT,
"api_key": MaaS_API_KEY}}})
# 写入:从对话中抽取记忆
m.add(f"用户{user_id}说: 我预算有限,优先性价比方案",
user_id=user_id, metadata={"source": "chat", "ts": now()})
# 检索:新会话开始时召回
memories = m.search("这个用户有什么偏好?", user_id=user_id)
# → [{'memory': '用户预算有限,优先性价比方案', 'score': 0.87, ...}]
Dify 侧通过 HTTP 请求节点或自研工具调用 Mem0 的 API,把召回的记忆拼进回复 Prompt。这条路径的优势:
- 去重与更新:同一事实变化时更新而非堆积;
- 相关性召回:按语义排序,而非全量注入;
- 遗忘控制:自带 TTL / 手动删除接口,满足隐私合规。
选型建议:画像字段少于 10 个、更新不频繁 → 纯 Dify(会话变量 + 档案库)足够;画像复杂、高频更新、需要跨团队共享 → Mem0 或 TencentDB Agent Memory 这类专用层。
七、实战三:R1 做检索决策------记忆不是越多越好
记忆系统最大的坑是**"什么都记,结果检索时全混在一起"**。上个月聊的 A 项目需求、这个月的 B 项目需求,都堆在同一个档案里,模型一检索就串味。解决办法是给记忆加"路由"------而路由决策,正是 R1 的高价值场景。
7.1 问题拆解
用户新消息进来,先判断:这轮问题需要调用哪类记忆?
- 闲聊/寒暄 → 不需要记忆,直接 V3 回复;
- 业务查询 → 需要检索知识库(公共记忆);
- 涉及个人历史 → 需要检索用户档案(个性化记忆);
- 涉及上轮上下文 → 短期记忆(会话历史)已覆盖,无需额外检索。
这个判断如果写死规则(关键词匹配),很快会漏;如果用 V3 直接判断,复杂语境下不稳定。用 R1 做一次小推理最合适。
7.2 检索路由节点
在 Chatflow 的检索节点前,加一个 R1 路由节点:
你是记忆路由。根据用户最新消息,决定需要激活哪类记忆。
可选记忆源:
- short_term: 当前会话历史(默认始终可用)
- kb_public: 产品/政策知识库
- profile: 该用户的历史画像与偏好
- none: 无需任何记忆,直接回答
用户消息: {{#sys.query#}}
已知画像: {{#sys.conversation.user_profile#}}
输出严格 JSON: {"sources": ["profile"], "reason": "用户在问自己的历史订单偏好"}
然后按输出用条件分支(IF/ELSE)路由到对应的检索节点。实测效果:路由准确率从规则版的约 82% 提升到 R1 版的 96% 以上,同时因为不命中时不检索,知识库调用次数下降约 40%,检索成本同步下降。
7.3 记忆的时效性:该忘就忘
长期记忆不等于永久记忆。三类信息必须能"遗忘":
- 过期事实 :用户换了城市、换了岗位------画像里的旧值要能被新值覆盖(实战一的
update机制); - 敏感信息:身份证号、银行卡号,默认不进记忆(见第八节脱敏);
- 低频噪音:三个月没被检索到的记忆,应降权或归档。
建议在记忆层设置两级策略:TTL 过期 (如画像字段 90 天未更新标记待确认)+ 主动澄清(Agent 检测到关键画像过期时,自然地问一句"您还在上海办公吗")。后者用 R1 判断"画像可信度低时主动确认",体验上比静默用旧数据好得多。
八、记忆的治理:隐私、脱敏与权限
记忆系统收集的是用户最私密的信息,治理不到位,技术再好也是事故。四条红线必须守:
8.1 采集层:默认不记敏感字段
抽取 Prompt 里明确禁止记录 PII(身份证、银行卡、密码、详细住址)。更稳的做法是在抽取节点后加一个 Code 脱敏节点,用正则把手机号、身份证号打码后再写入变量:
import re
def main(text: str) -> dict:
text = re.sub(r'(?<=\d{3})\d{4}(?=\d{4})', '****', text) # 手机号中段
text = re.sub(r'\d{17}[\dXx]', '******************', text) # 身份证
return {"masked": text}
8.2 存储层:加密与隔离
- 用户档案库与公共知识库物理分库,避免交叉检索;
- 向量库与结构化存储启用加密(静态加密 + TLS 传输);
- Langfuse 这类可观测平台若接入,注意对记忆相关字段做脱敏再上报(复用上一期可观测性文章的 Trace 脱敏方案)。
8.3 使用层:权限与审计
- 按用户 ID 做记忆隔离,Agent 只能读写自己的记忆(多租户场景必须做租户维度隔离);
- 重要画像变更记录审计日志(谁、何时、改了什么字段);
- 面向 C 端的产品,提供"查看我的记忆 / 清除我的记忆"入口------既是合规要求,也是信任建设。
8.4 合规:给用户"被遗忘权"
个人信息保护法(PIPL)要求提供删除渠道。记忆系统必须支持按用户 ID 一键清除全部记忆(会话变量、档案库文档、向量记录),并且要真的删干净------向量库的删除要确认 embedding 同步清除。
九、R1 在记忆系统里的三个高价值角色(小结)
整套系统里,R1 不是"主力回复模型",而是三个关键位置的"决策模型",这正是推理模型最值得用的地方:
| 角色 | 输入 | 输出 | 为什么用 R1 |
|---|---|---|---|
| 记忆抽取器 | 用户消息 + 现有画像 | 增/改/删操作 JSON | 需要推断隐含事实、权衡记不记 |
| 摘要压缩器 | 长对话历史 | 结构化摘要 | 需要取舍保留什么、丢弃什么 |
| 记忆路由器 | 用户消息 + 画像 | 激活哪些记忆源 | 需要综合判断,规则易漏 |
一句话:R1 负责"想清楚该记什么、该用哪段记忆",V3 负责"把记忆变成好听的话"。因为记忆操作频率远低于回复频率,R1 的额外成本几乎可以忽略,而记忆质量的提升是系统性的。
十、踩坑指南
- 会话变量被整体覆盖:变量赋值器是覆盖语义,不先做 Code 合并就写,历史字段全丢。所有写入统一走"读→合并→写"。
- 消息摘要阈值设太低:3 轮就摘要,模型频繁重写摘要,既贵又容易丢细节。建议 8~10 轮再触发。
- 摘要与原始历史混用:开启摘要后注意确认 Dify 的配置是"替换"还是"追加",避免上下文里新旧两套并存。
- 画像 JSON 解析失败:抽取节点的输出必须开 JSON Schema 约束,并加解析兜底(解析失败时返回空操作,不让流程报错)。
- 档案库检索串味:多个用户的画像写进同一个知识库,检索时必须带 user_id 元数据过滤,否则 A 的记忆会跑到 B 的对话里------这是隐私事故级别的 bug。
- R1 路由节点拖慢首轮 :路由 + 检索 + 回复三个串行节点,首轮延迟可能到 5~8 秒。对策:命中
none的闲聊直接短路;可并行的检索并行执行;路由用小上下文(只带画像不带全量历史)。 - 记忆无限膨胀:不设 TTL、不清理,向量库检索质量持续劣化。定期离线任务清理低频记忆。
- 忽略多租户隔离:单机 demo 无所谓,一旦多客户共用,记忆隔离就是第一优先级,别等技术债爆了再补。
十一、FAQ
Q1:Dify 自带的会话记忆和会话变量有什么区别?
会话记忆是"对话历史"本身,由系统自动拼接;会话变量是"业务状态",由你显式读写。前者管"说过什么",后者管"决定了什么"。两者互补:历史负责连续性,变量负责关键状态。
Q2:消息摘要和会话变量能互相替代吗?
不能。摘要是"压缩的历史",用于节省上下文;变量是"提炼的状态",用于跨节点/跨轮次复用。摘要回答"之前聊了啥",变量回答"现在该记住啥"。
Q3:什么时候该引入 Mem0 这类外部记忆层?
当出现这些信号之一:画像字段超过 10 个且高频更新、需要跨会话语义检索历史对话、多个 Agent 共享同一套用户记忆、有明确的隐私删除合规要求。早期用 Dify 原生能力足够。
Q4:记忆抽取为什么推荐 R1 而不是 V3?
抽取是"推断隐含信息 + 权衡取舍"的任务,推理模型在隐含事实识别(如从"有没有便宜点的"推断价格敏感)上显著更准。实测抽取准确率 R1 比 V3 高约 10~15 个百分点。而回复生成是"顺畅表达"任务,V3 性价比更高。
Q5:用户画像存在知识库会不会泄露?
会,如果检索不做隔离。必须:档案库独立、检索带 user_id 过滤、字段脱敏、存储加密、按 PIPL 提供删除入口。任何一步缺失都不建议上线。
Q6:记忆让 Agent 变"聪明"了,会不会产生幻觉?
记忆本身不产生幻觉,但过期或串味的记忆会诱导幻觉。所以时效性(更新/遗忘)和隔离(按用户)是记忆系统的两条命脉,比"记得多"更重要。
十二、总结与延伸
本文基于华为云 MaaS 的 DeepSeek-V3/R1 商用推理服务与 Flexus X 一键部署的 Dify 平台,构建了一套分级记忆系统:短期记忆 (会话历史+消息摘要)→ 中期记忆 (会话变量画像)→ 长期记忆(档案库向量化 / Mem0 记忆层),并给出 R1 在记忆抽取、摘要压缩、检索路由三个决策位上的实战用法,以及隐私治理的四条红线。
核心结论:
- 记忆是分层工程,不是单点功能------先会话变量,再档案库,最后按需引入记忆层;
- R1 该用在"想"的地方------记忆的写入与路由是推理模型的甜点区,V3 负责"说";
- 记忆治理 > 记忆容量------隔离、脱敏、遗忘,比"记得多"重要得多。
延伸方向:1 把记忆系统与上一期的可观测性方案结合------每次记忆写入打 Trace,画像变更可回放;2 引入 TencentDB Agent Memory 等独立记忆服务,做多 Agent 共享记忆;3 记忆摘要定期沉淀为团队知识库文档,让"个人记忆"升级为"组织记忆";4 结合 Text2SQL 智能问数,让"用户偏好"直接参与查询改写(比如画像里的"只看华东区"自动注入 SQL 过滤条件)。
DeepSeek 实战指南系列 🔗 从零手写 DeepSeek 推理优化 | MaaS 平台 DeepSeek 部署全攻略 | DeepSeek R1 + Dify Agent 企业级实战
Dify 实战系列 🔗 Dify 知识库问答 Agent 从零搭建 | Flexus X 实例性能深度评测