【Agent 开发】聊聊 Agent Memory 的设计:别把向量库当万能记忆
文章目录
🔥 个人主页:铁皮哥(欢迎关注)
📌 作者简介:28届校招生,后端开发/Agent 方向在学
📚 学习内容:Java、Python、计算机视觉、大语言模型、Agent开发
📝 专栏内容:从零开始的Claude Code零代码生活(持续更新中)
✨不只背八股,更想搞懂为什么这样设计
前言
最近在看 Agent 相关项目时,我发现一个挺容易被忽略的问题:
很多人提到 Agent 记忆,第一反应就是“把用户说过的话存下来”。
这个想法很自然。
毕竟对于一个能持续对话的 Agent 来说,如果它上一轮刚问完用户订单信息,下一轮就忘了用户在说哪件商品,体验一定很差。于是我们很容易继续往下想:既然要记住,那是不是把聊天记录全部存起来就行?再进一步,是不是直接丢进向量库,后面需要的时候检索出来?
但真实业务里,问题没有这么简单。
比如在一个订单客服 Agent 中,用户先说:
我想查一下昨天买的耳机到哪了。
过了一会儿又问:
那能不能帮我申请退款?
这时候 Agent 需要用到的信息其实有好几种:
“耳机”来自当前对话,“昨天买的”需要关联订单数据,“申请退款”涉及售后流程,当前用户是否已经登录、订单是否允许退款、退款流程走到了哪一步,也都需要被系统记录下来。
这些信息都可以被笼统地叫作“记忆”,但它们的性质并不一样。
有些信息只在当前几轮对话里有用,过一会儿就可以丢掉;
有些信息是明确的业务状态,必须精确读写;
还有一些信息更接近长期偏好或历史经验,后续可能通过语义检索再次用到。
所以,做 Agent 记忆系统时,真正要先想清楚的是这条信息以后会怎么被使用?
如果后面需要的是精确查询,就应该交给 Redis 或 MySQL 这类后端存储;如果后面需要的是相似召回,再考虑向量库;如果只是当前对话里的临时信息,放在上下文里就够了。
一、Agent 到底需要记住什么?
如果把 Agent 放到一个真实业务场景里,就会发现“记住用户说过的话”只是最表层的需求。
还是看前面那个订单客服的例子。
用户一开始说:
我想查一下昨天买的耳机到哪了。
过了几轮之后,又补了一句:
那能不能帮我申请退款?
这时候 Agent 如果只看最后一句话,其实很难做出正确判断。
因为“那”指的是前面提到的耳机订单,“申请退款”背后还牵扯订单状态、售后规则、用户身份、当前流程节点。
Agent 要记住的内容并不只有聊天记录本身。
1.1 当前对话里的临时上下文
第一类信息,是当前几轮对话里的临时上下文。
比如用户刚刚提到的商品是耳机,购买时间大概是昨天,当前意图从查询物流切换到了申请退款。这些信息通常和当前会话强相关,离开这次对话后价值就会下降。
这类信息适合放在 Prompt 上下文里,让模型在生成回复时直接看到。
它的特点是轻、短、变化快。
如果每一轮都把完整聊天记录塞进去,很容易把上下文撑长,也会引入很多没用信息。更合理的做法是保留关键片段,或者在多轮对话后做一次简短摘要。
例如可以整理成这样:
当前商品:耳机
用户意图:从查询物流转为申请退款
已知条件:用户提到购买时间是昨天
这样模型拿到的是干净的信息,不需要在一长串历史对话里自己找重点。
1.2 业务流程中的状态信息
第二类信息,是业务流程状态。
这部分经常被误放进聊天记录里,但它其实更像后端系统里的会话状态。
比如当前用户是否已经登录,当前会话绑定的是哪个订单,退款流程走到了哪一步,是否已经完成身份校验,这些信息都要求准确、可更新、可追踪。
这类信息不适合只靠模型自己“记住”。
原因很简单:模型生成的是文本,业务状态需要确定性。
如果用户已经完成身份校验,系统就应该明确记录下来;如果退款流程已经进入人工审核,也应该有一个清晰的状态字段,而不是每次都让模型从历史对话里猜。
可以把它想成一个简化的状态对象:
{
"user_id": "10086",
"current_order_id": "order_123",
"auth_checked": true,
"refund_step": "checking_policy"
}
这类信息更适合放在 Redis 或数据库里。
Redis 适合保存短期流程状态,MySQL 适合保存稳定业务数据。模型只需要在合适的时候读取这些状态,再基于状态生成回复。
1.3 可以长期复用的信息
第三类信息,是长期信息。
比如用户经常咨询数码产品售后,偏好上门取件,历史上多次因为物流慢申请退款。这些信息不会只服务于当前这一轮对话,后续再次和用户交互时,可能还能发挥作用。
这类信息可以沉淀到用户画像、历史记录表,或者进入向量库。
但不是用户说过的每一句话都值得长期保存。
长期记忆应该更关注稳定、可复用、有业务价值的信息。比如用户明确表达过的偏好、重复出现的问题、已经确认过的事实。
二、记忆到底该怎么取?
2.1 有明确锚点时,先用 Grep 思路
这里说的 Grep,不一定是直接执行 Linux 里的 grep 命令,更像是一种工程思路。
当目标信息有明确关键词、ID、字段、时间范围时,优先使用精确匹配。
比如:
订单号:order_123
用户 ID:10086
退款状态:checking_policy
错误码:PAYMENT_TIMEOUT
接口名:createRefund
这些信息都有很强的锚点。
只要锚点存在,直接查日志、查数据库、查结构化字段,通常比语义检索更稳定。
比如用户问:
我刚才那个 order_123 退款到哪一步了?
这个问题里已经有明确订单号。
这时最合理的方式,是拿着 order_123 去查订单表、退款表或状态缓存。
如果把这个问题交给向量库,让系统去召回“和退款相关的历史对话”,反而会绕远路。召回结果可能看起来相关,但未必是当前订单的真实状态。
Grep 思路的优势很直接:便宜、稳定、可解释。
它适合处理确定信息。
只要问题里有明确锚点,就应该先考虑精确查询。
2.2 表达模糊时,再考虑 RAG
RAG 适合处理另一类问题:用户知道自己想问什么,但表达里没有明确字段。
比如用户问:
上次你说那个退货限制是什么来着?
或者:
之前是不是提到过耳机拆封后还能不能退?
这类问题没有订单号,也没有稳定字段。
用户想找的其实是一段语义相关的历史内容。
这时只靠关键词匹配,可能会搜不到。
因为用户这次说的是“退货限制”,历史记录里写的可能是“拆封后不支持七天无理由”。两段文本关键词不完全一致,但语义上明显相关。
这就是 RAG 更适合发挥作用的地方。
RAG 更擅长找“意思接近”的内容,例如历史咨询、知识库说明、用户曾经表达过的偏好、项目文档里的相关片段。
不过 RAG 也要克制使用。
召回结果只是候选材料,不代表最终答案。
它可能过期,可能相似但不准确,也可能混入上下文相近但业务对象不同的片段。
所以在 Agent 记忆里使用 RAG 时,最好再加几层过滤:
用户过滤:只召回当前用户相关内容
时间过滤:优先使用最近信息
业务过滤:确认订单、商品、流程是否匹配
状态校验:最终结果以业务系统为准
RAG 的价值在于补全模糊语义。
它不适合承担强一致的业务判断。
2.3 强状态信息,直接查后端系统
还有一类信息,根本不应该从历史记录里“猜”。
比如:
当前订单是否允许退款
用户是否已经登录
售后工单是否已经创建
退款流程走到了哪一步
优惠券是否已经过期
这些信息的共同点是:必须准确。
用户说“帮我继续刚才的退款”,Agent 可以从上下文里知道用户大概率在说耳机订单;也可以通过 RAG 找到之前的咨询片段。
但退款是否已经提交、当前卡在哪个节点、是否还能取消,都应该以业务系统为准。
历史对话只能提供线索。
真正决定下一步动作的,应该是 Redis、MySQL、订单系统、工单系统里的当前状态。
例如一个简化流程:
用户:帮我继续刚才的退款
↓
从上下文中识别“刚才的退款”
↓
从会话状态中读取 current_order_id
↓
查询退款单当前状态
↓
根据状态生成下一步回复
这样做的好处是,模型负责理解表达,后端负责维护事实。
两边边界清楚,系统才更容易排查问题。
2.4 总表
Agent 记忆的关键不只是“存下来”,还要在使用时选对取数方式。
| 取数方式 | 适合场景 | 典型例子 |
|---|---|---|
| Grep / 精确匹配 | 有明确关键词、ID、字段 | 订单号、错误码、接口名、日志片段 |
| RAG / 语义召回 | 表达模糊,需要找相似内容 | 历史咨询、知识库说明、用户偏好 |
| 状态查询 | 需要当前真实状态 | 登录态、订单状态、退款节点、权限校验 |
简单来说:
有锚点,先精确查。
表达模糊,再语义召回。
涉及状态,永远以业务系统为准。
三、一个简化版记忆架构
这里还是沿用订单客服场景。
用户说:
帮我继续刚才那个耳机订单的退款。
它要理解“刚才那个”指向哪笔订单,要知道当前退款流程走到了哪一步,还要判断订单是否满足退款条件,最后再组织一段用户能看懂的回复。
可以把流程拆成下面这样:
用户输入
↓
抽取当前意图和关键信息
↓
读取会话上下文
↓
读取业务状态
↓
必要时检索历史信息或知识库
↓
组装 Prompt
↓
模型生成回复
↓
更新状态 / 沉淀长期信息
3.1 先抽取本轮意图和关键信息
用户输入进来之后,第一步是先把当前这句话里的关键信息抽出来。
比如这句话:
帮我继续刚才那个耳机订单的退款。
可以抽成:
{
"intent": "continue_refund",
"product_hint": "耳机",
"reference": "刚才那个",
"action": "继续处理退款"
}
这样后面无论是查 Redis、查 MySQL,还是检索历史记录,都有了明确方向。
3.2 再按顺序读取不同来源的记忆
拿到本轮意图之后,就可以开始读取记忆。
读取顺序可以遵循一个简单原则:
先取确定信息,再取补充信息。
比如当前会话上下文里,可能记录了用户之前提到过“昨天买的耳机”;Redis 里可能保存了当前会话绑定的 current_order_id;MySQL 里有订单和退款单的真实状态;向量库或知识库里,可能有售后规则说明。
可以整理成这样:
会话上下文:用户刚才提到耳机订单
Redis:current_order_id = order_123
MySQL:订单已签收,退款单未创建
知识库:耳机类商品拆封后需要判断是否影响二次销售
这些信息的优先级不一样。
会话上下文负责补充指代关系;
Redis 负责保存当前流程状态;
MySQL 负责提供真实业务数据;
知识库负责解释规则。
最后进入 Prompt 的内容,应该是筛选后的关键信息,而不是原始数据一股脑塞进去。
可以组装成:
用户意图:继续处理退款
当前订单:order_123
商品类型:耳机
订单状态:已签收
退款状态:未创建
相关规则:耳机类商品拆封后,需要判断是否影响二次销售
这样模型看到的信息更短,也更接近最终回答需要的材料。
3.3 回复之后,及时更新状态
如果这一轮对话推动了业务流程,系统还要及时更新状态。
例如模型回复用户:
可以继续为你申请退款,我需要先确认一下耳机是否已经拆封,以及是否影响二次销售。
这时候系统状态也应该同步变化:
{
"current_order_id": "order_123",
"refund_step": "waiting_user_confirm_package_status",
"required_info": ["package_opened", "affect_resale"]
}
下一轮用户只说:
拆了,但没怎么用。
Agent 也能知道这句话是在回答“是否拆封、是否影响二次销售”,而不是重新开启一个新话题。
写在文后
期待您的一键三连!如果有什么问题或建议欢迎在评论区交流!
更多推荐
所有评论(0)