所有文章

Agent 对话记忆化:从原理到实现

做 Agent 项目绕不开一个问题:大模型本身没有记忆,每次调用都是无状态的。所谓"记忆",本质上是把历史信息塞进下一次请求的 prompt 里。这篇文章从原理出发,整理三种主流实现方案。

做 Agent 项目绕不开一个问题:大模型本身没有记忆,每次调用都是无状态的。所谓"记忆",本质上是把历史信息塞进下一次请求的 prompt 里。这篇文章从原理出发,整理三种主流实现方案。

核心问题

大模型每次调用都是独立的,它不知道你上一轮说了什么。"记忆"的实现原理是这样的:

第1轮:[system] + [user: 你好]
第2轮:[system] + [user: 你好] + [assistant: 你好!] + [user: 我叫古恩豪]
第3轮:[system] + [前两轮历史] + [user: 我叫什么?]
                    ↑
              这就是"记忆"——把历史塞进 prompt

问题是 history 会无限增长,迟早超出 context window(Claude 200k token,GPT-4 128k token)。所以需要压缩策略。


方案一:滑动窗口

最简单的方案,只保留最近 N 轮,超出就丢掉最早的。

class ConversationAgent:
    def __init__(self, max_turns=10):
        self.history = []
        self.max_turns = max_turns
    
    def chat(self, user_input):
        self.history.append({"role": "user", "content": user_input})
        
        # 超出就截断,只保留最近 N 轮
        if len(self.history) > self.max_turns * 2:
            self.history = self.history[-self.max_turns * 2:]
        
        response = llm.call(messages=[
            {"role": "system", "content": "你是一个助手"},
            *self.history
        ])
        
        self.history.append({"role": "assistant", "content": response})
        return response

优点: 实现极简,延迟低,token 消耗可控
缺点: 早期信息完全丢失,用户说"你之前提到的那个方案"就找不到了

适合:客服、简单问答,对话之间关联性弱的场景。


方案二:摘要压缩

超过阈值时,把旧对话喂给 LLM 让它总结,摘要替代原始内容。

class ConversationAgent:
    def __init__(self, max_turns=20):
        self.history = []
        self.summary = ""
        self.max_turns = max_turns
    
    def _compress(self):
        to_compress = self.history[:self.max_turns // 2]
        self.history = self.history[self.max_turns // 2:]
        
        prompt = f"""
已有摘要:{self.summary}

新增对话:
{self._format(to_compress)}

请将以上内容合并更新为简洁摘要,保留关键信息(用户说了什么、决定了什么、重要的上下文):
"""
        self.summary = llm.call(prompt)
    
    def chat(self, user_input):
        self.history.append({"role": "user", "content": user_input})
        
        if len(self.history) > self.max_turns:
            self._compress()
        
        messages = [{"role": "system", "content": "你是一个助手"}]
        if self.summary:
            messages.append({
                "role": "system",
                "content": f"[对话历史摘要]\n{self.summary}"
            })
        messages.extend(self.history)
        
        response = llm.call(messages=messages)
        self.history.append({"role": "assistant", "content": response})
        return response

优点: 信息损失可控,关键内容保留
缺点: 摘要本身消耗 token,有额外延迟;摘要质量依赖 LLM

这是 Claude Code 本身用的方案——你现在看到的对话 Summary 就是这个机制。


方案三:分层记忆

三层结构,各司其职:

短期记忆(最近5轮)  → 直接放进 prompt,保证对话连贯性
     ↓ 超出阈值
中期记忆(摘要)     → LLM 压缩,作为 system message
     ↓ 全量存储
长期记忆(向量库)   → 历史向量化,按当前问题检索相关片段
from sentence_transformers import SentenceTransformer
import numpy as np

class AgentWithMemory:
    def __init__(self):
        self.short_term = []     # 最近5轮,完整保留
        self.summary = ""        # 中期摘要
        self.long_term = []      # 所有历史的向量存储
        self.embedder = SentenceTransformer("all-MiniLM-L6-v2")
    
    def _add_to_long_term(self, turn):
        text = f"用户:{turn['user']}\n助手:{turn['assistant']}"
        vec = self.embedder.encode(text)
        self.long_term.append({"text": text, "vec": vec})
    
    def _retrieve(self, query, top_k=2):
        if not self.long_term:
            return []
        q_vec = self.embedder.encode(query)
        scores = [np.dot(q_vec, item["vec"]) for item in self.long_term]
        top = sorted(range(len(scores)), key=lambda i: scores[i], reverse=True)[:top_k]
        return [self.long_term[i]["text"] for i in top]
    
    def chat(self, user_input):
        # 检索相关历史
        relevant = self._retrieve(user_input)
        
        messages = [{"role": "system", "content": "你是一个助手"}]
        if self.summary:
            messages.append({"role": "system", "content": f"[历史摘要] {self.summary}"})
        if relevant:
            messages.append({"role": "system", "content": "[相关历史]\n" + "\n---\n".join(relevant)})
        messages.extend(self.short_term)
        messages.append({"role": "user", "content": user_input})
        
        response = llm.call(messages=messages)
        
        # 更新记忆
        self.short_term.append({"role": "user", "content": user_input})
        self.short_term.append({"role": "assistant", "content": response})
        
        if len(self.short_term) > 10:
            old_user = self.short_term.pop(0)
            old_asst = self.short_term.pop(0)
            self._add_to_long_term({
                "user": old_user["content"],
                "assistant": old_asst["content"]
            })
        
        return response

优点: 信息损失最小,既保连贯性又能召回远期细节
缺点: 实现复杂,需要维护向量库,每次请求多一次检索


三种方案对比

方案实现复杂度信息损失延迟适合场景
滑动窗口极简高(早期全丢)最低简单问答、客服
摘要压缩中等中(关键信息保留)中等通用 Agent
分层记忆复杂较高长期陪伴、个人助手

实际项目里三层通常叠加用:短期用滑动窗口保连贯性,中期用摘要保关键信息,长期用向量库按需召回


面试怎么答

面试官问"你的 Agent 怎么处理长对话",不要只说"我用了分层记忆",要说清楚为什么选这个方案

  • 对话 Agent(问答场景):摘要压缩够用,分层记忆成本高但收益有限
  • 运维 Agent(每次告警是独立会话):滑动窗口就够,不需要跨会话记忆
  • 个人助手(需要记住用户偏好):分层记忆,向量库存用户画像

选型理由比实现细节更能体现思考深度。