前面两篇把Agent的框架和工具调用捋完了,今天这篇聊聊我踩坑最多、也最能拉开体验差距的部分——记忆。如果一个Agent聊完就忘,那它本质上就是个带流畅话术的搜索引擎,称不上真正的“助手”。只有让Agent记住你的偏好、习惯、项目背景、上次聊到一半的结论,它才从玩具变成生产工具。这篇我直接把记忆系统拆开,讲清楚短期记忆与长期记忆的实现思路、方案选型,再用LangGraph搭一个可持续跨会话记忆的Agent实例,最后把我在生产环境里踩过的坑和排查经验一并交代。
1. 为什么“记住了”才是合格的Agent
1.1 无状态Agent的尴尬:聊过就忘
先说不加记忆的Agent是什么体验。你用同一个Prompt或者同一个API Key拉起一个Agent,跟它说“我喜欢简洁的回答风格,不要铺陈”,它当场答应得好好的;第二天再打开同一个对话页面,问它“按我昨天说的风格给我写个邮件”,它一脸茫然,好像昨天的话从来没发生过。这不是模型不行,是因为每次请求都是“无状态”的,模型看到的内容只有你这次发出去的消息加上系统提示词,它没有任何“昨天”的概念。
无状态在API调用层面是天然属性,大模型本身不对持久化数据负责。所有的“记忆”本质上是应用层帮它补的——把该记的存下来,在合适的时候重新塞回它的上下文里。理解这一点特别关键,很多人以为换个贵的模型记忆就解决了,其实记忆跟模型能力是两回事,再强的模型,你不给它历史信息,它也猜不出你上周的偏好。
在生产项目里,无状态带来的问题更具体。用户连续问了三个问题,第一个问题说“我在用Spring Boot 3.2,JDK 21”,第二个问题说“帮我看看这个报错”,第三个问题说“我的环境还需要什么配置”,如果Agent不能把三个问题串起来,第二个问题它得让用户重新贴一遍报错,第三个问题它得再问一遍用户的技术栈。用户不是来伺候你的,这种体验基本等于劝退。
加了记忆之后,Agent的行为表现会发生质变:多轮对话能承上启下,跨会话能延续偏好,甚至能根据历史行为主动做推测。这个质变背后其实就是一个简单的闭环——写入记忆、存储记忆、检索记忆、注入上下文。难的不是概念,而是怎么把这个闭环在企业级场景里做得不炸、不贵、不泄露隐私。
1.2 记忆的分层模型:短期记忆、长期记忆与情景记忆
我在设计记忆系统时,喜欢先把记忆拆成三层。这个分层不是学术概念自嗨,而是直接对应不同的存储介质和访问策略,架构上分清楚了,后面写代码才不拧巴。
第一层是短期记忆,说白了就是当前会话里模型能看到的那部分上下文,包括用户最近几轮发言、Agent最近的回复、正在执行的任务中间态。这一层通常放在上下文窗口里,实现也最简单——历史消息按时间顺序拼进Prompt。但它的上限被上下文长度锁死,正常不会真把几万条历史全塞进去,所以需要窗口化策略:只保留最近的N轮,再往前的就压缩或移交到下一层。
第二层是长期记忆,跨会话持久化的信息都归它管。典型内容有用户的固定偏好(称呼、语言风格、信息详略程度)、项目背景(技术栈、业务领域、关键约束)、历史结论(上次讨论的方案、已经确认的需求)。这一层适合放到外部存储:关系数据库存结构化偏好,向量数据库存非结构化的语义记忆,或者干脆两者结合。长期记忆的核心问题是“怎么挑出值得记的”,以及“检索时怎么把最相关的记忆找回来”。
第三层是情景记忆,记录的是某个具体时间点发生过的完整事件。比如用户上周五在工单里提到“订单模块经常超时”,这条信息包含时间、事件、情绪和上下文,它既不是一条偏好,也不是一条稳定的知识,而是一段历史。情景记忆的价值在于做推理时能引用具体事件,比如Agent可以说“按照您上周五反馈的订单超时问题,我调了数据库连接池参数”。这层往往用日志型存储或时间序列数据库承载,检索时按事件语义和时间范围双维度匹配。
三层之间不是孤立的,实际运行时有一条数据流:会话中的短期记忆快塞不下时,触发提炼逻辑,把关键信息抽取到长期记忆和情景记忆;下一次会话开始时,再根据用户的新输入检索长期记忆,把相关片段注入到Prompt里作为背景。这套“写—存—检—注”的循环,就是Agent记忆系统的完整骨架。
2. 记忆系统实现方案与选型思路
2.1 最朴素的记忆:把历史对话塞进上下文
先说一种几乎所有做过ChatBot的人都会上手的方案——直接把历史对话拼进上下文。实现成本极低,用一个列表接收历史消息,请求模型时按角色前缀拼接,比如把用户消息标成Human,Agent消息标成AI,然后整体传给模型。这套方案在对话轮次少、单轮内容短的场景下完全够用,尤其是客服问答类的临时对话,用户问一句答一句,不需要跨会话记忆,短期记忆用上下文拼接就够了。
但把历史对话塞进上下文的坑很快会暴露。第一个坑是Token成本爆炸。假设每轮对话平均消耗800个Token,50轮下来就是4万个Token,按主流模型的价格算,一次请求的成本已经从几分钱跳到几块钱,业务量一大成本直接失控。第二个坑是模型“注意力稀释”——上下文里塞了太多早期内容,模型对最近关键信息的关注度反而下降,尤其是中间混杂了一些无关闲谈时,效果会明显变差。第三个坑是历史消息不做结构化区分,所有信息一视同仁,用户临时说了一句“这个颜色我不喜欢”,跟用户半年前说“我偏好深色模式”,在拼接方案里都变成了同样权重的消息,模型根本分不清哪个是长期偏好哪个是随口一提。
所以这个方案我给出的结论是:适合原型验证和低并发内部工具,不适合生产级助手。真正要上生产,你的记忆系统至少要能回答两个问题——哪些信息值得跨会话保留,以及下一次用户提问时,怎么把最相关的记忆捞出来。这就引出了下一节说的向量检索方案。
我在实际项目中还见过一种中间态优化:不拼接全部历史,而是用LLM定期把历史对话“总结压缩”成一段摘要,然后只拼摘要和最近几轮全文。这个思路比全量拼接聪明得多,摘要保留了大方向信息,最近几轮保留细节上下文。实现并不复杂,但摘要有一个天然缺陷——它是一次性压缩,细节会丢,用户半年前提到的一个关键项目名,在摘要里可能被一句话带过,而检索时你又不能像向量库那样按语义命中它。所以摘要方案适合做短期记忆的延伸,不适合做长期记忆的主体。
2.2 向量检索型长期记忆:让Agent“想得起来”
长期记忆想要在跨会话场景中“想得起来”,主流做法是向量化存储加语义检索,本质跟RAG一样:把记忆内容切成片段,用Embedding模型转成向量,存入向量数据库;用户提问时,把问题也转成向量,在库里做相似度检索,取回最相关的记忆片段,注入Prompt。这套方案的好处是打破了“必须精确匹配关键词”的限制,用户说“我想起了之前你帮我配置的链路追踪”,即使历史里没有“链路追踪”这个词,但向量相似度能把“SkyWalking采集日志链路”这条记忆捞出来,这是关键词搜不到的。
具体到落地,有几个环节必须仔细打磨。第一是记忆片段的粒度,切太粗,一段记忆里混了多个主题,检索命中时会把无关内容一起带进来;切太细,又会被打断成碎片,语义不完整。我在实践中一般按“一个自然段落讲清一件事”为标准切分,长度控制在100到300字之间。第二是Embedding模型的选择,通用Embedding模型对技术术语和业务黑话的效果比较弱,尤其在企业内部场景,最好在专用语料上做微调,否则检索Top 5里可能有两三条完全无关。第三是元数据打标,每条记忆入库时至少带上用户ID、存入时间、来源会话ID、记忆类型,这样检索时可以按用户ID过滤,避免A用户的记忆污染B用户。
向量数据库的选型我分别趟过几类。如果只是想快速验证,Chroma或者FAISS本地跑都行,数据量在十万条以下时响应还可以。要上生产,我建议直接用具备服务化能力的向量库,比如Milvus或Qdrant,它们对数据的持久化、索引构建、并发查询处理得更成熟。还有一条路是直接用云厂商的向量检索服务,省去运维,适合团队规模不大的场景。选型时除了检索性能,记得重点看过滤能力,大多数场景不是全库检索,而是先按用户维度过滤,再在子集里做相似度,这个能力很多轻量级库并不完善。
另外我想强调一点:向量检索是“模糊回想”,不是“精确读档”。它适合把相关背景捞出来,但不适合做精确的事实查询。比如用户问“我上个月买的套餐多少钱”,如果这个价格信息在向量库里以长文本存储,检索可能命中了一条相关的但价格已经被修改的旧记录。所以在做记忆系统时,最好把“固定事实型记忆”(用户ID、套餐、缴费日期)用结构化字段存关系库,把“语义型记忆”(偏好、项目背景、讨论结论)存向量库,两类配合,各干各擅长的事。
2.3 主流Agent框架的记忆方案对比
最近这个方向框架乱得很,LangGraph、Spring AI、LlamaIndex、AutoGen都有各自的记忆实现,选型时容易眼花。我实际用下来,把它们放在一起对比更有感觉。
LangGraph是目前我用得最多的,它的核心思路是把Agent定义成一个图:节点是处理逻辑,边是流转关系,状态在节点间传递。记忆功能基于两个机制:一个是Checkpointer,负责把每一步的状态快照持久化,这样Agent执行一半宕机可以恢复,也能让同一个会话的上下文跨请求续上;另一个是外部存储,你自己接向量库或数据库来保存跨会话的长期记忆。LangGraph上手的门槛确实高一些,但灵活度最高,适合复杂流程和深度定制的生产项目。
Spring AI对Java生态很友好,如果你的团队全是Java开发者,那它是自然选择。它在记忆方面提供了ChatMemory接口,内置了MessageWindowChatMemory这种基于滑动窗口的实现,直接把最近N轮消息管理好;长期记忆则可以配合Spring的VectorStore抽象接各类向量库。用Spring AI搭一个带短期记忆的Agent非常快,几行配置就能跑起来,但要做复杂的记忆提炼、多级记忆融合,需要自己再写不少胶水代码。
LlamaIndex本身侧重知识库和检索增强,它天然适合做“给Agent外挂记忆”的场景。它的ChatMemoryBuffer管短期记忆,VectorIndex管长期检索,如果你已经有文档知识库,想把知识库和会话记忆打通,LlamaIndex会比较顺手。AutoGen是多Agent协作框架,记忆的侧重点不在单Agent的个性化记住,而在多Agent之间共享上下文和任务状态。
选型建议用一句话收束:先看团队技术栈,再看业务复杂度,最后看记忆的核心诉求是“续上当前会话”还是“跨会话个性化”。如果只是续会话,用Spring AI的内置方案就够了;如果要做企业级的长期记忆和人设一致性,LangGraph的可控性会更强。
3. 实操:用LangGraph搭一个带永久记忆的Agent
3.1 环境准备与基础代码骨架
这一节我把前面讲的概念全部落到代码里。直接上LangGraph搭一个带长期记忆的Agent,流程是:先实现短期记忆(跨请求续会话),再实现长期记忆(从向量库存取用户偏好),最后加一条记忆提炼链路。环境准备先列一下,我用的是Python 3.11,框架依赖如下:
pip install langgraph langchain langchain-openai langchain-community chromadb一个最小的LangGraph Agent通常包含状态定义、节点函数和编译三部分。状态用MessagesState表示,它内部维护了一个消息列表,每次节点执行后会把新产生的消息追加进去。先搭一个最简单的问答节点:
from typing import TypedDict, Annotated from langgraph.graph import StateGraph, MessagesState, START, END from langchain_openai import ChatOpenAI from langchain_core.messages import HumanMessage, AIMessage, SystemMessage model = ChatOpenAI(model="gpt-4o-mini", temperature=0.7) def call_model(state: MessagesState): response = model.invoke(state["messages"]) return {"messages": [response]} # 构图 graph = StateGraph(MessagesState) graph.add_node("assistant", call_model) graph.add_edge(START, "assistant") graph.add_edge("assistant", END) agent = graph.compile()现在这个Agent还是无状态的,你调用一次agent.invoke(),聊完就结束。要让它的短期记忆跨请求生效,需要引入Checkpointer。
3.2 用Checkpointer实现跨会话状态持久化
Checkpointer是LangGraph里管短期记忆的核心组件。它会在每个节点执行后把整个状态保存下来,下次推进图时能从保存的状态继续走。最直观的体现是:你传入一个thread_id,它就能恢复属于这个线程的历史对话,相当于给Agent开了“内存档案”。
我用SQLite作为持久化存储,先创建一个连接并传入编译参数:
from langgraph.checkpoint.sqlite import SqliteSaver # 使用内存型SQLite,生产环境建议换成Postgres或MySQL的checkpointer with SqliteSaver.from_conn_string(":memory:") as saver: agent = graph.compile(checkpointer=saver) config = {"configurable": {"thread_id": "user-123"}} # 第一轮对话 result1 = agent.invoke( {"messages": [HumanMessage(content="请记住,我叫陈晨,偏好用简洁的列表回答技术问题")]}, config=config ) # 第二轮对话,Agent已经能看到第一条历史 result2 = agent.invoke( {"messages": [HumanMessage(content="我叫什么名字?")]}, config=config ) print(result2["messages"][-1].content)运行之后能看到第二轮模型会正确回答“陈晨”。机制是LangGraph把整个MessagesState按thread_id存进了SQLite,第二轮invoke时,图先从库中加载历史消息,再拼接新消息传给模型。
这里有个容易被忽略的细节:checkpointer传入的SqliteSaver必须是同一个连接实例,才能保证状态共享。如果你在每次请求里都新建一个SqliteSaver,那thread_id变成了一串没有记忆库的钥匙,什么也查不到。生产环境里我会创建一个模块级单例连接,或者在FastAPI应用启动时初始化一次,避免反复开关数据库连接。
短期记忆到这里工作正常,但问题在于它记得太多了。这个Agent会把所有消息原封不动存进SQLite,时间一长,单次推理时传给模型的消息列表会无止境膨胀。更麻烦的是,它只是记住了“全部”,并没有甄别哪些信息值得长期保留,用户随口一句“今天下雨了”也会被永久存着,没有任何价值。所以下一节要解决的关键问题就是——从大量历史状态里提取值得长期记住的信息,并把它放进向量库。
3.3 增加记忆检索与注入:从向量库召回用户偏好
长期记忆我选择Chroma做向量库,存储两类信息:一是从历史对话中提炼出的用户偏好,二是关键事件记录。先把基础检索链路搭出来:
from langchain_openai import OpenAIEmbeddings from langchain_community.vectorstores import Chroma memory_store = Chroma( collection_name="agent_long_term_memory", embedding_function=OpenAIEmbeddings(model="text-embedding-3-small") )接着在Agent的图里增加两个节点:retrieve_memory负责从向量库检索与当前输入相关的记忆,extract_memory负责把本轮对话中的关键信息提炼并写入向量库。先实现检索节点:
def get_last_user_message(messages): for m in reversed(messages): if isinstance(m, HumanMessage): return m.content return "" def retrieve_memory(state: MessagesState, config) -> dict: # 取出最后一次用户输入 query = get_last_user_message(state["messages"]) if not query: return {} user_id = config["configurable"].get("user_id", "default_user") # 先按用户过滤,再语义检索,避免跨用户记忆串场 docs = memory_store.similarity_search( query, k=5, filter={"user_id": user_id} ) memory_text = "\n---\n".join(doc.page_content for doc in docs) return {"memory_context": memory_text}状态里加上memory_context字段后,模型调用节点需要把它拼进SystemMessage。这里我强烈建议不要直接把检索结果塞进用户消息,而是塞进系统提示词。原因在于系统提示词在模型眼里是权威级别的信息,用户消息则可能被当成可忽略或可质疑的输入,塞进系统提示词能让模型更重视这些回忆片段。
接着实现记忆提炼节点。这一步是长期记忆能不能“进化”的核心。我用一轮独立的LLM调用做信息抽取,把用户输入里值得长期记住的内容挑出来,结构化后写入向量库:
import json def extract_memory(state: MessagesState, config) -> dict: user_id = config["configurable"].get("user_id", "default_user") # 取本轮新增的对话 messages = state["messages"] if len(messages) < 2: return {} conversation_pair = f""" 用户说:{messages[-2].content if isinstance(messages[-2], HumanMessage) else ""} Agent说:{messages[-1].content if isinstance(messages[-1], AIMessage) else ""} """ extraction_prompt = f""" 从下面的对话中提取需要长期记住的用户信息,包括但不限于: - 用户固定偏好(称呼、风格、语言习惯) - 用户的项目背景(技术栈、业务领域、团队规模) - 用户明确表达的需求和结论 - 重要的事件记录(时间、对象、结果) 只输出JSON列表,每项包含type和content两个字段。 对话内容:{conversation_pair} """ llm_output = model.invoke([HumanMessage(content=extraction_prompt)]).content try: memories = json.loads(llm_output) except json.JSONDecodeError: memories = [{"type": "unknown", "content": conversation_pair}] for mem in memories: memory_store.add_texts( texts=[mem["content"]], metadatas=[{ "type": mem.get("type", "unknown"), "user_id": user_id, "created_at": datetime.now().isoformat() }] ) return {}最后重构图,把两个新节点加入执行链路:
graph = StateGraph(AgentState) graph.add_node("assistant", call_model) graph.add_node("retrieve_memory", retrieve_memory) graph.add_node("extract_memory", extract_memory) graph.add_edge(START, "retrieve_memory") graph.add_edge("retrieve_memory", "assistant") graph.add_edge("assistant", "extract_memory") graph.add_edge("extract_memory", END) agent = graph.compile(checkpointer=saver)AgentState需要增加memory_context字段,模型调用节点改为读取它并拼进SystemMessage:
def call_model(state: AgentState): memory_ctx = state.get("memory_context", "") system_prompt = f"""你是用户的专属助手。 以下是关于用户的长期记忆,请在实际答复中主动参考: {memory_ctx}""" messages = [SystemMessage(content=system_prompt)] + state["messages"] response = model.invoke(messages) return {"messages": [response]}这一套跑下来,效果和之前的无记忆Agent完全不同。第一轮用户说“我叫陈晨,负责公司的支付系统,偏好列表式回答”,第二轮用户隔天再问“帮我写一封给技术部同事的邮件,催一下支付网关的联调进度”,系统会从向量库检索到陈晨的姓名、业务领域和表达偏好,模型会直接以“陈晨”的身份落款,并自动采用列表式、干脆利落的表达风格。这就是长期记忆带来的体验跃迁。
注意一个性能细节:extract_memory每一次对话都要调用一次LLM,如果对话频率高,Token成本会翻倍。我在生产里常用两个降本手段,一是限制触发频率,比如每五轮对话或每三分钟才做一次提炼;二是增加“信息价值过滤”,让提炼Prompt只输出有长期价值的信息,对那些日常闲聊直接返回空列表。
4. 记忆工程的坑与经验
4.1 记忆污染的后果与清洗策略
记忆系统最隐蔽、最难受的问题不是“记不住”,而是“记错”。我在一个客服项目里吃过一次大亏:用户反馈“订单总是超时”,当时上下文里有另一条关于“网络抖动”的闲聊,提炼节点无差别地把两条信息混合成一条“订单超时与网络不稳定有关”的记忆写进了向量库。之后Agent在回答所有订单相关问题时,都把这条不准确的归因当事实引用,输出结果开始出现幻觉式的错误推理,而且因为每次都会检索到这条记忆,错误被一遍遍强化。
这个问题的根源在于记忆提炼节点缺少校验。提炼模块本质上也是LLM,它会把推理和事实混在一起。我的解决方案分两层。第一层是让提炼Prompt尽量“事实化”——只抽取用户明确说出的陈述性内容,禁止模型自行推断因果,拿不准的信息放弃写入。第二层是建立记忆“可信度”机制,每条记忆带上数据来源和置信度分数,模型引用记忆时能根据来源判断权重;同时定期用一版独立LLM对存量记忆做审计,把与近期对话明显矛盾的记忆标记为过期。
另外我建议大家给长期记忆加一个“删除接口”,千万不要把记忆库做成只写不删的黑洞。我遇到过团队把记忆库跑了一年,里面堆了数万条冗余记忆,检索Top 5经常被陈旧记忆占据。后来我在管理后台单独做了一个“记忆管理”页面,支持按用户查看记忆内容、手动删除单条、一键清空,实时性和准确性都好了很多。
4.2 上下文膨胀:Token成本与性能平衡
记忆系统的初衷是让Agent更聪明,但设计不好反而让推理变慢变贵。短期记忆里存了太多历史消息,每次请求造Prompt时把这堆消息全部塞进去,模型处理时间拉长,Token费用直线上升。我见过一个团队因为短期记忆不设上限,每次请求的Prompt居然有十多万Token,一次对话下来成本比人工客服还贵。
成本控制的做法我总结成“三限”:
- 限轮数:短期记忆默认只保留最近10到20轮消息,超过的移出上下文,触发一次摘要后归档到长期记忆。
- 限长度:单条记忆入库前先做裁剪,超过300字的内容通过LLM压缩成一句话摘要再存储。
- 限召回量:向量检索Top K控制在3到5条,多了不仅费Token,还容易灌入无关信息。
这三个限制看着简单,但对稳定性和成本的改善非常明显。我优化过的一个项目,把Prompt从平均4万Token压到了8000Token以下,响应时间从8秒降到2秒左右,模型输出质量反而更稳定,因为干扰信息少了。
这里还有一个容易被忽略的平衡点:记忆不是越多越好,召回不是越准越好。记忆的目的是帮助模型“聚焦”,一旦Prompt里塞入了过多背景,模型会陷入选择困难,反而忽略用户当前的核心问题。所以在做检索排序时,我不仅看相似度分数,还会加上时间衰减因子——距离当前时间太久的记忆,相似度分数做一下打折,这样近期信息优先,避免老记忆抢占新信息的位置。
4.3 多Agent场景下的记忆隔离与共享
稍微复杂一点的系统不会只有一个Agent。我的一个项目里跑了三个Agent:一个负责售前咨询,一个负责售后工单,一个负责内部知识问答。如果它们共享同一个记忆库,会出现很尴尬的场景:用户在售前Agent那聊了“预算有限,希望找性价比高的方案”,到了售后Agent那边,这个信息被检索出来,售后Agent开始推荐低价产品,但用户此时关注的是故障处理,这种错位很让人抓狂。
解决方案是给记忆加“命名空间”。我在所有记忆元数据里固定维护三个字段:user_id表示这条记忆属于哪个用户,agent_id表示这条记忆在哪个场景下产生,scope_type表示这条记忆的可见范围。可见范围有三种取值:private只对当前Agent可见,shared在多个Agent之间可见,team在用户所属团队内可见。
默认创立的所有记忆都是private,只有用户明确表示“这个偏好对所有服务都生效”或者“告诉我的专属客服经理”时,才通过一个显式的转换流程升级为shared或team。这个设计既保住了多Agent协作时的信息共享效率,也防止了各场景信息乱窜导致的体验劣化。做好隔离还有一个额外好处:调试时能按Agent维度单独查记忆,定位问题快很多。
4.4 隐私边界:该记住什么,该忘掉什么
最后说一个容易被技术团队忽视的话题——记忆的隐私和合规边界。Agent能够记住用户,本身就意味着它掌握了大量个人数据,如果记忆库不做隐私控制,一旦泄露就是安全事故。
我在设计记忆系统时有几个硬性原则,所有业务不管大小都套用。第一是“最小必要”:只记录支撑服务所必需的信息,用户没提的、跟服务无关的敏感信息(身份证号、银行卡号、完整家庭住址)一律不进记忆库,提炼节点在写入前会自动做敏感信息脱敏,把数字替换成占位符。第二是“用户可控”:给用户提供查看自己记忆内容的入口,支持一键导出和一键清除,这既是合规需要,也让用户对Agent产生信任感。第三是“存储隔离”:记忆库在物理或逻辑上与主业务数据库隔离,访问凭证分权,只有Agent服务本身有读写权限,内部后台查看也需要单独授权。
有些记忆是需要主动遗忘的。比如用户请求删除历史数据、用户注销账号、或者某条记忆内容与后续事实矛盾,这些场景都应该有一条定时清理任务把对应记忆抹掉。我踩过的一个坑是:用户要求删除账号,我把关系库的数据清了,但向量库里还留着几千条该用户的记忆,导致几个月后新用户注册时偶发检索到前一个人的信息,这类事故在合规审计里非常麻烦。所以清理逻辑一定得同步覆盖所有存储介质——关系库、向量库、对象存储,少一个都不行。
5. 写在后面:记忆是Agent的人设底座
这套记忆系统的雏形,是我在一个企业内部助手项目里一点一点打磨出来的。最开始只是为了让Agent记住用户的称呼,后来发现记住偏好比记住称呼重要,再后来发现会“遗忘”比会“记住”更重要。走到现在,我对记忆系统的理解变成了一个非常朴素的总结:记忆不是为了堆砌数据,而是为了让Agent在恰当的时机,展现出“它真的懂你”的那一面。
如果这篇对你有一点启发,我建议你先从最简单的一步开始——给现有Agent加一个线程级的短期记忆,让同一个用户的多轮对话能承上启下。这一步做完,再去想向量库、记忆提炼、多Agent共享。记忆系统不是一个一次性的功能,而是一套持续演的机制,它需要你随着业务反馈不断调整提炼规则、检索策略和过期清理策略。最后再分享一个小技巧:调试记忆Agent时,多打印“这次检索到了什么记忆”,亲眼看到召回内容是否合理,比只看最终回答更有效。