记忆模块设计:短期记忆、长期记忆与混合架构

大家好,我是你们的老朋友。今天我们来聊聊 Agent 的记忆模块。

说实话,记忆这块儿我踩过不少坑。早期做客服机器人时,用户刚说完“我要退货”,下一句问“我的订单号是多少”,机器人就忘了前面说的啥。嗯,那种体验,用户不骂人才怪。

所以今天我把记忆模块的三种核心形态——短期记忆、长期记忆、混合架构——掰开揉碎了讲清楚。你想想看,一个没有记忆的 Agent,就像金鱼,游一圈就忘了自己从哪来。这显然不行。

短期记忆:上下文窗口

短期记忆,说白了就是当前对话的上下文。它存在内存里,用完就丢。

我个人习惯把短期记忆看作一个“滑动窗口”。窗口大小决定了 Agent 能记住多少轮对话。比如 GPT-4 的 128K token 窗口,理论上能塞下一整本书。但实际项目中,我建议别用满。

注意:窗口越大,推理成本越高,响应越慢。我曾经试过把 100 轮对话全塞进去,结果每次请求都要等 10 秒以上,用户早跑了。

短期记忆的实现其实很简单。就是一个列表,按时间顺序存消息。关键是怎么管理这个列表。

class ShortTermMemory:
    def __init__(self, max_tokens=4096):
        self.messages = []
        self.max_tokens = max_tokens
        self.current_tokens = 0
    
    def add_message(self, role, content):
        token_count = count_tokens(content)
        # 如果超了,就删最早的
        while self.current_tokens + token_count > self.max_tokens:
            removed = self.messages.pop(0)
            self.current_tokens -= count_tokens(removed['content'])
        self.messages.append({'role': role, 'content': content})
        self.current_tokens += token_count
    
    def get_context(self):
        return self.messages

这里有个细节:不要只按消息条数截断。我见过有人设 max_messages=10,结果一条消息 5000 token,另一条 10 token,窗口利用率极低。按 token 数管理才是正道。

长期记忆:向量数据库

长期记忆就复杂多了。它要存的是跨会话、跨时间的信息。比如用户的偏好、历史订单、知识库内容。

我常用的方案是向量数据库。把文本转成向量,存进去,然后通过语义相似度检索。说白了就是“模糊匹配”。

为什么不用精确匹配?因为用户不会每次都问一模一样的问题。比如昨天问“怎么退款”,今天问“钱能退回来吗”,意思一样,但字面不同。向量检索能搞定这种语义相似。

核心流程:
  1. 文本 → Embedding 模型 → 向量
  2. 向量存入向量数据库(如 Chroma、Pinecone、Milvus)
  3. 查询时,把问题也转成向量
  4. 用余弦相似度或欧氏距离找最相似的 top-k 条

这里我踩过一个坑:Embedding 模型的选择。早期我用了一个通用中文模型,结果用户问“苹果手机”,它把“苹果”和“水果”关联上了。后来换了领域微调过的模型,准确率才上来。

import chromadb
from sentence_transformers import SentenceTransformer

# 初始化
model = SentenceTransformer('all-MiniLM-L6-v2')
client = chromadb.Client()
collection = client.create_collection('user_memories')

# 写入记忆
text = "用户喜欢蓝色,偏好京东快递"
vector = model.encode(text).tolist()
collection.add(
    embeddings=[vector],
    documents=[text],
    metadatas=[{"user_id": "123", "timestamp": "2024-01-01"}],
    ids=["mem_001"]
)

# 检索
query = "用户喜欢什么颜色?"
query_vec = model.encode(query).tolist()
results = collection.query(
    query_embeddings=[query_vec],
    n_results=3
)
print(results['documents'])

混合记忆架构

短期记忆和长期记忆各有优劣。短期记忆快但容量小,长期记忆容量大但检索慢。所以实际项目中,我几乎都用混合架构。

混合架构的核心思路是:短期记忆处理当前对话,长期记忆提供背景知识。两者协同工作。

我的经验:短期记忆存最近 5-10 轮对话,长期记忆存用户画像、历史行为、知识库。每次请求时,先查短期记忆拿到上下文,再查长期记忆补充背景,最后拼在一起送给 LLM。

下面这张图是我自己项目里用的架构,你可以参考一下:

用户输入 记忆路由 短期记忆(上下文窗口) 最近 5 轮对话 长期记忆(向量数据库) 用户画像 + 历史 记忆拼接 LLM

你看,用户输入先到记忆路由。路由判断:当前对话上下文够不够?不够就去长期记忆里捞。最后把两段记忆拼在一起,送给 LLM 处理。

记忆的读写与检索策略

读写策略这块,我总结了几条原则:

  • 写优先:用户的关键信息(如偏好、决策)要立即写入长期记忆,别等。我吃过亏,用户说“我不吃辣”,结果没及时存,下一轮又推荐了麻辣火锅。
  • 读延迟:检索长期记忆时,不要每次都查。如果短期记忆已经够用,就别去查库。省时间。
  • 分级检索:先查短期记忆(毫秒级),再查长期记忆(百毫秒级)。如果短期记忆命中,直接返回。
检索策略对比:
策略 适用场景 延迟 准确率
精确匹配 固定指令、模板
向量检索 语义相似、开放问答 中高
混合检索 复杂场景、多轮对话 中高

我曾经在一个电商客服项目里,用了混合检索。先精确匹配常见问题(比如“退货流程”),没命中再向量检索。效果不错,响应时间从 2 秒降到了 0.5 秒。

避坑指南

最后分享几个我踩过的坑:

  • 别把所有记忆都存向量库。有些信息是临时的,比如“用户刚才说想喝咖啡”,存短期记忆就够了。存长期记忆反而污染数据。
  • 定期清理长期记忆。我见过一个系统跑了半年,向量库里有 500 万条记录,检索越来越慢。后来加了 TTL(生存时间)机制,超过 30 天的自动归档。
  • 注意隐私。用户明确说“别记了”,就要真的删掉。别偷偷存。我在一个金融项目里,因为没处理好用户删除请求,差点吃官司。

好了,记忆模块就讲到这里。记住一句话:短期记忆管当下,长期记忆管背景,混合架构才是王道


专注资料整理