AI Agent多轮记忆实战:构建长期记忆与上下文压缩系统

AI与大数据

摘要

  • AI Agent的记忆系统分为3层:工作记忆(上下文窗口)、短期记忆(会话级)、长期记忆(跨会话持久化)
  • 对话上下文压缩是长对话场景的刚需,LLM摘要压缩可将100轮对话压缩到2K tokens内
  • 长期记忆的3种存储模式:向量检索记忆、知识图谱记忆、结构化键值记忆
  • 记忆检索的RAG方案:将历史对话嵌入向量数据库,按相关性召回关键片段
  • 本文提供从记忆架构设计到生产部署的完整方案,含Redis+Milvus持久化实现

目录


AI Agent记忆系统的3层架构

┌──────────────────────────────────────────────────────────────┐
│              AI Agent 3层记忆架构                               │
│                                                                │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  Layer 1: 工作记忆 (Working Memory)                   │    │
│  │  上下文窗口内的对话历史                                │    │
│  │  容量: 4K-128K tokens                                │    │
│  │  延迟: 0ms (已在内存)                                │    │
│  │  生命周期: 单次请求                                   │    │
│  └──────────────────────────────────────────────────────┘    │
│                         ↓ 溢出压缩                            │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  Layer 2: 短期记忆 (Short-Term Memory)                │    │
│  │  当前会话的完整对话状态                                │    │
│  │  存储: Redis / 内存缓存                               │    │
│  │  容量: 无限制                                         │    │
│  │  生命周期: 单次会话 (关闭浏览器后过期)                 │    │
│  └──────────────────────────────────────────────────────┘    │
│                         ↓ 关键信息提取                        │
│  ┌──────────────────────────────────────────────────────┐    │
│  │  Layer 3: 长期记忆 (Long-Term Memory)                 │    │
│  │  跨会话的用户偏好、知识、历史                          │    │
│  │  存储: Milvus向量库 + PostgreSQL结构化                 │    │
│  │  容量: 无限制                                         │    │
│  │  生命周期: 永久                                       │    │
│  └──────────────────────────────────────────────────────┘    │
└──────────────────────────────────────────────────────────────┘

3层记忆对比

维度 工作记忆 短期记忆 长期记忆
存储 LLM上下文窗口 Redis/Memory Milvus+PostgreSQL
容量 4K-128K tokens 无限制 无限制
延迟 0ms 1-5ms 10-30ms
生命周期 单次请求 单次会话 永久
检索方式 直接访问 Key-Value 向量+结构化
典型内容 当前对话 会话历史 用户偏好/知识

工作记忆:上下文窗口管理

Token预算分配

┌──────────────────────────────────────────────────────────┐
│              8K上下文窗口的Token预算分配                    │
│                                                            │
│  System Prompt:     500 tokens (6%)                       │
│  长期记忆召回:      1000 tokens (12%)                     │
│  对话历史:          5000 tokens (62%)                     │
│  工具调用结果:      1000 tokens (12%)                     │
│  预留输出空间:      500 tokens (6%)                       │
│  ─────────────────────────────────                        │
│  合计:              8000 tokens                            │
└──────────────────────────────────────────────────────────┘

上下文窗口管理器

from dataclasses import dataclass, field
from typing import Optional

@dataclass
class Message:
    role: str
    content: str
    token_count: int = 0
    metadata: dict = field(default_factory=dict)

class ContextWindowManager:
    def __init__(
        self,
        max_tokens: int = 8192,
        system_prompt_tokens: int = 500,
        reserved_output_tokens: int = 500,
        memory_recall_tokens: int = 1000,
    ):
        self.max_tokens = max_tokens
        self.system_prompt_tokens = system_prompt_tokens
        self.reserved_output_tokens = reserved_output_tokens
        self.memory_recall_tokens = memory_recall_tokens
        self.available_for_history = (
            max_tokens - system_prompt_tokens - reserved_output_tokens - memory_recall_tokens
        )

    def build_context(
        self,
        system_prompt: str,
        memory_facts: list[str],
        conversation_history: list[Message],
        tool_results: list[str] = None,
    ) -> list[dict]:
        messages = [{"role": "system", "content": system_prompt}]

        if memory_facts:
            memory_text = "用户相关记忆:\n" + "\n".join(f"- {f}" for f in memory_facts)
            messages.append({"role": "system", "content": memory_text})

        budget = self.available_for_history
        selected_history = []
        for msg in reversed(conversation_history):
            if budget <= 0:
                break
            if msg.token_count <= budget:
                selected_history.insert(0, msg)
                budget -= msg.token_count
            else:
                break

        for msg in selected_history:
            messages.append({"role": msg.role, "content": msg.content})

        if tool_results:
            for result in tool_results:
                messages.append({"role": "tool", "content": result})

        return messages

