智能体开发实战05|记忆系统实战

前四篇我们做了一件了不起的事:搭了骨架(第01篇)、装了操作系统(第02篇)、省了80% Token费(第03篇)、给它装上了双手(第04篇)。

但你的 Agent 有一个致命缺陷------

它转头就忘。

  • 上一轮对话里你告诉它的事,下一轮就清零了
  • 三天前搜索过的知识,今天又要重新来一遍
  • 用户的偏好、项目的背景------每次都得从头解释
  • 像个金鱼,7秒记忆

这不是模型的错。大模型本身是「无状态」的------每次调用都是一张白纸。记忆是 Agent 开发者自己造的。今天这篇,我们给 Agent 装上记忆。从短期到长期,从简单到架构化。


一、Agent 记忆的三个层次

人脑记忆不是一层,而是分层的。Agent 也一样。我们把 Agent 记忆分为三层:

1.1 为什么需要分层

很简单:你不能把所有记忆都塞进上下文。代价有三个------

  1. Token 成本爆炸:10万条对话 = 数百万 tokens = 每轮对话烧几十块钱
  2. 速度急剧下降:上下文越大,推理越慢,用户体验崩溃
  3. 注意力稀释:信息太多时模型反而抓不住重点(参考第03篇上下文工程)

分层记忆的核心思想:当前任务用上下文,近期对话缓存起来,长期知识存向量库里检索。


二、第一层:上下文记忆(你已会的)

这是最基本的形式。把对话历史拼进 messages 数组里送回去。

python 复制代码
# 最基本的内存形式------每次把历史全塞进去
class NaiveMemory:
    def __init__(self, max_tokens=None):
        self.messages = []
        self.max_tokens = max_tokens or 128000
    
    def add(self, role: str, content: str):
        self.messages.append({"role": role, "content": content})
    
    def get_context(self) -> list:
        # 简单截断,不超窗口
        total = 0
        result = []
        for msg in reversed(self.messages):
            cost = len(msg["content"]) // 3  # 粗略估算 token
            if total + cost > self.max_tokens * 0.8:
                break
            result.insert(0, msg)
            total += cost
        return result
    
    def clear(self):
        self.messages = []

这能跑,但太粗糙了------没有压缩、没有分层、没有持久化。我们的目标是:超出上下文的记忆也能被 Agent 用到。


三、第二层:短期记忆------带摘要的对话历史

3.1 设计思路

短期记忆要解决的是「最近 N 轮对话里发生了什么」。核心原则:

  • 最近 5-10 轮保留完整原文
  • 更早的对话压缩为结构化摘要(谁说了什么、做了什么决定、改了什么文件)
  • 摘要定期更新------不是一次性压缩就不管了

3.2 实现

python 复制代码
import json

class ShortTermMemory:
    def __init__(self, max_recent=8, max_summary_tokens=3000):
        self.recent = []        # 最近N轮完整对话
        self.summary = ""        # 更早对话的摘要
        self.max_recent = max_recent
        self.max_summary = max_summary_tokens
        self.decisions = []      # 记录关键决策
    
    def add_turn(self, user_msg: str, assistant_msg: str):
        self.recent.append({
            "user": user_msg,
            "assistant": assistant_msg
        })
        
        # 超过阈值时,把最老的一轮并入摘要
        if len(self.recent) > self.max_recent:
            oldest = self.recent.pop(0)
            self._merge_to_summary(oldest)
    
    def record_decision(self, decision: str):
        self.decisions.append(decision)
    
    def _merge_to_summary(self, turn: dict):
        # 累积式摘要:追加简要信息
        snippet = f"[用户问: {turn['user'][:80]}... | Agent: {turn['assistant'][:80]}...]"
        self.summary = (self.summary + " " + snippet)[-self.max_summary:]
    
    def get_memory_context(self) -> str:
        parts = []
        if self.decisions:
            parts.append(f"## 关键决策记录\n" + "\n".join(
                f"- {d}" for d in self.decisions[-10:]
            ))
        if self.summary:
            parts.append(f"## 对话历史摘要\n{self.summary}")
        if self.recent:
            parts.append(f"## 最近 {len(self.recent)} 轮对话\n" + "\n".join(
                f"Q: {t['user'][:120]}\nA: {t['assistant'][:120]}"
                for t in self.recent
            ))
        return "\n\n".join(parts)

3.3 关键设计点

  • 决策记录:有些信息比普通对话重要得多------「我们决定用 Redis 而不是 MySQL」这种需要单独存档
  • 累积式摘要:不是每次重新调用 LLM 做总结(那本身就要烧 Token),而是在消息被逐出时增量追加一句
  • 截断保护:摘要不能无限膨胀,用滑动窗口限制最大长度

