Память AI-агентов: архитектура

Разбор трёх типов памяти: Working (контекстное окно LLM), Short-term (сессия агента), Long-term (векторная БД с ChromaDB). Практическая архитектура.

📊 Средний⏱ 12 мин

# 1. ТРИ ТИПА ПАМЯТИ

# Архитектура памяти AI-агента — три уровня
# Уровень 1: Working Memory — контекстное окно LLM (то, что модель "видит" прямо сейчас)
# Уровень 2: Short-term Memory — сессия агента (история сообщений в Redis/Postgres)
# Уровень 3: Long-term Memory — векторная БД (все прошлые взаимодействия, факты, предпочтения)

class MemoryArchitecture:
    """Трёхуровневая архитектура памяти агента."""
    def __init__(self):
        self.working_memory  = []     # список сообщений в контекстном окне
        self.short_term     = []     # история текущей сессии
        self.long_term      = None   # ChromaDB коллекция

# 2. WORKING MEMORY — КОНТЕКСТНОЕ ОКНО

pip install tiktoken

import tiktoken

encoder = tiktoken.encoding_for_model("gpt-4")

# Функция безопасной обрезки контекста
def trim_context(messages, max_tokens=8000, reserve=2000):
    """Обрезает историю сообщений, сохраняя system prompt и последние N сообщений."""
    total = 0
    trimmed = []
    system_msg = None
    for msg in messages:
        if msg["role"] == "system":
            system_msg = msg
            continue
    available = max_tokens - reserve
    if system_msg:
        available -= len(encoder.encode(system_msg["content"]))
        trimmed.append(system_msg)
    for msg in reversed(messages):
        if msg["role"] == "system": continue
        tokens = len(encoder.encode(msg["content"]))
        if total + tokens > available: break
        total += tokens
        trimmed.insert(1, msg)
    return trimmed

# 3. SHORT-TERM MEMORY — СЕССИЯ АГЕНТА

pip install redis

import redis
import json
from datetime import datetime

r = redis.Redis(host='localhost', port=6379, decode_responses=True)

class SessionMemory:
    """Краткосрочная память: Redis хранит всю историю сессии."""
    def __init__(self, session_id, ttl=3600):
        self.key = f"session:{session_id}"
        self.ttl = ttl

    def add_message(self, role, content):
        msg = {"role": role, "content": content, "ts": datetime.now().isoformat()}
        r.rpush(self.key, json.dumps(msg))
        r.expire(self.key, self.ttl)

    def get_history(self, limit=50):
        raw = r.lrange(self.key, -limit, -1)
        return [json.loads(m) for m in raw]

# 4. LONG-TERM MEMORY — CHROMADB

pip install chromadb openai

import chromadb
from openai import OpenAI

client = OpenAI()
chroma = chromadb.PersistentClient(path="./agent_memory")
collection = chroma.get_or_create_collection("memories")

def embed(text):
    """Получить эмбеддинг через OpenAI ADA-002."""
    resp = client.embeddings.create(model="text-embedding-ada-002", input=text)
    return resp.data[0].embedding

def remember(fact, metadata=None):
    """Сохранить факт в долгосрочную память."""
    collection.add(documents=[fact], embeddings=[embed(fact)], ids=[f"mem_{hash(fact)}"], metadatas=[metadata or {}])

def recall(query, n=5):
    """Найти релевантные воспоминания."""
    results = collection.query(query_embeddings=[embed(query)], n_results=n)
    return results['documents'][0]

# 5. АРХИТЕКТУРА MEMORY PIPELINE

def memory_pipeline(user_input, session_id):
    """Полный пайплайн: user msg → embed → search → inject → respond."""
    # 1. Получаем историю сессии (short-term)
    session = SessionMemory(session_id)
    history = session.get_history()

    # 2. Ищем релевантные воспоминания (long-term)
    relevant = recall(user_input, n=3)

    # 3. Инжектим в system prompt
    memory_context = "\n".join(f"- {m}" for m in relevant)
    system_prompt = f"""Ты AI-агент с памятью.
Релевантные воспоминания:
{memory_context}

История диалога ниже. Отвечай полезно, используя контекст."""

    # 4. Формируем messages для LLM (working memory)
    messages = [{"role": "system", "content": system_prompt}] + history[-20:]
    messages.append({"role": "user", "content": user_input})

    # 5. LLM ответ и сохранение
    resp = client.chat.completions.create(model="gpt-4", messages=messages)
    answer = resp.choices[0].message.content
    session.add_message("user", user_input)
    session.add_message("assistant", answer)
    return answer

# 6. ПРОДВИНУТЫЙ ПРИМЕР: АГЕНТ С ПАМЯТЬЮ

pip install chromadb openai redis tiktoken

import os, json, hashlib
from datetime import datetime
import chromadb, redis, tiktoken
from openai import OpenAI

class MemoryAgent:
    """Полноценный AI-агент с трёхуровневой памятью."""
    def __init__(self, session_id, model="gpt-4"):
        self.client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
        self.redis = redis.Redis(decode_responses=True)
        self.chroma = chromadb.PersistentClient(path="./agent_db")
        self.coll = self.chroma.get_or_create_collection("ltm")
        self.session = session_id
        self.model = model
        self.enc = tiktoken.encoding_for_model(model)

    def chat(self, msg):
        # Long-term recall
        emb = self._embed(msg)
        memories = self.coll.query(query_embeddings=[emb], n_results=3)['documents'][0]
        ctx = "\n".join(f"• {m}" for m in memories)

        # Short-term history
        raw = self.redis.lrange(f"sess:{self.session}", -30, -1)
        hist = [json.loads(m) for m in raw]

        # Build messages + trim
        msgs = [{"role":"system","content":f"Память:\n{ctx}\n\nБудь полезным."}] + hist + [{"role":"user","content":msg}]
        msgs = self._trim(msgs, 8000)

        # LLM call
        resp = self.client.chat.completions.create(model=self.model, messages=msgs)
        ans = resp.choices[0].message.content

        # Save to both memories
        for role, text in [("user",msg),("assistant",ans)]:
            self.redis.rpush(f"sess:{self.session}", json.dumps({"role":role,"content":text,"ts":datetime.now().isoformat()}))
        self.coll.add(documents=[text], embeddings=[self._embed(text)], ids=[f"{self.session}_{hashlib.md5(text.encode()).hexdigest()[:8]}"])
        return ans

    def _embed(self, text):
        return self.client.embeddings.create(model="text-embedding-ada-002", input=text).data[0].embedding

    def _trim(self, msgs, limit):
        total = 0; out = []
        for m in reversed(msgs):
            t = len(self.enc.encode(m["content"]))
            if total + t > limit: break
            total += t; out.append(m)
        return list(reversed(out))

🔗 Полезные ссылки

📖 ChromaDB Docs📖 OpenAI Embeddings📖 LangChain Memory📖 Redis Docs