短期记忆:会话级状态持久化

Redis会话存储

import json
import redis
from datetime import timedelta

class SessionMemory:
    def __init__(self, redis_url: str = "redis://localhost:6379"):
        self.redis = redis.from_url(redis_url)
        self.session_ttl = timedelta(hours=24)

    def _key(self, session_id: str) -> str:
        return f"session:{session_id}"

    async def save_message(self, session_id: str, role: str, content: str):
        key = self._key(session_id)
        message = {"role": role, "content": content, "timestamp": int(time.time())}
        await self.redis.rpush(key, json.dumps(message, ensure_ascii=False))
        await self.redis.expire(key, self.session_ttl)

    async def get_history(self, session_id: str, limit: int = 50) -> list[dict]:
        key = self._key(session_id)
        raw_messages = await self.redis.lrange(key, -limit, -1)
        return [json.loads(m) for m in raw_messages]

    async def get_session_summary(self, session_id: str) -> Optional[str]:
        key = f"session_summary:{session_id}"
        summary = await self.redis.get(key)
        return summary.decode() if summary else None

    async def save_session_summary(self, session_id: str, summary: str):
        key = f"session_summary:{session_id}"
        await self.redis.set(key, summary, ex=self.session_ttl)

    async def clear_session(self, session_id: str):
        await self.redis.delete(self._key(session_id))
        await self.redis.delete(f"session_summary:{session_id}")

长期记忆:跨会话知识积累

3种长期记忆模式

模式 存储 检索方式 适用场景
向量检索记忆 Milvus 语义相似度 开放式知识回忆
知识图谱记忆 Neo4j 关系遍历 结构化关系推理
结构化键值记忆 PostgreSQL 精确匹配 用户偏好/配置

向量检索记忆实现

from pymilvus import MilvusClient, DataType
from openai import OpenAI
import hashlib

class VectorLongTermMemory:
    def __init__(self, milvus_uri: str, embedding_model: str = "text-embedding-3-small"):
        self.client = MilvusClient(uri=milvus_uri)
        self.embedding_client = OpenAI()
        self.embedding_model = embedding_model
        self._ensure_collection()

    def _ensure_collection(self):
        if self.client.has_collection("agent_memory"):
            return
        schema = self.client.create_schema(auto_id=True, enable_dynamic_field=True)
        schema.add_field(field_name="id", datatype=DataType.INT64, is_primary=True)
        schema.add_field(field_name="user_id", datatype=DataType.VARCHAR, max_length=128)
        schema.add_field(field_name="content", datatype=DataType.VARCHAR, max_length=65535)
        schema.add_field(field_name="embedding", datatype=DataType.FLOAT_VECTOR, dim=1536)
        schema.add_field(field_name="category", datatype=DataType.VARCHAR, max_length=64)
        schema.add_field(field_name="timestamp", datatype=DataType.INT64)

        index_params = self.client.prepare_index_params()
        index_params.add_index(field_name="embedding", index_type="HNSW", metric_type="COSINE", params={"M": 16, "efConstruction": 200})

        self.client.create_collection("agent_memory", schema=schema, index_params=index_params)

    def _embed(self, text: str) -> list[float]:
        response = self.embedding_client.embeddings.create(input=text, model=self.embedding_model)
        return response.data[0].embedding

    async def store(self, user_id: str, content: str, category: str = "general"):
        embedding = self._embed(content)
        self.client.insert("agent_memory", [{
            "user_id": user_id,
            "content": content,
            "embedding": embedding,
            "category": category,
            "timestamp": int(time.time()),
        }])

    async def recall(self, user_id: str, query: str, top_k: int = 5) -> list[str]:
        query_embedding = self._embed(query)
        results = self.client.search(
            "agent_memory",
            data=[query_embedding],
            filter=f'user_id == "{user_id}"',
            limit=top_k,
            output_fields=["content", "category"],
            search_params={"metric_type": "COSINE", "params": {"ef": 64}},
        )
        return [r["entity"]["content"] for r in results[0]]

