🔥 个人主页:铁皮哥(欢迎关注)
📌 作者简介: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 也能知道这句话是在回答“是否拆封、是否影响二次销售”,而不是重新开启一个新话题。


写在文后

期待您的一键三连!如果有什么问题或建议欢迎在评论区交流!

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