
9 月 14 日,Salesforce 分享了美国运输安全管理局(TSA)的客服案例。TSA 部署的 AI Agent Ace,目前每月处理约 10 万次旅客咨询,其中约 96% 无需转人工。复杂问题转给人工后,客服可以在统一工作台里查看此前的咨询记录,并继续安排后续处理。
这件事放到日常售后里,其实就是一个问题:用户再次来找客服时,希望上次说过的话还被记得。
比如,一副正在换货的耳机。假设用户几天前在在线客服里申请了换货,补过收货地址,也说过“进度发信息就行”。今天换到邮件里,只留下三个字:“发了吗?”
要回答这句话,客服 Agent 需要找回对应的订单和工单,再查询最新进度;此前确认过的地址和通知偏好,也应该继续派上用场,而不是让用户重新说明。
这背后涉及三类不同的信息:用户已经交代过的事情、处理同类任务时可以复用的经验,以及业务方统一维护的政策。
比如,“这位用户希望进度发短信”属于个人偏好;“处理换货前先核验订单和政策”则可以沉淀为通用流程。两者的适用范围不同,也需要分别管理。
本文以消费者售后客服为例,通过 Python 代码片段,展示如何把这三类信息组合成一套完整的客服记忆方案。
注:文中的订单、工单、物流和通知工具均为 Demo 中的模拟业务工具,不代表 MemOS 内置这些业务系统。政策文本同样为示例内容,实际接入时应替换成业务方真实的系统和正式政策。
从耳机换货开始,跑三个场景
先从一笔耳机换货任务开始。整个 Demo 分为三个阶段:
DAY 1 · 在线客服
customer_001 提交耳机换货请求。客服 Agent 查询订单、检查政策、创建工单,并记录收货时间、地址和短信通知要求。
DAY 4 · 邮件工单
customer_001 使用新的 conversation_id 追问换货进度。这一步验证用户事实和偏好能否跨会话延续。`
DAY 7 · 在线客服
customer_002 遇到相似的耳机杂音问题。客服 Agent 尝试复用此前形成的 Skill,验证用户信息保持隔离的同时能否跨用户复用通用 Skill。
我们最终要验证的不是“Agent 能不能保存聊天记录”,而是两件事:
该记住的人,能不能接着上次的事情办。
已经形成的经验,能不能交给下一位用户复用。
先把三类信息分清
一次售后任务,通常会同时留下三种完全不同的信息。
比如 DAY 1,用户提出耳机换货请求:
- 订单号是什么;
- 耳机哪里出了问题;
- 工单有没有创建;
- 收货地址是什么;
- 用户希望通过什么渠道接收进度。
这些信息回答的是:“这位用户之前发生了什么?”
与此同时,客服 Agent 在办理这笔业务时,也可能形成一套可以复用的处理流程:
查询订单 → 核验政策 → 创建工单 → 更新履约信息 → 按用户确认的方式通知进度
这些信息回答的是:“以后遇到类似问题,通常怎么处理?”
还有一类信息来自业务方:
- 退换货条件是什么?
- 保修多久?
- 哪种情况可以换货?
它回答的是:“这一次应该按照什么规则处理?”
所以,这个方案里有三类上下文:
| 信息类型 | 保存什么 | 解决什么问题 | 归属 |
|---|---|---|---|
| 用户记忆 | 保存订单、故障、工单状态、地址和通知偏好 | 回答“这位消费者已经处理到哪一步” | 只属于当前 user_id |
| Agent Skill | 保存客服 Agent 从完整任务轨迹中提炼的通用处理流程 | 回答“完成这类任务通常需要执行哪些步骤” | 属于稳定 agent_id |
| 政策知识库 | 保存退换货、保修、物流和发票等正式规则 | 核对“按规定应该怎么处理” | 由业务方统一维护 |
三者的边界一旦混在一起,就容易出现两个典型问题:
用户 A 的地址被用户 B 看到,或者某个用户的一次性要求被错误地当成所有人的通用流程。
所以,重要的并不是“把所有历史消息都存下来”,而是把信息放到正确的位置。
方案简介
整体可以概括为 “两次写入、一次组合召回”。
每轮任务结束后,同一份任务记录分别写入用户记忆和 Agent Skill,通过 allow_memory_view 指定不同的记忆类型;用户再次发起请求时,通过一次 /search/memory 同时获取当前用户记忆、Agent Skill 和政策知识。当前会话历史仍由客服应用自己维护。
具体来说,用户记忆通过 user_id 限定范围,Agent Skill 通过 agent_id 跨用户复用,政策知识库通过 knowledgebase_ids 加入同一次检索。
用户请求
│
├─ Agent 从业务应用读取当前会话历史
│
├─ 一次组合召回:agent_id + knowledgebase_ids
│ filter 选中当前用户记忆(related_id)与 Agent Skill(SkillMemory)
│ → 用户事实、偏好、Skill、政策
│
├─ 合并上下文并执行订单、工单、通知等业务工具
│
├─ 大模型根据工具结果生成最终回复
│
├─ 写入一:user_id + agent_id → 事实、偏好
│
└─ 写入二:agent_id → 请求生成或更新 Skill
接入前准备
动手前,先准备好接口和项目配置。
- 一个 MemOS API Key,以及该 Key 所属项目对政策知识库的访问权限。
- 一个 OpenAI 或 OpenAI 兼容的大模型接口。
- 在 MemOS 控制台为当前项目开启「为 Agent 创建独立记忆」。
「为 Agent 创建独立记忆」默认关闭。开启后,才能以 agent_id 为独立主体写入和检索 Agent Skill。
先让 Agent 知道业务规则
当用户说“耳机有杂音”时,客服 Agent 不能只依赖用户记忆,还需要知道当前适用什么售后规则。
因此,初始化时先创建一个政策知识库,上传售后政策文档,并等待文件处理完成:
def create_policy_knowledge_base():
headers = {
"Content-Type": "application/json",
"Authorization": f"Token {MEMOS_API_KEY}",
}
# 1. 创建知识库:POST /create/knowledgebase
res = requests.post(
f"{MEMOS_BASE_URL}/create/knowledgebase",
headers=headers,
json={
"knowledgebase_name": "消费者售后政策知识库",
"knowledgebase_description": "消费者退换货、保修、物流与发票政策",
},
)
body = res.json()
if body.get("code") != 0:
sys.exit(f"创建知识库失败:{body.get('message')}")
policy_kb_id = body["data"]["id"]
# 2. 上传政策文档:POST /add/knowledgebase-file
# 文件内容需要 base64 编码后以 data URL 形式传入
encoded = base64.b64encode(POLICY_DOC_MD.encode("utf-8")).decode("utf-8")
res = requests.post(
f"{MEMOS_BASE_URL}/add/knowledgebase-file",
headers=headers,
json={
"knowledgebase_id": policy_kb_id,
"file": [{
"type": "document",
"name": "consumer-after-sale-policy.md",
"content": f"data:text/markdown;base64,{encoded}",
}],
},
)
if res.json().get("code") != 0:
sys.exit(f"上传政策文档失败:{res.json().get('message')}")
# 3. 轮询文件解析状态:POST /get/knowledgebase-file
print("政策文档已上传,等待解析...")
for _ in range(40):
time.sleep(3)
res = requests.post(
f"{MEMOS_BASE_URL}/get/knowledgebase-file",
headers=headers,
json={"knowledgebase_id": policy_kb_id, "page": 1, "page_size": 20},
)
files = res.json().get("data", {}).get("file_detail_list", [])
statuses = {str(item.get("status", "")).lower() for item in files}
if files and statuses <= {"completed", "available", "failed"}:
if "failed" in statuses:
sys.exit("政策文档解析失败")
break
print(f"政策知识库已就绪:{policy_kb_id}")
return policy_kb_id
知识库 ID 直接来自创建接口的响应,随后传给客服助手:
policy_kb_id = create_policy_knowledge_base()
assistant = CustomerServiceAssistant(policy_kb_id)
创建接口返回的 policy_kb_id 会传给客服助手,后续通过 knowledgebase_ids 参与召回。
这里有一个容易忽略的问题:
文档上传成功,不等于已经可以检索。
文件可能还处于解析状态,所以 Demo 会先等待文件处理完成,再进入后续步骤。
一份任务,分别沉淀用户记忆和 Agent Skill
一轮完整的客服任务里,既包含用户提供的信息,也包含 Agent 实际执行过的处理过程。
因此,写回 MemOS 时,不应该只提交用户问题和最终答案。而应该把用户请求、工具调用、工具结果和最终回复组成一份完整任务记录。
user
→ assistant.tool_calls
→ tool
→ assistant
通过 role_id 标明真实发言主体:
- 用户消息使用
user_id; - 客服回复与工具调用使用
agent_id。
# 以 DAY 1 的换货请求为例,一轮对话写回 MemOS 的完整消息列表
memory_messages = [
# 1. 用户消息:role_id 为当前用户
{
"role": "user",
"role_id": user_id, # 例如 customer_001
"content": "我的降噪耳机左耳有电流声,想换货",
},
# 2. 工具调用:客服 Agent 查询订单,role_id 为 agent_id
{
"role": "assistant",
"role_id": AGENT_ID,
"content": "",
"tool_calls": [{
"id": "call_1",
"type": "function",
"function": {
"name": "query_order",
"arguments": '{"order_id": "20260820-88"}',
},
}],
},
# 3. 工具结果:通过 tool_call_id 与上面的调用对应,无需 role_id
{
"role": "tool",
"tool_call_id": "call_1",
"content": '{"order_id": "20260820-88", "status": "已签收"}',
},
# 4. 最终回复:同样归属客服 Agent
{
"role": "assistant",
"role_id": AGENT_ID,
"content": "已为您创建换货工单 EX20260821-03,换货双向免运费……",
},
]
完整轨迹用于 Skill 提炼,用户事实和偏好则从同一份记录中生成。
划分写入类型
用户写入与 Agent 写入使用不同的视图,召回则共用一份视图配置:
USER_WRITE_VIEWS = ["detail_factual", "preference"]
AGENT_SKILL_WRITE_VIEWS = ["skill"]
CONTEXT_VIEWS = ["detail_factual", "preference", "skill"]
这样,同一份任务记录可以写入两个记忆空间,又不会重复生成同类型的记忆。
写入用户事实和偏好
第一条 /add/message 同时传入 user_id 和 agent_id:
def add_user_memories(self, messages, user_id, conversation_id, channel):
"""第一次写入:只在用户视角生成事实与偏好。"""
user_data = {
"user_id": user_id,
"agent_id": AGENT_ID,
"conversation_id": conversation_id,
"info": {"channel": channel, "scene": "consumer_support"},
"allow_memory_view": USER_WRITE_VIEWS,
"messages": messages,
}
# POST /add/message:写入为异步任务,返回 task_id 后
# 用 /get/status 轮询至 completed(完整实现见文末 Demo)
res = requests.post(
f"{MEMOS_BASE_URL}/add/message",
headers=self.headers,
json=user_data,
)
if res.json().get("code") != 0:
print(f" [MemOS] 写入用户事实与偏好失败:{res.json().get('message')}")
记忆归属当前 user_id,同时挂到客服 Agent 的记忆空间下,后续才能在 Agent 视角的组合召回中通过 related_id 精确选中。
MemOS 根据对话内容判断是否形成事实或偏好。某轮对话没有表达稳定偏好时,可以只生成事实。
再写入 Agent Skill
第二条 /add/message 传 agent_id,允许生成 Skill:
def add_agent_skill(self, messages, conversation_id, channel):
"""第二次写入:只在 Agent 视角请求生成或更新 Skill。"""
skill_data = {
"agent_id": AGENT_ID,
"conversation_id": conversation_id,
"info": {"channel": channel, "scene": "consumer_support"},
"allow_memory_view": AGENT_SKILL_WRITE_VIEWS,
"custom_extract_prompt": {"skill": SKILL_EXTRACT_PROMPT},
"messages": messages,
}
# 同样是 POST /add/message,只是不传 user_id 且 allow_memory_view 只含 skill
res = requests.post(
f"{MEMOS_BASE_URL}/add/message",
headers=self.headers,
json=skill_data,
)
if res.json().get("code") != 0:
print(f" [MemOS] 写入 Agent Skill 失败:{res.json().get('message')}")
Skill 写入不与任何用户绑定,只在 Agent 视角存在,因此可以跨用户复用。每轮回答后都会执行这次写入。
这里不需要由调用方提前判断“这轮到底有没有值得沉淀的 Skill”。每轮任务都可以提交给 MemOS,由 MemOS 结合任务轨迹和已有 Skill,自行判断是生成、更新还是跳过。
把一次换货经验,沉淀成通用 Skill
Skill 的目标是留下下一次还能复用的处理方法。
可以通过 custom_extract_prompt.skill 进一步限制 Skill 的抽取范围:
- 根据业务目标、触发条件、核心工具链和成功标准判断是否属于同一个 Skill;
- 相同流程优先合并已有 Skill,没有新的通用信息时不重复生成;
- 移除姓名、地址、用户 ID、订单号、工单号和具体日期;
- 将实例值替换成
order_id、ticket_id、shipping_address等参数; - 不把用户个人偏好或单次时间要求写成通用规则;
- 只保留工具结果能够证明的执行步骤。
例如:
用户要求“换货进度发短信”。
这是用户偏好。
而:
按用户确认的通知渠道发送进度。
才更接近通用流程。
这样,下一位用户即使更希望用邮件接收通知,Skill 仍然可以继续复用。
跨过一个会话,继续处理同一笔业务
到了 DAY 4,用户换到邮件继续追问换货进度。
这一次,客服 Agent 需要同时拿到三类信息:
- 当前用户此前留下的事实和偏好;
- 当前客服 Agent 已经沉淀的通用 Skill;
- 当前生效的售后政策。
因此,生成回复前,客服助手只调用一次 /search/memory,将这几类信息放在同一次请求中检索。
def search_memory(self, query, user_id):
"""一次组合召回:用户事实/偏好 + Agent Skill + 政策知识。"""
context_data = {
"query": query,
"agent_id": AGENT_ID,
"knowledgebase_ids": self.knowledgebase_ids,
"include_memory_view": CONTEXT_VIEWS,
"memory_limit_number": 9,
"preference_limit_number": 6,
"filter": {
"user": {
"or": [
{"related_id": [user_id]},
{"memory_type": "SkillMemory"},
]
}
},
}
# POST /search/memory:用户事实、偏好、Agent Skill 与政策一次取回
res = requests.post(
f"{MEMOS_BASE_URL}/search/memory",
headers=self.headers,
json=context_data,
)
result = res.json().get("data") or {}
return (
result.get("memory_detail_list", []), # 用户事实
result.get("preference_detail_list", []), # 用户偏好
result.get("skill_detail_list", []), # Agent Skill
)
这里的 filter 用 or 连接两个条件,二者是或关系、不需要同时满足:related_id 选中当前用户的记忆,保证不同用户之间的事实和偏好保持隔离;memory_type: "SkillMemory" 让 Agent Skill 不受用户限制、跨用户返回。政策知识库则通过 knowledgebase_ids 加入同一次检索,不需要单独调用。
三类信息在这次检索中各自解决一个问题:
- 用户记忆回答“这位消费者之前处理到哪一步”;
- Agent Skill 回答“处理这类任务通常需要怎么做”;
- 政策知识库回答“当前业务规则是什么”。
另外,记忆不等于实时业务状态。
例如,用户记忆中记录“换货工单已经创建”,只能说明此前处理到了这一步。如果用户现在问“已经发货了吗”,客服 Agent 仍然需要调用物流或工单系统查询最新状态,再根据结果生成回复。
换个会话,再换个用户,验证记忆效果
技术接入完成之后,可以通过三个阶段检查整个链路。
DAY 1:检查第一次任务
完成换货任务后,确认用户事实和偏好写入 User Cube,同一任务记录提交给 Agent Cube 进行 Skill 判断。如果生成了 Skill,还需要检查其中是否保留了具体用户信息。
DAY 4:换到邮件
使用新的 conversation_id,确认仍能通过 customer_001 召回此前订单、故障和通知要求。
这里验证的是:用户事实和偏好能不能跨会话延续。
DAY 7:换成另一个用户
切换到 customer_002,提出相似的耳机问题。
这里同时验证:
第一位用户的个人信息是否保持隔离,以及当前 Agent 能否复用已经形成的通用 Skill。
这正是整个方案最重要的两个验证点:记住该记住的人,同时复用该复用的经验。
相关链接
MemOS 官网:
GitHub:
文档:
完整 Demo:
关于 MemOS
MemOS 为 AI 应用构建统一的记忆管理平台,让智能系统如大脑般拥有灵活、可迁移、可共享的长期记忆和即时记忆。
作为记忆张量首次提出 “记忆调度” 架构的 AI 记忆操作系统,我们希望通过 MemOS 全面重构模型记忆资源的生命周期管理,为智能系统提供高效且灵活的记忆管理能力。
249



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