短期记忆已经可以让 Agent 记住「半小时前我们聊了什么」。但三天前的对话呢?用户偏好去哪了?这就是长期记忆要解决的问题。


四、第三层:长期记忆------向量检索

4.1 什么该进长期记忆

不是所有对话都值得永久记住。筛选标准:

4.2 架构:Embedding + 向量检索

经典方案:把记忆文本通过 Embedding 模型转为向量,存到向量数据库,检索时用语义相似度匹配。

python 复制代码
import numpy as np
from openai import OpenAI

client = OpenAI(api_key="your_key")

class LongTermMemory:
    def __init__(self, vector_db=None):
        self.memories = []  # 简化版:用列表代替向量数据库
        self.embed_model = "text-embedding-3-small"
    
    def embed(self, text: str) -> list:
        resp = client.embeddings.create(
            model=self.embed_model,
            input=text
        )
        return resp.data[0].embedding
    
    def cosine_sim(self, a: list, b: list) -> float:
        a = np.array(a)
        b = np.array(b)
        return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))
    
    def store(self, content: str, metadata: dict = None):
        vec = self.embed(content)
        self.memories.append({
            "content": content,
            "vector": vec,
            "metadata": metadata or {},
            "timestamp": __import__("time").time()
        })
    
    def search(self, query: str, top_k: int = 5) -> list:
        q_vec = self.embed(query)
        scored = [(self.cosine_sim(q_vec, m["vector"]), m)
                  for m in self.memories]
        scored.sort(key=lambda x: x[0], reverse=True)
        return [m for _, m in scored[:top_k]]
    
    def store_if_important(self, content: str):
        # 只有重要信息才存长期记忆
        keywords = ["偏好", "决定", "配置", "项目", "架构",
                    "偏好", "技术栈", "API"]
        if any(kw in content for kw in keywords):
            self.store(content)

4.3 典型检索场景

场景:用户说「帮我优化一下上次那个搜索接口的性能」

ini 复制代码
# 1. 从长期记忆中检索"搜索接口"相关记忆
results = lt_memory.search("搜索接口 性能优化")

# 2. 把检索结果作为上下文注入
context = "\n".join([
    f"[相关记忆 {i+1}] {m['content']}"
    for i, m in enumerate(results)
])

# 3. 拼入 System Prompt
task_msg = f"""## 长期记忆
{context}

## 当前任务
用户要求优化搜索接口性能,请根据以上记忆中的技术栈和之前的优化记录,给出建议。
"""

五、混合记忆架构:AgentMemory

把三层记忆整合成一个统一的 AgentMemory 类:

python 复制代码
class AgentMemory:
    def __init__(self):
        self.short_term = ShortTermMemory()
        self.long_term = LongTermMemory()
        self.user_profile = {}  # 用户画像
    
    def on_user_message(self, msg: str):
        # 自动提取并记忆关键信息
        self._extract_and_store(msg)
    
    def on_agent_reply(self, reply: str):
        # 记录决策和结论
        self._detect_decisions(reply)
    
    def build_context(self) -> str:
        parts = []
        
        # 1. 用户画像(压缩版)
        if self.user_profile:
            profile_lines = [f"- {k}: {v}"
                           for k, v in self.user_profile.items()]
            parts.append("## 用户画像\n" + "\n".join(profile_lines))
        
        # 2. 短期记忆(最近对话 + 摘要)
        stm = self.short_term.get_memory_context()
        if stm:
            parts.append(stm)
        
        # 3. 长期记忆(按当前问题检索)
        # 由调用方传入检索结果
        
        return "\n\n".join(parts)
    
    def recall_for(self, query: str, top_k: int = 5) -> list:
        return self.long_term.search(query, top_k)
    
    # --- 记忆提取 ---
    def _extract_and_store(self, msg: str):
        # 简化版规则提取
        import re
        patterns = {
            "name": r"我是(.*?)[,,。\s]",
            "language": r"用(Python|Go|Rust|TypeScript|Java)",
            "framework": r"(React|Vue|Django|FastAPI|Flask|Gin)",
        }
        for field, pattern in patterns.items():
            match = re.search(pattern, msg)
            if match:
                self.user_profile[field] = match.group(1)
                self.long_term.store(
                    f"用户偏好 {field}: {match.group(1)}",
                    {"type": "user_preference", "field": field}
                )
    
    def _detect_decisions(self, reply: str):
        if "决定" in reply or "方案" in reply:
            self.short_term.record_decision(
                reply.split("。")[0][:200]
            )


