Долгосрочная память и управление состоянием диалога
Введение: Проблема Амнезии у ИИ
Представьте, что вы наняли блестящего аналитика. Он эрудирован, мгновенно считает в уме и пишет отличные отчеты. Но у него есть одна особенность: каждый раз, когда вы выходите из комнаты и возвращаетесь, он забывает, кто вы, о чем вы говорили пять минут назад и над каким проектом работаете уже месяц. Вам приходится заново представляться и пересказывать всю историю сотрудничества.
Именно так работает «ванильная» модель Gemini (и любая другая LLM) без внешних механизмов памяти. Модели stateless — они не хранят состояние между запросами. Для создания действительно полезных агентов, способных решать сложные задачи (планирование путешествий, написание кода в несколько этапов, долгосрочное менторство), нам необходимо решить проблему «амнезии».
В этом уроке мы отойдем от простых чат-ботов и погрузимся в архитектуру долгосрочной памяти и управления состоянием диалога. Мы разберем, как использовать огромный контекст Gemini 3 эффективно, когда стоит применять векторные базы данных, и как научить агента помнить не только что было сказано, но и каков статус текущей задачи.
Уровни памяти: От буфера до базы знаний
В разработке агентов память — это не просто сохранение логов чата. Это многоуровневая система. Давайте разделим её на три ключевых слоя:
- Краткосрочная память (Short-term / Buffer Memory): Это то, что находится непосредственно в контекстном окне модели прямо сейчас. Это «оперативная память». В Gemini 3 контекстное окно огромно (до 2 млн токенов), что позволяет держать в оперативной памяти целые книги. Но это стоит денег и времени на обработку.
- Долгосрочная память (Long-term / Vector Memory): Это «жесткий диск» агента. Сюда попадают факты, которые нужно хранить неделями или годами. Для этого мы используем векторные базы данных (Vector Stores). Мы превращаем текст в эмбеддинги (числовые векторы) и ищем по смыслу, а не по точному совпадению слов.
- Память сущностей (Entity Memory): Выделение и хранение конкретных фактов о пользователе или объекте. Например: «Пользователь любит Python», «Проект дедлайн: 25 октября». Это структурированные данные.
Главная ошибка новичков — пытаться запихнуть всё в контекстное окно. Даже с возможностями Gemini, отправлять историю чата за год в каждом запросе — это архитектурное самоубийство из-за латентности и стоимости. Нам нужна стратегия.
# Пример 1: Примитивная память (Sliding Window)
# Подход: храним только последние N сообщений.
# Проблема: теряем контекст начала разговора и важные детали из прошлого.
class BufferMemory:
def __init__(self, capacity=10):
self.history = []
self.capacity = capacity
def add_message(self, role, content):
self.history.append({"role": role, "content": content})
if len(self.history) > self.capacity:
self.history.pop(0) # Удаляем самое старое
def get_context(self):
return self.history
# Пример использования
memory = BufferMemory(capacity=4)
memory.add_message("user", "Привет, меня зовут Алекс.")
memory.add_message("model", "Привет, Алекс! Чем могу помочь?")
# ... спустя 10 сообщений ...
# Агент уже забыл, что пользователя зовут Алекс, так как это сообщение ушло из окна.
Стратегия 1: Суммаризация и Context Caching
Чтобы не терять важные детали при переполнении буфера, мы используем суммаризацию. Когда диалог становится слишком длинным, мы просим Gemini «сжать» прошлые события в краткое резюме и помещаем это резюме в начало системного промпта (System Instruction).
Однако, в Gemini 3 у нас есть мощный инструмент — Context Caching (Кэширование контекста). Если вы создаете агента, который работает с огромной документацией или очень длинной историей переписки, вы можете закэшировать префикс промпта.
Как это работает:
Вы отправляете массивную вводную часть (например, историю проекта за месяц) один раз, получаете идентификатор кэша, и в следующих запросах ссылаетесь на него. Это радикально снижает стоимость (input tokens) и ускоряет ответ, так как модели не нужно заново «читать» весь текст.
import google.generativeai as genai
from google.generativeai import caching
import datetime
# Представим, что у нас есть огромный лог общения
huge_history = "...тысячи строк текста о проекте..."
# Создаем кэш, который живет, например, 1 час
cache = caching.CachedContent.create(
model="models/gemini-1.5-pro-002",
display_name="project_alpha_history",
system_instruction="Ты помощник по проекту Alpha. Используй историю для контекста.",
contents=[huge_history],
ttl=datetime.timedelta(minutes=60),
)
# Теперь создаем модель, подключенную к этому кэшу
model = genai.GenerativeModel.from_cached_content(cached_content=cache)
# Запросы к этой модели будут обрабатываться быстрее и дешевле
response = model.generate_content("На чем мы остановились в прошлый вторник?")
print(response.text)
Стратегия 2: RAG для памяти (Retrieval-Augmented Generation)
Кэширование отлично подходит для одной длинной сессии. Но что, если пользователь возвращается через неделю? Кэш истек. Здесь на сцену выходит Векторная память.
Идея проста: каждое важное утверждение пользователя мы сохраняем в векторную БД (например, Chroma, Pinecone или даже простой FAISS). Когда приходит новый запрос, мы:
- Преобразуем запрос пользователя в вектор (embedding).
- Ищем в базе самые похожие по смыслу записи из прошлого.
- Добавляем найденные факты в контекст текущего промпта.
Это позволяет агенту отвечать на вопросы вроде «Какую книгу я советовал тебе прочитать в прошлом месяце?», даже если этот диалог был сотни сообщений назад.
# Псевдокод логики агента с долгосрочной памятью
class LongTermAgent:
def __init__(self, db_client, model):
self.db = db_client
self.model = model
def chat(self, user_input):
# 1. Поиск воспоминаний
# Превращаем запрос в вектор и ищем топ-3 релевантных факта
relevant_memories = self.db.similarity_search(user_input, k=3)
context_str = "\n".join([mem.text for mem in relevant_memories])
# 2. Формирование промпта
prompt = f"""
Используй следующую информацию из памяти для ответа, если нужно:
{context_str}
Пользователь: {user_input}
"""
# 3. Генерация ответа
response = self.model.generate_content(prompt)
# 4. Сохранение нового опыта (асинхронно или по важности)
# Не всё стоит запоминать. Можно спросить модель: "Стоит ли это запомнить?"
self.save_to_memory_if_important(user_input, response.text)
return response.text
Управление состоянием диалога (State Management)
Память — это «что было». Состояние — это «где мы сейчас».
В простых чатах состояние неявно. Но если агент должен оформить страховку, у него есть четкие этапы: Сбор данных -> Выбор тарифа -> Оплата -> Подтверждение.
Полагаться только на память LLM здесь опасно. Она может перескочить с оплаты обратно на знакомство. Для сложных агентов мы используем подход Finite State Machine (Конечный автомат) в связке с LLM.
Гибридный подход:
Мы определяем жесткие состояния (States) в коде, а LLM используем для навигации внутри состояния и принятия решения о переходе (Transition). LLM возвращает не просто текст, а структурированный JSON с флагом next_state.
Паттерн: Граф Состояний (State Graph)
В современной разработке (например, с использованием LangGraph) мы описываем поведение агента как граф.
- Nodes (Узлы): Функции, выполняющие работу (например, «Поиск в базе», «Генерация ответа», «Запрос уточнений»).
- Edges (Ребра): Логика переходов.
Gemini 3 отлично справляется с ролью «маршрутизатора» в таком графе. Вы даете ей текущее состояние и возможные пути, а она выбирает, куда идти дальше, основываясь на намерении пользователя.
Создайте класс 'MemoryManager', который реализует гибридную память. <br><br>Требования:<br>1. Метод `add_interaction(user, ai)`: сохраняет пару сообщений.<br>2. Метод `get_full_context()`: возвращает последние 5 сообщений (Short-term).<br>3. Метод `extract_facts(text)`: использует Gemini для извлечения важных фактов из текста (например, имя, предпочтения) и возвращает их списком. Это симуляция сохранения в Long-term память.<br><br>На вход дайте диалог, где пользователь говорит: «Я живу в Берлине, люблю джаз». Проверьте, что `extract_facts` вернул эти данные.
Вы разрабатываете бота техподдержки с использованием Gemini. Клиент может вести диалог в течение нескольких дней. Какое решение будет наиболее эффективным по соотношению цена/качество для хранения контекста?