LLM-модели хорошо решают задачи диалога, но имеют одно ключевое ограничение: отсутствие встроенной долговременной памяти. Модель опирается только на текущий контекст сообщений, и при его обрезании:

  • забывает факты

  • путает детали

  • теряет согласованность личности

  • повышается стоимость из-за длины контекста

В этой статье я хочу разобрать архитектуру, которую использовал для реализации выборочной памяти в Telegram-боте на Python. Эта система позволяет сохранять важные сведения о пользователе и автоматически внедрять их в системный промпт при каждом запросе, обеспечивая стабильное и естественное поведение модели.

Статья не про бота или продукт, а только про техническую реализацию.

Проблема: LLM не имеют устойчивой памяти

Если использовать GPT или любую другую LLM «как есть», возникают типичные эффекты:

  • модель забывает имя пользователя через десяток сообщений

  • не различает важные и неважные факты

  • начинает выдумывать несвязанные данные

  • качество падает при увеличении истории диалога

  • стоимость растёт пропорционально длине контекста

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

Архитектура решения

Механизм памяти состоит из трёх слоёв:

  1. Извлечение фактов из сообщений пользователя

  2. Нормализация (дедупликация, фильтрация)

  3. Интеграция фактов в системный промпт

Сессии хранятся локально, в структуре вида:

{
    "history": [...],
    "user_name": "Иван",
    "facts": ["Он любит кофе", "У него есть собака"],
    "last_message_time": 17328131.3
}

История ограничена 20 сообщениями для экономии токенов (мой проект не коммерческий, полностью личная разработка и поддержание).

1. Извлечение значимой информации

Простейший пример: извлечение имени из текста.

def extract_and_store_name(user_message: str, session: dict):
    if session.get("user_name"):
        return  # имя уже сохранено

    patterns = [
        r"меня зовут\s+([а-яёА-ЯЁ]+)",
        r"зовут\s+([а-яёА-ЯЁ]+)",
        r"мое имя\s+([а-яёА-ЯЁ]+)",
    ]

    for p in patterns:
        match = re.search(p, user_message.lower())
        if match:
            session["user_name"] = match.group(1).capitalize()
            return

Для эмоций, предпочтений или обстоятельств правила аналогичны.

2. Извлечение других фактов

Более общий обработчик:

def extract_facts(content: str):
    content = content.lower().strip()
    memory = []

    if "кофе" in content:
        memory.append("Он любит кофе")

    if "собака" in content:
        memory.append("У него есть собака")

    if any(w in content for w in ["устал", "грустно", "плохо"]):
        memory.append("Он устал или грустит")

    return memory

В рабочей версии таких правил несколько десятков.
Важно: факты извлекаются только из сообщений пользователя, никогда из ответов модели.
Это предотвращает галлюцинации в памяти.

3. Дедупликация и фильтрация

Память может быстро засориться, поэтому перед записью выполняется очистка:

def normalize_memory(memory: list[str]):
    memory = [m.strip() for m in memory if m.strip()]
    memory = list(dict.fromkeys(memory))  # удаление повторов
    return memory

Если этого не делать, модель начинает путаться и переоценивать отдельные слова («кофе», значит пользователь - бариста и т.п.).

4. Интеграция памяти в системный промпт

Самая важная часть архитектуры.

Перед отправкой запроса LLM системный prompt динамически обновляется:

def update_system_prompt(base_prompt: dict, memory: list[str]):
    memory_text = "\n\nПамять:\n" + "\n".join(f"- {m}" for m in memory)

    return {
        "role": "system",
        "content": base_prompt["content"] + memory_text
    }

Далее промпт заменяет нулевой элемент истории:

session["history"][0] = update_system_prompt(base_prompt, session["facts"])

Это даёт два эффекта:

  1. Модель видит память как часть собственной личности, а не как сторонние подсказки.

  2. Важные сведения доступны в каждом запросе, но контекст остаётся компактным.

Пример: как это работает в диалоге

Пользователь:
«Сегодня устал на работе.»

Извлечённые факты:

  • Он работает

  • Он устал или грустит

Через сутки:

«Не могу уснуть.»

Модель отвечает приблизительно так:

«После напряжённого дня иногда сложно переключиться…»

Это достигается простым присутствием фактов в системном промпте.

Хранение данных

Сессии сохраняются в pickle:

user_sessions.pkl

Структура:

{
    "history": [...],
    "facts": [...],
    "user_name": "...",
    "last_message_time": 17328131.3
}

Сессия загружается при каждом сообщении и обновляется после обработки.

Основные проблемы и решения

1. Модель придумывает факты

Решение:
Хранить только факты, явно полученные из сообщений пользователя.

2. Забывание имени

Решение:
Имя включено прямо в системный промпт, а значит сохраняется стабильно.

3. Слишком быстрый переход на фамильярность

Решение:
Правила в системном промпте + ограничение эмоциональной близости.

4. Путаница времен года

Решение:
Фильтрация календарных выводов модели + использование timestamp последнего сообщения.

Использованный стек

  • Python

  • python-telegram-bot

  • OpenAI GPT-3.5-turbo (позднее обновлено до 4o-mini)

  • Fly.io как хостинг

  • локальное хранение (pickle + json)

  • простой словарь для представления состояния пользователя

Итоги

Выборочная долговременная память позволяет:

  • сохранять важные сведения о пользователе,

  • поддерживать устойчивую личность модели,

  • уменьшать стоимость контекста,

  • улучшать согласованность диалога,

  • контролировать поведение LLM на уровне промптов.

Главный вывод:

Эффективная работа LLM в диалоге зависит не от самой модели, а от слоя, который управляет памятью, контекстом и личностью.

Комментарии (3)


  1. NeriaLab
    14.12.2025 08:37

    То, что Вы описали - это даже не пародия на LTM, а только STM.


  1. mikeinside
    14.12.2025 08:37

    Тоже решаю задачу сохранения памяти у ИИ персонажа и что-то как-то все не нравится.
    Может есть у кого хорошие мысли или ссылки, чтобы реализовать хорошую взрослую архитекутуру памяти?


    1. NeriaLab
      14.12.2025 08:37

      У LLM не может быть LTM, т.к. это фундаментальное ограничение, которое изначально не заложено в архитектуру. Все пляски с LTM - это всего лишь попытки улучшить STM. Большинство даже не понимают как должна работать LTM, не говоря уж про релизацию

      Даже в когнитивно-символьных системах, где LTM изначально встроена в архитектуру, полностью взаимодействует с STM и локальной БЗ, то описание занимает несколько листов текста, а не "пару строк кода" как в статье