Agent记忆系统:记住一切是个坏主意

Agent记忆系统:记住一切是个坏主意

上周调一个客服Agent的对话记忆,用户聊了200轮之后,Agent开始答非所问。翻了半天日志,发现上下文已经塞了4万多token,模型直接开始"幻觉"——把用户三小时前说的地址,当成了当前订单的收货地址。

问题出在哪?不是模型不够聪明,是我太贪心——把所有对话记录一股脑塞进context,以为"记得越多越好"。

事实恰恰相反。Agent的记忆系统,核心不是"记住",而是"该忘什么"。


问题拆解:三种记忆,三种处理方式

人脑不会把所有经历都存成高清视频。短期记忆(刚说了啥)、长期记忆(重要事实)、工作记忆(当前任务需要的信息),各管各的。

Agent也一样。我把记忆拆成三层:

from dataclasses import dataclass, field
from datetime import datetime, timedelta
from typing import Optional
import hashlib
import json

@dataclass
class MemoryItem:
    content: str
    created_at: datetime
    importance: float  # 0.0 ~ 1.0
    access_count: int = 0
    last_accessed: Optional[datetime] = None
    memory_type: str = "working"  # working / short_term / long_term

    @property
    def age_hours(self) -> float:
        return (datetime.now() - self.created_at).total_seconds() / 3600

    @property
    def decay_score(self) -> float:
        """衰减分数:越老、越不重要的记忆,分数越低"""
        age_factor = max(0, 1.0 - self.age_hours / 24)  # 24小时线性衰减
        access_bonus = min(0.3, self.access_count * 0.05)  # 被访问越多,衰减越慢
        return self.importance * age_factor + access_bonus

decay_score是关键——它决定了这条记忆是保留还是被清理。一条"用户叫张三"的记忆,importance设0.9,48小时后decay_score还能到0.5以上;一条"用户问了天气"的记忆,importance只有0.2,6小时后基本就该清掉了。


短期记忆:用滑动窗口,别用全量记录

最简单的记忆管理,就是限制窗口大小。但"限制token数"太粗暴了——你需要的是按语义切分,不是按字符数截断。

class ShortTermMemory:
    def __init__(self, max_turns: int = 10):
        self.items: list[MemoryItem] = []
        self.max_turns = max_turns

    def add(self, content: str, importance: float = 0.5):
        item = MemoryItem(
            content=content,
            created_at=datetime.now(),
            importance=importance,
            memory_type="short_term"
        )
        self.items.append(item)
        self._evict()

    def _evict(self):
        """超过窗口的旧记忆,按decay_score排序淘汰"""
        if len(self.items) <= self.max_turns:
            return
        # 保留最重要的N条
        self.items.sort(key=lambda x: x.decay_score, reverse=True)
        evicted = self.items[self.max_turns:]
        self.items = self.items[:self.max_turns]
        # 被淘汰的记忆不是直接丢弃,而是评估是否要提升到长期记忆
        for item in evicted:
            if item.importance >= 0.7:
                self._promote_to_long_term(item)

    def _promote_to_long_term(self, item: MemoryItem):
        """重要记忆提升到长期存储"""
        item.memory_type = "long_term"
        # 这里对接长期记忆的存储(向量数据库、文件等)
        print(f"[Memory] 提升到长期记忆: {item.content[:50]}...")

实测效果:把max_turns从50调到10之后,客服Agent的回复准确率从73%提升到89%。不是模型变好了,是噪声少了。


长期记忆:向量化存储 + 语义检索

短期记忆是FIFO,长期记忆得靠检索。我把重要记忆向量化之后存到ChromaDB,查询时按语义相似度召回。

import chromadb
from sentence_transformers import SentenceTransformer

class LongTermMemory:
    def __init__(self, collection_name: str = "agent_memory"):
        self.client = chromadb.Client()
        self.collection = self.client.get_or_create_collection(
            name=collection_name,
            metadata={"hnsw:space": "cosine"}
        )
        self.encoder = SentenceTransformer("paraphrase-multilingual-MiniLM-L12-v2")

    def store(self, content: str, metadata: dict = None):
        embedding = self.encoder.encode(content).tolist()
        doc_id = hashlib.md5(content.encode()).hexdigest()
        self.collection.add(
            ids=[doc_id],
            embeddings=[embedding],
            documents=[content],
            metadatas=[metadata or {}]
        )

    def recall(self, query: str, top_k: int = 5, threshold: float = 0.6) -> list[str]:
        """语义检索,只召回相关度高于阈值的记忆"""
        query_embedding = self.encoder.encode(query).tolist()
        results = self.collection.query(
            query_embeddings=[query_embedding],
            n_results=top_k
        )
        # 过滤掉相似度不够的
        memories = []
        for doc, distance in zip(results["documents"][0], results["distances"][0]):
            similarity = 1 - distance  # cosine距离转相似度
            if similarity >= threshold:
                memories.append(doc)
        return memories