结构化键值记忆实现

import asyncpg

class StructuredLongTermMemory:
    def __init__(self, db_url: str):
        self.db_url = db_url
        self.pool = None

    async def init(self):
        self.pool = await asyncpg.create_pool(self.db_url, min_size=2, max_size=10)
        async with self.pool.acquire() as conn:
            await conn.execute("""
                CREATE TABLE IF NOT EXISTS user_memory (
                    id SERIAL PRIMARY KEY,
                    user_id VARCHAR(128) NOT NULL,
                    memory_key VARCHAR(256) NOT NULL,
                    memory_value TEXT NOT NULL,
                    category VARCHAR(64) DEFAULT 'general',
                    updated_at TIMESTAMP DEFAULT NOW(),
                    UNIQUE(user_id, memory_key)
                )
            """)

    async def set_memory(self, user_id: str, key: str, value: str, category: str = "general"):
        async with self.pool.acquire() as conn:
            await conn.execute("""
                INSERT INTO user_memory (user_id, memory_key, memory_value, category)
                VALUES ($1, $2, $3, $4)
                ON CONFLICT (user_id, memory_key)
                DO UPDATE SET memory_value = $3, category = $4, updated_at = NOW()
            """, user_id, key, value, category)

    async def get_memory(self, user_id: str, key: str) -> Optional[str]:
        async with self.pool.acquire() as conn:
            row = await conn.fetchrow(
                "SELECT memory_value FROM user_memory WHERE user_id = $1 AND memory_key = $2",
                user_id, key
            )
            return row["memory_value"] if row else None

    async def get_all_memories(self, user_id: str, category: str = None) -> list[dict]:
        async with self.pool.acquire() as conn:
            if category:
                rows = await conn.fetch(
                    "SELECT memory_key, memory_value, category FROM user_memory WHERE user_id = $1 AND category = $2",
                    user_id, category
                )
            else:
                rows = await conn.fetch(
                    "SELECT memory_key, memory_value, category FROM user_memory WHERE user_id = $1",
                    user_id
                )
            return [{"key": r["memory_key"], "value": r["memory_value"], "category": r["category"]} for r in rows]

对话压缩:长对话场景的刚需

3种压缩策略

策略 压缩比 信息损失 延迟 适用场景
滑动窗口截断 0ms 简单对话
LLM摘要压缩 200-500ms 长对话
结构化提取 极小 100-300ms 任务型对话

LLM摘要压缩

class ConversationCompressor:
    def __init__(self, llm_client, max_summary_tokens: int = 512):
        self.llm = llm_client
        self.max_summary_tokens = max_summary_tokens

    async def compress(self, messages: list[dict], keep_recent: int = 4) -> list[dict]:
        if len(messages) <= keep_recent + 2:
            return messages

        to_compress = messages[:-keep_recent]
        recent = messages[-keep_recent:]

        conversation_text = "\n".join(
            f"{m['role']}: {m['content'][:200]}" for m in to_compress
        )

        response = self.llm.chat.completions.create(
            model="Qwen/Qwen2.5-7B-Instruct",
            messages=[{
                "role": "system",
                "content": "将以下对话历史压缩为简洁摘要,保留关键事实、决策和用户偏好。只输出摘要内容,不要解释。"
            }, {
                "role": "user",
                "content": conversation_text
            }],
            max_tokens=self.max_summary_tokens,
            temperature=0.0,
        )

        summary = response.choices[0].message.content
        return [
            {"role": "system", "content": f"之前的对话摘要:{summary}"},
            *recent,
        ]

结构化信息提取

class StructuredMemoryExtractor:
    def __init__(self, llm_client):
        self.llm = llm_client

    async def extract(self, user_message: str, assistant_message: str) -> list[dict]:
        response = self.llm.chat.completions.create(
            model="Qwen/Qwen2.5-7B-Instruct",
            messages=[{
                "role": "system",
                "content": """从对话中提取需要长期记住的结构化信息。
输出JSON数组,每个元素包含:key(记忆键名)、value(记忆值)、category(分类:preference/fact/decision/task)

示例输出:
[{"key":"preferred_language","value":"Python","category":"preference"}]

如果没有需要记住的信息,输出空数组 []"""
            }, {
                "role": "user",
                "content": f"用户: {user_message}\n助手: {assistant_message}"
            }],
            max_tokens=256,
            temperature=0.0,
            response_format={"type": "json_object"},
        )

        try:
            data = json.loads(response.choices[0].message.content)
            return data.get("memories", data.get("items", []))
        except json.JSONDecodeError:
            return []

