Когда контекста не хватает: почему RAG — это не память
RAG — отличный инструмент для поиска по документам. Но это внешняя база знаний, а не память агента. Представьте: вы общаетесь с помощником, который каждые пять минут забывает ваше имя, ваши предпочтения, что вы только что обсуждали. Раздражает? Вот с этим и сталкиваются разработчики LLM-агентов.
Контекстное окно — это оперативная память. Долговременная память — это то, что сохраняется между сессиями, между запросами, между перезапусками системы.
Основная проблема: LLM по своей архитектуре статичны. Они не меняются во время инференса. Веса заморожены, контекст сбрасывается. Вся "эпизодическая память" о диалоге испаряется, как только токены выходят за пределы контекстного окна.
Три фундаментальных паттерна долговременной памяти
1 Эпизодическая память: дневник агента
Самый простой и самый рабочий подход. Каждое взаимодействие с агентом записывается в структурированном виде. Не просто лог диалога, а семантически размеченные события:
{
"timestamp": "2024-03-15T14:30:00Z",
"event_type": "user_preference",
"content": "Пользователь предпочитает получать ответы кратко, без лишних деталей",
"confidence": 0.9,
"source": "диалог_сессия_42"
}
Зачем такая сложность? Потому что простой текстовый лог — это мусор для LLM. Агент не сможет эффективно извлекать из него информацию. Структурированные записи позволяют фильтровать, искать, агрегировать.
2 Семантическая память: векторные эмбеддинги опыта
Звучит как RAG, но работает иначе. Здесь мы эмбеддим не документы, а извлеченные из диалогов сущности, факты, выводы. Каждая запись — это сжатое представление опыта.
Пример плохой реализации:
# Как НЕ делать
memory_entries = [
"Пользователь сказал, что любит кофе",
"Пользователь просил помочь с кодом на Python",
"Сегодня была проблема с API"
]
# Это превратится в хлам при поиске
Пример правильного подхода:
# Извлекаем и структурируем
import json
memory_entry = {
"entity": "user_preference",
"attribute": "coffee_type",
"value": "эспрессо",
"context": "утренний ритуал",
"embedding": get_embedding("предпочтение кофе: эспрессо утром")
}
# Сохраняем в векторную БД с метаданными
vector_db.add(
text=json.dumps(memory_entry),
embedding=memory_entry["embedding"],
metadata={
"type": "preference",
"entity": "user",
"timestamp": datetime.now().isoformat()
}
)
3 Процедурная память: навыки и паттерны действий
Это самый сложный, но самый мощный паттерн. Агент запоминает не только что произошло, но как он это сделал. Какие инструменты сработали, какие последовательности действий привели к успеху.
Представьте агента, который научился эффективно искать информацию в вашей семантической базе знаний. Он запоминает, какие запросы давали лучшие результаты, как структурировать вопросы.
Архитектурные решения: где и как хранить
| Хранилище | Для чего | Сложность | Когда выбирать |
|---|---|---|---|
| SQLite / PostgreSQL | Структурированные записи, метаданные | Низкая | Эпизодическая память, когда нужны сложные запросы |
| Chroma / Qdrant | Векторные поиски по опыту | Средняя | Семантическая память, поиск по смыслу |
| Redis | Быстрый кэш недавних событий | Низкая | Горячая память сессии |
| JSON-файлы | Прототипирование, локальная разработка | Очень низкая | Только для тестов. В продакшене — никогда. |
Ключевой момент: почти всегда нужна комбинация. Векторная БД для семантического поиска по опыту, реляционная — для структурированных данных, Redis — для быстрого доступа к текущему контексту.
Готовые решения: open-source репозитории, которые работают
MemGPT: операционная система для агентов с памятью
MemGPT — это не просто библиотека, это архитектура. Агенты здесь имеют иерархическую память: маленькое контекстное окно (как у людей — рабочая память) и внешнее хранилище (долговременная память).
Как это выглядит в коде:
# Упрощенная схема работы MemGPT
class MemGPTAgent:
def __init__(self):
self.context_window = [] # "Рабочая память"
self.external_memory = VectorStore() # Долговременная память
def process_query(self, query):
# 1. Ищем релевантные воспоминания
memories = self.external_memory.search(query)
# 2. Загружаем самые важные в контекст
self.context_window.extend(memories[:3])
# 3. Обрабатываем запрос с расширенным контекстом
response = llm.generate(self.context_window + [query])
# 4. Решаем, что сохранить в долговременную память
if should_save_to_memory(response, query):
self.external_memory.save(create_memory_entry(query, response))
# 5. Управляем переполнением контекста
self.context_window = manage_context_size(self.context_window)
return response
Сильная сторона MemGPT — явное разделение на управляющую LLM (решает, что сохранять/загружать) и рабочую LLM (отвечает пользователю). Слабая — сложность настройки.
LangGraph + память: производственный подход
LangGraph позволяет создавать stateful-агентов с явным управлением состоянием. Память здесь — часть состояния графа.
from langgraph.graph import StateGraph
from typing import TypedDict
class AgentState(TypedDict):
messages: list # Текущий диалог
memory: dict # Долговременная память
user_id: str # Идентификатор для персистентности
def memory_node(state: AgentState):
"""Узел, который обновляет память на основе диалога"""
# Анализируем последние сообщения
new_memories = extract_memories(state["messages"][-2:])
# Обновляем состояние
state["memory"].update(new_memories)
# Сохраняем в БД (асинхронно)
save_memory_to_db(state["user_id"], new_memories)
return state
# Строим граф с циклом памяти
graph = StateGraph(AgentState)
graph.add_node("process", process_query_node)
graph.add_node("update_memory", memory_node)
graph.add_edge("process", "update_memory")
graph.add_edge("update_memory", "process") # Цикл!
Этот подход ближе к production-системам. Память обновляется на каждом шаге, состояние сохраняется между вызовами.
AutoGen с персонализированными агентами
AutoGen хранит память в виде "профилей" агентов. Каждый агент имеет свою базу знаний, свои предпочтения, свой стиль общения. При смене агента (например, от программиста к маркетологу) память не теряется — она привязана к роли.
В AutoGen память часто реализуют через user proxy — специального агента, который помнит историю взаимодействий и предпочтения конкретного пользователя.
Типичные ошибки (и как их избежать)
Ошибка 1: Сохранять все подряд
Логировать каждый чих — путь к катастрофе. Векторная БД заполнится мусором, поиск станет бесполезным.
Решение: фильтрация на входе. Сохранять только:
- Пользовательские предпочтения (явно выраженные)
- Факты, проверенные через trusted sources
- Успешные шаблоны решения задач
- Ошибки и их исправления
Ошибка 2: Путать память с кэшем
Кэш — это временные данные для ускорения. Память — это ценный опыт, который должен сохраняться. Не используйте TTL для настоящих воспоминаний.
Ошибка 3: Игнорировать консистентность
Что происходит, если агент узнал, что пользователь веган, а через день пользователь говорит, что любит стейк? Противоречивая память хуже, чем отсутствие памяти.
Решение: система версионирования и разрешения конфликтов:
def update_memory(key, new_value, confidence):
old_value = memory_db.get(key)
if not old_value:
# Первое сохранение
memory_db.set(key, {
"value": new_value,
"confidence": confidence,
"sources": [current_source],
"updated_at": now()
})
elif should_override(old_value, new_value, confidence):
# Перезаписываем с сохранением истории
old_value["previous_values"].append({
"value": old_value["value"],
"changed_at": old_value["updated_at"]
})
old_value.update({
"value": new_value,
"confidence": confidence,
"updated_at": now()
})
Интеграция с существующими системами
Допустим, у вас уже работает RAG-система для документов. Как добавить долговременную память?
- Определите scope: что будет в RAG (документы), что в памяти (опыт взаимодействия)
- Добавьте слой извлечения воспоминаний перед RAG-поиском
- Реализуйте механизм обновления памяти на основе успешных ответов
- Настройте приоритеты: сначала память, потом RAG, потом базовые знания LLM
Пример архитектуры:
class HybridAgent:
def answer(self, question):
# Шаг 1: Поиск в личной памяти
personal_memories = self.memory.search(question)
# Шаг 2: Если в памяти нет — поиск в документах через RAG
if not personal_memories or confidence_low(personal_memories):
doc_context = self.rag_search(question)
context = personal_memories + doc_context
else:
context = personal_memories
# Шаг 3: Генерация ответа
answer = self.llm.generate(context, question)
# Шаг 4: Если ответ успешен — сохраняем в память
if answer_is_valuable(question, answer):
self.memory.save(create_memory(question, answer))
return answer
Будущее: куда движется архитектура памяти
Текущие системы — примитивны. Они запоминают факты, но не понимают причинно-следственные связи. Следующий шаг — memory с временными метками и графами зависимостей.
Представьте память, которая знает не только что "пользователь любит кофе", но и что "после утреннего кофе он продуктивен", "в пятницу он пьет капучино", "последний раз жаловался на кислотность".
Технически это означает переход от векторного поиска к графовым базам знаний. От изолированных фактов — к связанным нарративам.
Пока индустрия увлечена мультимодальными RAG и гигантскими контекстными окнами, настоящий прорыв будет в системах, которые умеют забывать. Да, забывать. Потому что память без забывания — это просто хранилище данных. Интеллект требует селективности.
Начните с простого: эпизодической памяти в SQLite. Добавьте векторный поиск для семантики. Поэкспериментируйте с упаковкой навыков в процедурную память. И помните: лучшая система памяти — та, которую пользователь не замечает.