这里有个坑:paraphrase-multilingual-MiniLM-L12-v2对中文的语义匹配效果还行,但对专业术语(比如"OOM"、"熔断")经常匹配不上。后来我加了个同义词词典做预处理,召回率从61%提到82%。


遗忘机制:这才是核心

记忆系统最难的不是"怎么存",而是"怎么删"。删多了Agent变傻,删少了Agent变晕。

我的遗忘策略用了三个维度打分:

class ForgetPolicy:
    """遗忘策略:决定哪些记忆该被清除"""

    def __init__(self, max_memory_size: int = 1000):
        self.max_memory_size = max_memory_size

    def score_memory(self, item: MemoryItem) -> float:
        """综合评分:决定这条记忆的存活权重"""
        # 维度1:重要性(人工标注)
        importance = item.importance

        # 维度2:新鲜度(时间衰减)
        freshness = max(0, 1.0 - item.age_hours / 72)  # 72小时衰减到0

        # 维度3:被引用频率
        access_weight = min(1.0, item.access_count * 0.1)

        # 综合得分
        return importance * 0.4 + freshness * 0.3 + access_weight * 0.3

    def forget(self, memories: list[MemoryItem]) -> list[MemoryItem]:
        """执行遗忘:返回要保留的记忆"""
        if len(memories) <= self.max_memory_size:
            return memories

        scored = [(self.score_memory(m), m) for m in memories]
        scored.sort(key=lambda x: x[0], reverse=True)

        keep = [m for _, m in scored[:self.max_memory_size]]
        discard = [m for _, m in scored[self.max_memory_size:]]

        # 记录被遗忘的内容(调试用)
        for m in discard:
            print(f"[Forget] 清除: {m.content[:40]}... (score={self.score_memory(m):.2f})")

        return keep

跑了一个月的数据,发现一个规律:被遗忘的记忆里,87%确实不重要,但有13%是"暂时不重要但后来变重要"的——比如用户上周提过一次地址,这周又问配送进度。

解决方案:不清除,而是归档。被遗忘的记忆压缩后存到冷存储,Agent可以主动检索但不会自动加载。


冷热分离:最终架构

把三层串起来,完整的记忆系统长这样:

class AgentMemorySystem:
    def __init__(self):
        self.short_term = ShortTermMemory(max_turns=10)
        self.long_term = LongTermMemory()
        self.forget_policy = ForgetPolicy(max_memory_size=500)

    def on_conversation_turn(self, role: str, content: str):
        """每轮对话的处理"""
        # 1. 评估重要性
        importance = self._assess_importance(role, content)

        # 2. 存入短期记忆
        self.short_term.add(content, importance)

        # 3. 重要的自动存入长期记忆
        if importance >= 0.7:
            self.long_term.store(content, {"role": role, "turn": "auto"})

    def build_context(self, current_query: str) -> str:
        """构建送给LLM的上下文"""
        parts = []

        # 短期记忆(最近对话)
        recent = [m.content for m in self.short_term.items[-6:]]
        parts.append("最近对话:\n" + "\n".join(recent))

        # 长期记忆(语义相关)
        relevant = self.long_term.recall(current_query, top_k=3)
        if relevant:
            parts.append("相关记忆:\n" + "\n".join(relevant))

        return "\n\n".join(parts)

    def _assess_importance(self, role: str, content: str) -> float:
        """简单的重要性评估(实际项目用LLM打分更好)"""
        high_importance_keywords = ["地址", "电话", "订单", "退款", "投诉", "密码"]
        for kw in high_importance_keywords:
            if kw in content:
                return 0.9
        if role == "user" and len(content) > 100:
            return 0.6  # 用户长消息通常比较重要
        return 0.3

踩过的三个坑

坑1:importance评分用规则引擎太死板。 上线后发现"我要退款"和"我想退款"得分不一样,因为规则写的"我要退款"。后来换成用LLM打分,准确率上去了,但每条消息多一次API调用。折中方案:规则引擎兜底,命中关键词的直接给分,没命中的才调LLM。

坑2:向量数据库的distance阈值需要动态调整。 不同业务场景的语义密度不一样,0.6这个阈值在客服场景刚好,在技术问答场景就太松了——召回一堆不相关的记忆。解决方案:按场景配置不同的阈值,上线前跑测试集标定。

坑3:遗忘时机不对。 一开始设的是每100轮对话触发一次遗忘清理。结果在高峰时段,清理和新消息涌入同时发生,Agent响应延迟从200ms飙到2秒。改成异步清理+写时复制,延迟问题解决。


一句话

Agent的记忆不是数据库,是决策系统。存什么、忘什么、什么时候想起来——这三个问题的答案,决定了Agent是"智能助手"还是"话痨复读机"。

完整的代码在GitHub上,地址放评论区。有问题直接评论区聊。



关注「安全值班室」公众号

每天一篇AI安全早报 + 实战攻防案例

关注安全值班室

posted on 2026-05-25 09:01  明.Sir  阅读(11)  评论(0)    收藏  举报

导航