Agent 记忆系统:短期、长期和工作记忆

客服 Agent 上线一周,用户满意度没涨,投诉倒是翻了倍。

老李把团队拉到会议室,投影仪上是一条条用户反馈:“每次都要我重新说一遍订单号”、“昨天刚聊过,今天又问我是谁”、“这AI是不是只有七秒记忆”。

“这怎么回事?”老李的保温杯重重顿在桌上,“我昨天跟Agent说我是技术部老李,今天早上一开对话,它又问‘请问您是哪个部门的’。这不是傻子吗?”

小王举手:“老李你想啊,你养的金鱼,每天早晨看见你都会吓一跳——因为它根本不记得昨天喂它的是谁。咱们现在的Agent,就是那条金鱼。”

“那怎么给它装个脑子?”

“不是装一个脑子,是三层。就像你办公桌上的便签纸、你的笔记本、还有公司的档案室——它们各管各的,合在一起才叫记忆。”


三层记忆:便签纸、笔记本和档案室

小王走到白板前,画了三个圈,从小到大。

“第一层,工作记忆。就是你桌上的便签纸。写满了随手就扔,容量极小,但存取极快。对大模型来说,这就是它的上下文窗口——当前对话中,你能塞进去的所有内容。模型最多只能同时‘看见’这么多字,超了就得把前面的擦掉。”

老李点头:“这个我知道,GPT的上下文窗口好像挺大?”

“现在是大了,但也不是无限。而且你把一百页的历史对话全塞进上下文窗口,响应速度就会断崖式下滑。所以需要第二层——短期记忆。”

小王指着第二个圈:“这是你的笔记本。一页一页翻,能记住整个会议讨论了什么,但它不跨会话。换个新项目,旧笔记本就归档了。在Agent里,短期记忆用会话摘要来实现——当对话太长,就把前面聊过的内容压缩成一段摘要,清掉原文腾出空间。”

“第三层,长期记忆。这是档案室。能跨会话、跨项目、跨年来记住用户的关键信息——老李是技术部负责人,偏好用顺丰发货,不喜欢收到邮件通知。这些信息存一次,管一年。技术实现通常用向量数据库,把用户偏好和历史交互存进去,下次对话时检索相关的记忆。”

图:Agent 记忆的三层架构,各司其职


工作记忆与短期记忆:不要让上下文窗口爆炸

“短期记忆具体怎么实现?总不能每次都做摘要,那也挺贵的。”老李追问。

“两种方式互补。第一种是滑动窗口——只保留最近 N 轮对话的原文,太老的就扔掉。优点是简单,零成本;缺点是真有用的信息可能在窗口外被丢了。”

小王在终端上写了一段示意逻辑:

python
# 滑动窗口:只保留最近 10 轮对话
def manage_context_window(messages, max_turns=10):
    """保留最近 N 轮对话,过期的直接丢弃"""
    if len(messages) > max_turns * 2:  # 每轮包含 user + assistant
        return messages[-(max_turns * 2):]
    return messages

“第二种是摘要压缩。当对话超过一定长度,就把前面的内容用 LLM 总结成一段摘要,替换掉原文。摘要长度可控,关键信息不丢。”

python
# 摘要压缩:将历史对话压缩为一小段文字
def summarize_conversation(old_messages):
    """将旧的对话历史压缩为摘要"""
    prompt = f"请将以下对话压缩为一段简洁的摘要,保留关键信息:\n{old_messages}"
    summary = llm.generate(prompt)
    # 把摘要塞回上下文,释放原文空间
    return summary  # 例如:"用户老李是技术部负责人,正在处理订单#12345的退货事宜"

“实际项目里,两者结合——最近的 5 轮保留原文,更早的压缩成摘要。既能跟上当前话题,又不丢失前面的关键信息。”

老李若有所思:“那如果用户在对话中间突然说‘记住我下次用顺丰’,这句话在摘要里会不会丢了?”

“这就是长期记忆要解决的问题。”


长期记忆:让Agent拥有老朋友般的记忆