# --- 集成到 Agent 中 ---
class Agent:
    def __init__(self):
        self.memory = AgentMemory()
        self.context_manager = ContextManager()  # 第03篇的上下文管理
    
    async def run(self, user_input: str):
        # 1. 存入短期记忆
        self.memory.on_user_message(user_input)
        
        # 2. 检索长期记忆
        long_memories = self.memory.recall_for(user_input, top_k=5)
        
        # 3. 组装上下文
        memory_ctx = self.memory.build_context()
        if long_memories:
            long_ctx = "\n".join([
                f"- {m['content']}" for m in long_memories
            ])
            memory_ctx += f"\n\n## 相关长期记忆\n{long_ctx}"
        
        # 4. 调用 LLM
        messages = [
            {"role": "system", "content": f"你是 AI Agent。\n{memory_ctx}"},
            {"role": "user", "content": user_input}
        ]
        response = await self._call_llm(messages)
        
        # 5. 记录回复
        self.memory.on_agent_reply(response)
        self.memory.short_term.add_turn(user_input, response)
        
        return response

六、进阶话题

6.1 记忆的遗忘与更新

记忆不是「只增不减」。信息会过时、会矛盾。好的记忆系统需要------

  • TTL 过期:旧记忆的权重随时间衰减(指数衰减或线性衰减)
  • 冲突检测:新旧信息矛盾时,用更新的覆盖旧的,并标记冲突
  • 主动遗忘:用户说「不用那个了」,Agent 要能从长期记忆中删掉

6.2 生产环境的向量数据库选择

6.3 CACHE:上下文记忆的加速器

回到第03篇讲的 Prompt Caching。如果你的系统指令包含用户画像和长期记忆,用缓存可以省掉重复传输成本。DeepSeek 和 Anthropic 都支持自动 Prompt Caching------只要系统指令不变,Token 成本大降。


七、最佳实践总结

  1. 分层设计:短期(最近N轮) / 长期(向量检索) / 用户画像 三层各司其职
  2. 不是所有话都记:用规则或小模型判断是否值得存入长期记忆
  3. 检索优于全传:永远不要把所有记忆塞进上下文,用语义检索只取最相关的 top_k 条
  4. 摘要优于原文:超过 10 轮对话后,自动压缩为摘要
  5. 决策独立存档:关键决策从对话流中提取出来单独标记
  6. 遗忘是功能不是 bug:设置 TTL、冲突检测、主动删除
  7. 用户画像独立维护:姓名、偏好、技术栈等结构化存储,不混入对话流
  8. 与上下文工程配合:记忆检索结果注入上下文时遵守预算分配(参考第03篇)

八、下期预告

  • 第06篇: Multi-Agent 模式------一个 Agent 不够?那就来一队
  • 第07篇: Skills 工程------像安装 App 一样给 Agent 装技能
  • 第08篇: 生产化部署------Agent 从 demo 到上线的全部坑

提示:记忆系统是 Agent 从「工具」变成「伙伴」的分水岭。

没有记忆的 Agent,每次对话都像第一次见面------你烦,它也烦。有了分层记忆,Agent 能记住你的偏好、你的项目、你做过的决定------这才叫「你的」Agent。

实现上,先上短期记忆(几乎零成本),再根据需要加向量数据库做长期记忆。不要一步到位上全套------先用最简单的实现,跑通了再迭代。

下一篇,我们把多个 Agent 组队------Multi-Agent 模式。一个 Agent 干活不够?那就请一个团队。


提示 :本文由 码农大坚果 出品,欢迎转发分享,转载请注明出处。

参考: OpenAI Embeddings 文档、LangChain Memory 模块、Qdrant 文档、Weaviate 向量数据库实践 | 整理 by 码农大坚果

相关推荐
guyoung2 小时前
BoxAgnts 运行时(4)——要能力安全,不要 Root 权限
agent·ai编程
DylanlZhao2 小时前
Superpowers 原理探析
agent·ai编程·claude
YDS8292 小时前
DeepSeek RAG&MCP + Agent智能体项目 —— 动态决策策略的接口对接
java·spring boot·ai·agent·spring ai·deepseek
yichudu3 小时前
autoResearch 官方项目复现笔记
agent
花椒技术3 小时前
AI 代码评审落地实践:GitLab 接入、项目规则与反馈闭环
后端·github·agent
阿里云云原生4 小时前
AI Agent 进入生产深水区:如何破解 Token 成本黑洞与排障难题?
人工智能·阿里云·agent·云监控
vivo互联网技术5 小时前
把输入框变成 AI 的“超级入口”(ProseMirror 全流程实战)
前端·agent
码农大坚果5 小时前
智能体开发实战02|Harness工程入门
人工智能·agent