一、引言:用户第 17 次回来,你的 Agent 还认得他吗
先做一个实验。打开你基于 Dify 搭的多 Agent 客服系统,问一句"我是上周咨询过退款流程的李先生",然后关掉会话。明天再打开,问"我那个订单处理得怎么样了"。如果系统一脸茫然——"您好,请问您的订单号是?"——那么恭喜你,你的 Agent 患上了会话失忆症。
单 Agent 时代,记忆问题已经够头疼了:模型上下文窗口有限,聊长了就"忘"。多 Agent 时代,问题直接升级三个量级:用户跨会话回来,上下文分散在多个 Agent 手里;会话中间 Agent 交接,记忆在传递中丢失或串味;长期积累的用户画像、业务偏好、历史决策,明明存在数据库里,却没有任何 Agent 知道怎么用。
上一期我们讲安全边界,让系统"守得住、跑不脱";再上一期讲上下文工程,让 Agent"记住该记住的、忘掉该忘掉的"。这一期我们往前走一大步:多 Agent 系统的会话记忆增强与长期记忆架构——当用户跨天、跨周甚至跨月回来,系统怎么把"记得"升级成"懂你"。
先说结论:长期记忆不是"把对话记录存下来"这么简单。它是分层架构、写入策略、检索策略、更新策略、权限策略五个维度的系统工程。这一期我们从一张"记忆架构全景图"开始,逐层拆解,最后给出 Dify 上的完整工程实现。
二、先破除三个误区:长期记忆不是"聊天记录备份"
2.1 误区一:把全部对话原样存下来就是记忆
很多团队的第一版记忆系统是这样的:每次对话结束,把完整对话 JSON 扔进数据库,字段叫 conversation_history。用户再次回来时,把最近 50 条消息全塞进提示词。结果呢?Token 消耗爆炸、上下文被历史噪音污染、模型抓不住重点,还经常把三个月前的过时信息当成最新事实。
原生对话日志是数据,不是记忆。 记忆是"从对话中提炼出来、经过结构化、能按需检索的语义资产"。
2.2 误区二:记忆越多越好
另一种极端是"记忆越多越懂用户":把用户所有历史行为、所有偏好、所有说过的话全部灌给模型。结果模型在庞大上下文里迷失,回答变得瞻前顾后,甚至出现"记忆幻觉"——把 A 用户的信息安到 B 用户头上(这在多 Agent 系统里尤其致命,因为多个用户共享同一套 Agent 服务)。
记忆的正确姿势是"少而精、准而新":宁缺毋滥,按需取用。
2.3 误区三:记忆是单个 Agent 的事
单 Agent 系统里,记忆只服务一个对话上下文。多 Agent 系统里,用户的旅程横跨客服 Agent、订单 Agent、售后 Agent——记忆必须跨 Agent 共享,否则用户在客服 Agent 里说过的偏好,到了订单 Agent 那里又要重说一遍。
但跨 Agent 共享又带来新问题:共享粒度怎么控制?订单 Agent 需要知道用户偏好,但不需要知道用户昨天吐槽过什么。这就是这一期要解决的核心矛盾:共享与隔离的平衡。
三、记忆架构全景图:五个层次,一次看透
多 Agent 长期记忆系统,我习惯拆成五个层次:
┌─────────────────────────────────────────────────┐
│ L1 存储层 短期会话缓存 / 中期向量库 / 长期结构化库 │
├─────────────────────────────────────────────────┤
│ L2 写入层 在线提炼 / 离线批处理 / 记忆分级归档 │
├─────────────────────────────────────────────────┤
│ L3 检索层 相关性召回 / 时效加权 / 权限过滤 │
├─────────────────────────────────────────────────┤
│ L4 更新层 新增 / 合并 / 修正 / 遗忘(四类操作) │
├─────────────────────────────────────────────────┤
│ L5 应用层 单 Agent 注入 / 多 Agent 共享黑板 │
└─────────────────────────────────────────────────┘
- L1 存储层:回答"记忆放在哪"。短期记忆放 Redis 会话缓存,中期记忆放向量数据库(按语义检索),长期记忆放结构化数据库(用户画像、偏好、事实表)。
- L2 写入层:回答"记忆从哪来"。在线提炼(对话中实时抽取)、离线批处理(每天对当天对话做总结沉淀)、人工标注(客服主管修正画像)。
- L3 检索层:回答"该给模型喂什么"。不是把所有记忆都塞进去,而是按当前任务相关性召回,按时间衰减加权,按用户权限过滤。
- L4 更新层:回答"记忆怎么变"。新增(第一次知道的事实)、合并(重复信息的归并)、修正(用户改口)、遗忘(过期信息的淘汰)。
- L5 应用层:回答"记忆怎么用"。单 Agent 内注入上下文,多 Agent 间通过共享记忆服务读写。
这个五层模型是后面所有设计的骨架。接下来逐层深入。
四、L1 存储层:三类存储,各司其职
4.1 短期记忆:Redis 会话缓存
短期记忆 = 当前会话内的上下文,特点是高频读写、生命周期短(会话结束即过期)。多 Agent 场景下,短期记忆要解决的独特问题是跨 Agent 的会话状态同步:客服 Agent 刚确认了订单号,转交订单 Agent 时不能丢。
实现上推荐 Redis Hash + TTL:
import redis, json, time
r = redis.Redis(host="localhost", port=6379, decode_responses=True)
SESSION_TTL = 60 * 60 * 2 # 2小时会话有效
def save_session_state(session_id: str, agent_name: str, state: dict):
"""Agent 在交接前把自己的会话状态写入共享存储"""
key = f"session:{session_id}:{agent_name}"
r.hset(key, mapping={
"state": json.dumps(state, ensure_ascii=False),
"updated_at": str(time.time()),
})
r.expire(key, SESSION_TTL)
def load_session_state(session_id: str, agent_name: str) -> dict:
key = f"session:{session_id}:{agent_name}"
raw = r.hget(key, "state")
return json.loads(raw) if raw else {}
注意 TTL 是硬约束:短期记忆必须会过期,否则系统里会堆满僵尸会话。Dify 自身的会话机制覆盖单 Agent 内的短期记忆,多 Agent 交接的共享状态需要单独处理(第 8 节给完整方案)。
4.2 中期记忆:向量数据库语义召回
中期记忆 = 跨会话但时效性较强的事实("用户上周问过退款政策""用户三周前投诉过物流")。特点是需要语义检索、数据量大、不需要精确实时。
推荐方案:对话结束后,把当次对话的关键信息提炼成"记忆片段",用 Embedding 模型向量化,写入向量库(Dify 生态里常用 Weaviate / Qdrant / 华为云 CSS 向量检索)。
from langchain_community.embeddings import HuggingFaceEmbeddings
import qdrant_client
client = qdrant_client.QdrantClient(url="http://localhost:6333")
embedder = HuggingFaceEmbeddings(model_name="BAAI/bge-large-zh-v1.5")
def store_memory_fragment(user_id, fragment, meta):
vec = embedder.embed_query(fragment)
client.upsert(
collection_name="agent_memory",
points=[qdrant_client.models.PointStruct(
id=hash(f"{user_id}:{meta['ts']}") % (10**18),
vector=vec,
payload={
"user_id": user_id,
"text": fragment,
"agent": meta.get("agent", ""),
"ts": meta.get("ts", 0),
"importance": meta.get("importance", 0.5),
},
)],
)
中期记忆的关键设计点是给每条记忆打"重要性分"(importance):日常寒暄 0.3,业务事实 0.6,用户明确表达的偏好 0.9。检索时用 score = 语义相似度 × 重要性 × 时间衰减 综合排序,而不是只看相似度。
4.3 长期记忆:结构化用户画像
长期记忆 = 用户的基础事实与稳定偏好:姓名、行业、常用地址、价格敏感度、沟通风格。特点是低频变化、必须精确、直接决定个性化体验。
推荐存结构化数据库(PostgreSQL / MySQL / 华为云 GaussDB),一张 user_profile 表:
CREATE TABLE user_profile (
user_id VARCHAR(64) PRIMARY KEY,
name VARCHAR(64),
industry VARCHAR(64),
region VARCHAR(64),
price_sensitivity VARCHAR(16), -- high / medium / low
comm_style VARCHAR(16), -- concise / detailed
last_visit_at TIMESTAMP,
extra JSONB -- 扩展属性
);
为什么长期记忆必须结构化? 因为模型读 JSON 对话记录是"猜",读结构化的 price_sensitivity=high 是"查"。多 Agent 系统里,订单 Agent 要判断是否推荐高配方案,直接查字段即可,不需要让模型从历史对话里推理。精确性是长期记忆的生命线。
五、L2 写入层:记忆从哪来?三个来源
5.1 在线提炼:对话中实时抽取
对话进行中,用一个轻量级提炼 Agent(或规则+模型混合)实时抽取记忆候选。抽取规则示例:
- 用户提到"我是做跨境电商的" → 行业事实
- 用户说"价格无所谓,质量第一" → 价格敏感度低
- 用户明确说"下次直接帮我下单" → 行为偏好
- 用户提供订单号、地址等实体 → 业务事实
用 DeepSeek-R1 做提炼时,提示词模板(关键:要求结构化 JSON 输出):
你是用户画像分析师。从下面的客服对话中抽取值得长期记忆的用户信息。
只抽取【明确表达】的事实,不猜测、不推断。
输出 JSON 数组,每个元素包含:
- field: 字段名(name/industry/region/preference/behavior)
- value: 值
- importance: 0-1 的重要性分
- reason: 一句话说明为什么值得记
对话内容:
{conversation}
输出:
5.2 离线批处理:每天一次总结沉淀
在线提炼追求实时,但成本高(每次对话都调模型)。离线批处理补充覆盖:每天凌晨对昨天的会话做一次批量总结,把零散信息归并成画像更新。
典型做法:把昨天的对话按用户分组,对每个用户跑一次"总结 + 画像更新"任务,写入长期记忆库。批处理可以用 Dify 的定时工作流(Schedule Workflow)编排,用 DeepSeek 做总结,用 Python 节点写库。
离线批处理的另一个价值是跨会话事实归并。用户这个月咨询了三次:第一次问国际物流,第二次问清关材料,第三次问退换货政策——三次在线提炼会生成三条零散记忆。离线批处理把它们归并成一条画像事实:"用户从事跨境电商出口,关注国际物流与清关"。归并减少了记忆冗余,也让检索命中率更高。
5.3 人工修正:记忆的"审计通道"
机器提炼必然有错。长期记忆系统必须留一个人工修正入口:客服主管可以查看某个用户的画像,修正错误字段、删除错误记忆。这个能力在企业场景不是可选项——错误记忆比没有记忆更危险(用户会被错误画像冒犯,甚至收到完全错误的推荐)。
人工修正的界面可以很简单:一张用户画像表格 + 编辑/删除按钮 + 操作日志。但注意三点:修正记录要留痕(谁改的、改了什么、为什么改);修正操作要同步到向量库(中期的错误记忆片段也要删);修正权限要收口(只有主管角色能改,普通客服只能查看)。
六、L3 检索层:该给模型喂什么记忆?
6.1 三层过滤:相关性 → 时效 → 权限
检索不是"查出来全塞进去",而是三级过滤:
第一级:相关性召回。 根据当前任务意图,从向量库召回 Top-K 候选记忆。客服 Agent 处理退款咨询时,召回"退款相关历史",不召回"用户昨天的闲聊"。
第二级:时效加权。 记忆分新旧。score = sim × importance × decay(age),decay 可以是半衰期 30 天的指数衰减。一年前"用户喜欢蓝色主题"的偏好,权重远低于上周刚确认的。
第三级:权限过滤。 这是多 Agent 特有的一级:Agent 只能看到自己权限范围内的记忆。财务 Agent 能看到用户的支付历史,但不能看用户的医疗信息;售后 Agent 能看到投诉记录,但不能看用户的商业机密。记忆权限和工具权限(上一期)必须同源管理。
6.2 上下文预算:给记忆分配"额度"
即使过滤后,记忆量也可能超预算。推荐做法:给记忆分配固定上下文额度(比如总上下文的 15%),按分数从高到低填充,满额即止。剩下的留给系统提示词、工具说明和实时对话。
def build_memory_context(memories, budget_chars=800):
"""按分数从高到低填充记忆上下文,直到预算用尽"""
memories = sorted(memories, key=lambda m: m["score"], reverse=True)
ctx_parts, used = [], 0
for m in memories:
text = f"[{m['field']}] {m['value']} (重要度{m['importance']:.1f})"
if used + len(text) > budget_chars:
break
ctx_parts.append(text)
used += len(text)
return "\n".join(ctx_parts) if ctx_parts else "(暂无历史记忆)"
这条函数很小,但它体现了记忆工程的核心思想:不是"有什么记忆就喂什么",而是"预算内喂最值得的"。
七、L4 更新层:记忆的增删改查(还有"忘")
7.1 四类操作:新增、合并、修正、遗忘
- 新增(Create):新事实写入。写前先查重——同一个事实重复写入会造成记忆污染(用户画像里出现 5 条"行业=跨境电商")。
- 合并(Merge):同字段多值归并。用户第一次说"我在上海",半年后说"搬到深圳了"——不是新增一条,而是更新字段。
- 修正(Correct):用户主动改口或人工修正。修正要留痕迹:
old_value→new_value,便于审计(上一期的审计思路在这里复用)。 - 遗忘(Forget):过期记忆淘汰。三个遗忘触发器:TTL 到期、重要性长期偏低、用户主动要求删除(GDPR/个保法合规刚需)。
7.2 冲突消解:同一事实两个版本怎么办
多 Agent 并发写记忆时必然有冲突。原则:以用户最新明确表达为准,辅以人工修正优先。
def upsert_profile(user_id, field, value, source, ts):
"""冲突消解:时间戳新者胜,人工修正最高优先级"""
current = get_profile(user_id)
if field not in current or source == "manual":
write_profile(user_id, field, value, source, ts)
return
if ts >= current[field]["ts"]:
write_profile(user_id, field, value, source, ts)
注意一个反直觉点:不是所有新信息都覆盖旧信息。用户随口说"我可能去北京"不应覆盖"常驻上海"——所以写入前要有"可信度判断":明确表达("我搬到北京了")> 推测("可能去北京")。这提醒我们,L2 提炼时就要带上可信度标签。
八、L5 应用层:Dify 多 Agent 的完整工程实现
理论讲完,上工程。基于 Dify + DeepSeek + 华为云 Flexus 云服务器,搭一套可运行的多 Agent 长期记忆系统。
8.1 系统架构总览
用户 ──► 入口 Agent(路由)──► 客服 Agent ──► 订单 Agent
│ │ │
▼ ▼ ▼
┌──────────────────────────────────────────────┐
│ Memory Service(记忆服务,独立部署) │
│ Redis(短期)│ Qdrant(中期)│ PostgreSQL(长期) │
└──────────────────────────────────────────────┘
记忆服务独立部署(可以是一台轻量 API 服务,或 Dify 里的自定义工具集合),所有 Agent 通过工具调用它。关键决策:记忆不放在任何单个 Agent 内部,而是独立成服务——这样才可能跨 Agent 共享,且权限管控有单一入口。
8.2 Dify 里的落地:三个自定义工具
在 Dify 里为每个 Agent 挂三个记忆工具(可以用代码节点或自定义 API 工具实现):
工具一:memory_read —— 会话开始时注入记忆:
def memory_read(user_id: str, task: str, agent_role: str):
# 1. 短期:读 Redis 会话状态
short = load_session_state(user_id, agent_role)
# 2. 中期:向量检索 Top-K,按分数过滤
cands = vector_search(user_id, task, top_k=10)
allowed = permission_filter(cands, agent_role) # 权限过滤
mid = build_memory_context(allowed, budget_chars=600)
# 3. 长期:读结构化画像
profile = get_profile(user_id)
return {"short": short, "mid": mid, "profile": profile}
工具二:memory_write —— 会话中实时写入(带提炼):
def memory_write(user_id: str, conversation: str):
fragments = extract_facts(conversation) # DeepSeek 提炼
for frag in fragments:
if frag["field"] in PROFILE_FIELDS:
upsert_profile(user_id, frag["field"], frag["value"],
source="agent", ts=time.time())
else:
store_memory_fragment(user_id, frag["value"], {
"agent": frag["agent"], "ts": time.time(),
"importance": frag["importance"],
})
工具三:memory_forget —— 用户要求删除/纠正时调用:
def memory_forget(user_id: str, field: str = None, all: bool = False):
if all:
delete_all(user_id) # 用户要求清空全部记忆
elif field:
delete_field(user_id, field) # 删除单个字段
return {"status": "ok"}
8.3 工作流编排:把记忆工具串起来
在 Dify 工作流里:
- 会话开始节点:调
memory_read,把返回的短期状态、中期记忆、长期画像注入系统提示词,格式:
【用户画像】{profile} 【历史记忆】{mid} 【本会话上下文】{short} - 对话主流程:Agent 正常处理,DeepSeek 生成回复。
- 会话结束节点(或定期):调
memory_write提炼沉淀。 - 用户纠正节点:当检测到用户说"不是/不对/其实我是"等纠正信号时,调
memory_forget+ 重新memory_write。
8.4 一个完整的提示词注入示例
你是企业多 Agent 客服系统中的客服 Agent,负责售前咨询与售后引导。
请基于以下记忆信息个性化服务,但不得编造记忆中没有的信息。
【用户画像】
- 姓名:李先生
- 行业:跨境电商(出口)
- 常用地址:深圳市南山区
- 价格敏感度:medium(接受中端方案,偏好性价比)
- 沟通风格:concise(简洁直接,不要长篇大论)
【历史记忆】
- [行为] 上周咨询过国际物流时效问题(重要度0.8)
- [偏好] 偏好顺丰国际,曾提到"空运优先"(重要度0.7)
【本会话上下文】
- 用户已提供订单号:EX20260819
请用不超过 3 句话回应,先确认订单状态,再询问是否需要了解物流时效。
看到区别了吗?模型拿到的是提炼后的结构化记忆,而不是原始对话日志。这就是"记得"和"懂你"的分水岭。
九、多 Agent 记忆共享的三个边界问题
9.1 共享粒度:什么记忆该跨 Agent,什么该隔离
原则:业务事实共享,个人隐私隔离。
- 共享:订单状态、物流信息、业务偏好(行业、常用地址)——这些是协作必需。
- 隔离:用户对某个 Agent 的负面评价、敏感个人信息、未确认的推测——这些只对该 Agent 可见。
实现:记忆 payload 带 visibility 字段,shared / agent_private:{agent_name} / private。权限过滤时按 Agent 角色匹配。
9.2 记忆漂移:共享记忆被错误覆盖
A Agent 写入"用户偏好 A 方案",B Agent 根据片面对话覆盖成"用户偏好 B 方案"——这就是记忆漂移。防御:写入前查重 + 冲突消解按"明确表达 > 推测"加权,且敏感字段(如身份、地址)只允许人工或用户本人修改。
9.3 合规红线:记忆即数据,数据即责任
《个人信息保护法》下,用户有查询、更正、删除个人信息的权利。记忆系统必须提供:
- 记忆可视化(用户能看到系统记得自己什么)
- 一键删除(memory_forget(all=True) 必须真的全删,包括向量库)
- 访问审计(谁在什么时候读了哪些记忆——上一期安全篇的审计体系直接复用)
记忆权限 = 数据合规。 这不是技术债,是法律义务。
十、踩坑指南:我见过的六个真实翻车现场
- 把所有对话都向量化入库:向量库里塞满闲聊,检索质量直线下降。解法:写入前用提炼 Agent 过滤,只存"值得记的"。
- 记忆注入顺序混乱:把几十条记忆无序拼接进提示词,模型抓不住重点。解法:按分数排序 + 分区块(画像/历史/上下文)注入。
- 跨 Agent 记忆互相覆盖:订单 Agent 和客服 Agent 同时写画像字段,后写的覆盖先写的。解法:冲突消解 + 字段级锁(写入前比对 timestamp)。
- 记忆权限和工具权限两套体系:Agent 能查记忆但不能用工具(或反之),安全边界形同虚设。解法:统一权限模型,记忆和工具共用一张权限表。
- 只写不读:记忆写入了一大堆,但提示词里根本没用上——纯浪费存储和提炼成本。解法:上线前先做"记忆读取率"埋点,低于阈值就检查注入链路。
- 用户改口后旧记忆还在:用户说"我之前说的不对",系统还是按旧记忆回答。解法:纠正信号检测(关键词"不对/其实/更正"+ 意图分类),触发覆盖写。
十一、FAQ:关于多 Agent 长期记忆的六个高频问题
Q1:Dify 自带的会话变量/对话历史不够用吗?
Dify 的会话变量适合单 Agent 单会话内的状态保存,无法跨 Agent 共享、无法语义检索、无法长期沉淀。长期记忆需要在 Dify 之外(或作为自定义工具)构建记忆服务。
Q2:提炼记忆用 DeepSeek 会不会很贵?
可控。在线提炼只对"关键对话"触发(可以先用规则过滤:含实体、含偏好词、长度超阈值),离线批处理一天一次。实测一个中等客服系统,记忆提炼的 token 成本约占整体调用成本的 3%-5%。
Q3:向量库选型有什么建议?
中小规模用 Qdrant/Weaviate(Dify 默认集成),大规模或要求高可用用华为云 CSS 向量检索。Embedding 模型中文场景推荐 bge-large-zh-v1.5。
Q4:用户画像字段怎么设计才够用?
起步阶段 6-8 个核心字段(姓名/行业/地区/价格敏感度/沟通风格/常用偏好)足够,用 extra JSONB 兜底扩展。不要一开始就设计 50 个字段——维护成本爆炸,且大部分用不上。
Q5:记忆系统需要单独部署还是放 Dify 插件里?
推荐单独部署成轻量 API 服务(FastAPI 即可,华为云 Flexus 轻量实例完全够用),Dify 里通过自定义 API 工具调用。独立部署的好处:多 Agent 共享、权限统一管控、不依赖 Dify 版本。
Q6:如何衡量记忆系统做得好不好?
三个指标:记忆命中率(检索结果被模型采纳的比例)、画像准确率(抽样人工核对)、个性化满意度(用户好评率对比)。和评测篇同理——记忆也要纳入评测体系,用 DeepSeek-R1 当裁判,构造"带记忆 vs 不带记忆"的对照测试集。
十二、总结:长期记忆架构的四条设计铁律
- 分层存储:短期会话、中期语义、长期画像各司其职,一个 Redis 走天下或一个向量库包打一切都是偷懒;
- 提炼优先:记忆是"提炼后的语义资产",不是"原始日志备份",写入前先问"这条值得记吗";
- 按需取用:检索三层过滤(相关/时效/权限)+ 上下文预算,宁缺毋滥;
- 能忘才健康:TTL、冲突消解、用户一键删除——会遗忘的记忆系统才是完整的记忆系统。
回顾开头的场景:第 17 次回来的李先生,入口 Agent 通过 memory_read 拿到了他的画像和上周的物流咨询记录,一句话确认身份,客服 Agent 直接接上话题:"李先生您好,您的订单 EX20260819 已出库,空运预计后天到达,需要我帮您预约收货时间吗?"——这就是从"记得"到"懂你"的体验跃迁。
下一期我们聊聊多 Agent 系统的任务路由与负载均衡——当请求像潮水一样涌来,系统怎么把每个任务派给最合适的 Agent,让整体吞吐和响应质量同时在线。
📚 延伸阅读
如果你对 DeepSeek 的实战用法感兴趣,推荐阅读我的另一篇文章:
👉 DeepSeek 实战指南:提示词工程、API 集成与效率提升全攻略
这篇文章系统地拆解了 DeepSeek 的提示词工程技巧、API 封装方法以及日常效率提升场景,全文代码可直接运行,适合已经上手 DeepSeek 但希望更高效使用的开发者。
Dify 多 Agent 实战系列:
- Dify 多 Agent 安全边界与权限治理实战:让每一个智能体都"知道边界、守得住门"
- Dify 多 Agent 上下文工程实战:让智能体"记住该记住的,忘掉该忘掉的"
- Dify 多 Agent 故障演练实战:用混沌工程主动"搞破坏",让智能体系统越炸越稳
- Dify 多 Agent 灰度发布实战:让每一次变更都"小步快跑、随时可回滚"
- Dify 多 Agent 评测实战:用 DeepSeek-R1 当裁判,打造多智能体系统的自动化质量保障体系
本文是"华为云Flexus+DeepSeek征文"系列文章之一。该系列基于华为云 Flexus 云服务器与 MaaS 平台 DeepSeek 推理服务,结合 Dify 工作流引擎,从部署、Agent 开发、评测、成本治理、稳定性建设、安全治理到记忆架构,系统拆解企业级 AI 应用的工程实践。

4739

被折叠的 条评论
为什么被折叠?