“长期记忆存在向量数据库里,跨会话持久化。”小王打开一段代码,“当用户说‘以后都用顺丰发货’,Agent在回复之后,会自动把这个偏好存进长期记忆。下次用户再下单,Agent从数据库里检索出这条偏好,主动说‘老李,还是用顺丰对吧?’”

他写了一个精简版的三层记忆实现:

python
class AgentWithMemory:
    def __init__(self, vector_db, llm):
        self.short_term = []        # 短期:当前会话消息
        self.long_term = vector_db  # 长期:向量数据库
        self.llm = llm

    def chat(self, user_id: str, message: str) -> str:
        # 1. 检索长期记忆:用户偏好、历史关键信息
        memories = self.long_term.search(f"{user_id}: {message}", top_k=3)
        memory_context = "\n".join([m["content"] for m in memories])

        # 2. 组装工作记忆:系统提示 + 长期记忆 + 当前对话
        context = [
            {"role": "system", "content": f"用户{user_id}的相关记忆:\n{memory_context}"},
            *self.short_term[-10:],  # 最近5轮原文
            {"role": "user", "content": message}
        ]
        response = self.llm.generate(context)

        # 3. 更新短期记忆
        self.short_term.append({"role": "user", "content": message})
        self.short_term.append({"role": "assistant", "content": response})

        # 4. 自动提取并存储长期记忆(异步,不阻塞当前回复)
        if "记住" in message or "以后" in message:
            self._store_memory(user_id, message)

        return response

    def _store_memory(self, user_id, memory_text):
        """将用户明确的偏好存入长期记忆"""
        self.long_term.add(
            id=f"{user_id}_{hash(memory_text)}",
            content=f"用户{user_id}的偏好:{memory_text}",
            metadata={"user_id": user_id}
        )

老李仔细扫完这段代码,指着 _store_memory 问:“靠关键词‘记住’和‘以后’来触发记忆存储,是不是太粗暴了?万一用户说‘我不记得了’,也触发?”

“对,生产环境不会这么简单。更好的做法是每次对话结束后,异步跑一个记忆提取器——用LLM把整个对话扫描一遍,自动提取出值得长期保存的事实和偏好。用户说‘我家地址是xxxx’、‘我在处理合同#C2024’,这些事实型信息会被自动抓出来存进长期记忆。不需要用户显式地说‘记住我’。”


记忆的遗忘曲线:不是越多越好

“那是不是什么都记住就更好?”老李反问,“如果用户一年前随口说过一句话,Agent今天还拿出来,也挺吓人的吧?”

“这就是记忆管理中最容易忽视的问题——遗忘机制。人类大脑之所以高效,不是因为记住了所有,而是因为知道该忘什么。”

小王在白板上画了一条艾宾浩斯遗忘曲线:“Agent的记忆也需要衰退机制。两个月没用过的记忆,自动降低权重;用户纠正过的错误记忆,立即覆盖;用户明确要求删除的,彻底移除。还有——有些记忆有保质期,比如‘老李这周出差’,过了这周就该自动失效。”

老李靠在椅背上,叹了口气:“所以记忆不是存进去就完事了,还要管理——更新、过期、权重调整、隐私控制。又是一个系统。”

“对。但相比让Agent每次都重新认识用户,这点投入绝对值得。”

老李沉默了一会儿,拧开保温杯喝了一口。

“行。先给客服Agent加上短期记忆——滑动窗口加摘要。长期记忆先做用户偏好这一块,订单历史、物流偏好。其他的慢慢加。”

他站起来,走了两步又回头:“那什么,你刚才说用LLM自动从对话里提取记忆,这个提取的prompt怎么写?它怎么判断哪些值得长期记、哪些是废话?给我看看。”

小王咧嘴笑了,把提取器的代码调出来。老李端着保温杯凑到屏幕前,枸杞在杯里晃晃悠悠,像那些终于被妥善保管的记忆碎片,不再随每次对话的结束而被扔进虚空。

任务规划:把大象放进冰箱需要几步?
给 Agent 装上超能力:Tool Calling 完全指南