Долгосрочная память и управление состоянием диалога

45 минут Урок 19

Введение: Проблема Амнезии у ИИ

Представьте, что вы наняли блестящего аналитика. Он эрудирован, мгновенно считает в уме и пишет отличные отчеты. Но у него есть одна особенность: каждый раз, когда вы выходите из комнаты и возвращаетесь, он забывает, кто вы, о чем вы говорили пять минут назад и над каким проектом работаете уже месяц. Вам приходится заново представляться и пересказывать всю историю сотрудничества.

Именно так работает «ванильная» модель Gemini (и любая другая LLM) без внешних механизмов памяти. Модели stateless — они не хранят состояние между запросами. Для создания действительно полезных агентов, способных решать сложные задачи (планирование путешествий, написание кода в несколько этапов, долгосрочное менторство), нам необходимо решить проблему «амнезии».

В этом уроке мы отойдем от простых чат-ботов и погрузимся в архитектуру долгосрочной памяти и управления состоянием диалога. Мы разберем, как использовать огромный контекст Gemini 3 эффективно, когда стоит применять векторные базы данных, и как научить агента помнить не только что было сказано, но и каков статус текущей задачи.

Уровни памяти: От буфера до базы знаний

В разработке агентов память — это не просто сохранение логов чата. Это многоуровневая система. Давайте разделим её на три ключевых слоя:

  • Краткосрочная память (Short-term / Buffer Memory): Это то, что находится непосредственно в контекстном окне модели прямо сейчас. Это «оперативная память». В Gemini 3 контекстное окно огромно (до 2 млн токенов), что позволяет держать в оперативной памяти целые книги. Но это стоит денег и времени на обработку.
  • Долгосрочная память (Long-term / Vector Memory): Это «жесткий диск» агента. Сюда попадают факты, которые нужно хранить неделями или годами. Для этого мы используем векторные базы данных (Vector Stores). Мы превращаем текст в эмбеддинги (числовые векторы) и ищем по смыслу, а не по точному совпадению слов.
  • Память сущностей (Entity Memory): Выделение и хранение конкретных фактов о пользователе или объекте. Например: «Пользователь любит Python», «Проект дедлайн: 25 октября». Это структурированные данные.

Главная ошибка новичков — пытаться запихнуть всё в контекстное окно. Даже с возможностями Gemini, отправлять историю чата за год в каждом запросе — это архитектурное самоубийство из-за латентности и стоимости. Нам нужна стратегия.

python
# Пример 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) и ускоряет ответ, так как модели не нужно заново «читать» весь текст.

python
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). Когда приходит новый запрос, мы:

  1. Преобразуем запрос пользователя в вектор (embedding).
  2. Ищем в базе самые похожие по смыслу записи из прошлого.
  3. Добавляем найденные факты в контекст текущего промпта.

Это позволяет агенту отвечать на вопросы вроде «Какую книгу я советовал тебе прочитать в прошлом месяце?», даже если этот диалог был сотни сообщений назад.

python
# Псевдокод логики агента с долгосрочной памятью

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. Клиент может вести диалог в течение нескольких дней. Какое решение будет наиболее эффективным по соотношению цена/качество для хранения контекста?