生产部署:Redis+Milvus记忆服务

记忆服务统一接口

class AgentMemoryService:
    def __init__(self, redis_url: str, milvus_uri: str, pg_url: str, llm_client):
        self.session_memory = SessionMemory(redis_url)
        self.vector_memory = VectorLongTermMemory(milvus_uri)
        self.structured_memory = StructuredLongTermMemory(pg_url)
        self.compressor = ConversationCompressor(llm_client)
        self.extractor = StructuredMemoryExtractor(llm_client)

    async def on_user_message(self, session_id: str, user_id: str, message: str) -> list[str]:
        vector_facts = await self.vector_memory.recall(user_id, message, top_k=3)
        structured = await self.structured_memory.get_all_memories(user_id)
        preference_facts = [f"{m['key']}: {m['value']}" for m in structured if m["category"] == "preference"]
        return vector_facts + preference_facts

    async def on_assistant_message(self, session_id: str, user_id: str, user_msg: str, assistant_msg: str):
        await self.session_memory.save_message(session_id, "user", user_msg)
        await self.session_memory.save_message(session_id, "assistant", assistant_msg)

        memories = await self.extractor.extract(user_msg, assistant_msg)
        for mem in memories:
            await self.structured_memory.set_memory(user_id, mem["key"], mem["value"], mem["category"])

        await self.vector_memory.store(user_id, f"用户说: {user_msg}", category="conversation")

    async def build_prompt(self, session_id: str, user_id: str, current_message: str) -> list[dict]:
        memory_facts = await self.on_user_message(session_id, user_id, current_message)
        history = await self.session_memory.get_history(session_id, limit=50)
        messages = self.session_memory.build_context(
            system_prompt="你是一个有记忆的AI助手,能记住用户的偏好和历史对话。",
            memory_facts=memory_facts,
            conversation_history=history,
        )
        messages.append({"role": "user", "content": current_message})
        return messages

K8s部署

apiVersion: apps/v1
kind: Deployment
metadata:
  name: agent-memory-service
  namespace: ai-agent
spec:
  replicas: 2
  selector:
    matchLabels:
      app: agent-memory-service
  template:
    spec:
      containers:
        - name: memory-service
          image: myregistry/agent-memory-service:v1.0
          ports:
            - containerPort: 8080
          resources:
            requests:
              cpu: "1"
              memory: 512Mi
            limits:
              cpu: "2"
              memory: 1Gi
          env:
            - name: REDIS_URL
              value: "redis://redis:6379"
            - name: MILVUS_URI
              value: "http://milvus-svc:19530"
            - name: PG_URL
              value: "postgresql://postgres:password@postgres:5432/agent_memory"

记忆系统性能基准

操作 延迟(P50) 延迟(P99) QPS
保存消息(Redis) 1.2ms 3ms 8000
获取历史(Redis) 2ms 5ms 6000
向量存储(Milvus) 15ms 30ms 500
向量召回(Milvus) 8ms 15ms 1000
结构化存储(PG) 3ms 8ms 3000
结构化查询(PG) 2ms 5ms 5000
对话压缩(LLM) 350ms 800ms 50

总结与引流

AI Agent记忆系统是Agent智能的核心基础设施。3层架构(工作记忆→短期记忆→长期记忆)解决了上下文窗口有限、会话状态丢失、跨会话知识遗忘三大问题。对话压缩和结构化提取是长对话场景的关键技术。

开发要点回顾

  1. 3层记忆架构:工作记忆(上下文窗口)→短期记忆(Redis)→长期记忆(Milvus+PG)
  2. Token预算分配:System 6% + 记忆召回 12% + 对话历史 62% + 工具 12% + 输出 6%
  3. 对话压缩优先用LLM摘要,任务型对话用结构化提取
  4. 长期记忆3种模式:向量检索(开放式)、知识图谱(关系型)、结构化键值(精确匹配)
  5. 生产部署Redis+Milvus+PostgreSQL三件套

相关阅读

权威参考

本站提供浏览器本地工具,免注册即可试用 →

#AI Agent记忆管理#多轮对话记忆#长期记忆架构#Agent状态持久化#对话上下文压缩#2026