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

А сегодня давайте поговорим про память ИИ-агентов, я поделюсь какие я использую чаще, поговорим про базовый минимум, без чего нет смысла пытаться строить ИИ-агентов. Начнём с того как вообще работает контекстное окно и почему его не хватает, потом разберём какие типы памяти бывают, как работает RAG и Graph RAG, какие фреймворки существуют

Контекстное окно

Чтобы понять зачем нужна память, нужно сначала разобраться как работает контекстное окно.

Контекстное окно — это максимальный объём текста который модель может обработать за один вызов. Всё что ей передали: ваш вопрос, история разговора, инструкции, документы — всё вместе, одним куском. Что влезло модель видит. Что не влезло не существует.

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

Допустим, вы парсите сайт или ищете информацию через ИИ в интернете и ваш инструмент (MCP, функция, да что угодно) спарсил вам весь HTML-код  страницы. Так вот, да он целиком отправится в модель и может заполнить всё контекстное окно. И тогда LLM начнёт галлюцинировать: середину контекста она уже плохо видит, начало тоже размывается, зато прекрасно помнит ту самую HTML-страницу которую вы зачем-то ей скормили.

Рассмотрим на примере travel‑агента с системным промптом, набором инструментов и возможностью ходит в интернет и т.д:

Кстати, а как считать токены ?

Токен – это кусок текста. Не символ, не слово, а кусок. Модель режет текст на куски перед обработкой. Как именно режет зависит от токенизатора, все современные модели используют примерно одинаковые токенизаторы.

  1. Английский текст режется по словам и частям слов

  2. Русский режется мельче, потому что кириллицы в обучающих данных было меньше:

Эксперимент: берем один и тот же текст на русском и на английском и считаем токены. Результат: русский текст занимает примерно в 2 раза больше токенов чем английский. Если контекстное окно модели 100 тыс. токенов русскоязычного текста туда поместится, с запасом, примерно 35–40 тыс. слов.

  • "you are a useful AI agent" – 26 символов, 6 токенов

  • "ты полезный ии агент" – 21 символ, 9 токенов

Проверить свой текст можно на claude-tokenizer.vercel.app

Проблема «lost in the middle»

Даже когда контекст формально не переполнен есть ещё одна проблема. Модели плохо работают с информацией из середины длинного контекста.

Почему это происходит? Модель читает текст последовательно, токен за токеном. Первые токены получают максимум внимания они фундамент, на них строится всё остальное. Последние токены тоже на виду они только что прочитаны. А вот середина оказывается в слепой зоне: слишком далеко от начала, чтобы быть якорем, слишком далеко от конца чтобы быть свежей. Внимание модели распределяется неравномерно.

Но происходит это не всегда. Если ваш контекст занимает 50–60% от размера контекстного окна, модель справляется нормально, внимания хватает на всё. Проблема начинается когда вы заполняете 60–70% и больше. Чем плотнее набито окно, тем глубже провал в середине. У вас контекст на 200K токенов и вы используете 150K, то середина почти гарантированно поплывёт – значит модель будет галлюцинировать.

Разберём как не допускать заполнения окна до критических значений и какие инструменты для этого есть. 

Три основных подхода.

  1. Суммаризация. Самый распространённый. Берём старые сообщения, просим отдельную LLM сжать их в резюме. Вместо 50 сообщений по 500 токенов появляется одно резюме на 1 000 токенов. Сжатие в 5–20 раз, экономия 70–80%. Многие популярные агентские системы: ChatGPT, Claude Code, Cursor и другие работают именно так: когда контекст подходит к лимиту система автоматически сжимает историю.

  2. Скользящее окно. Ещё проще: храним только последние N сообщений, всё что старше удаляем. Как лента чата в которой видно только последние 20 сообщений, а прокрутить вверх нельзя. Быстро и дёшево, но грубо, если что-то важное было в начале разговора, оно исчезнет навсегда.

  3.  Селективный контекст. Храним не всё подряд, а только важное. Системный промпт оставляем, он нужен всегда. Последние сообщения оставляем. Результаты инструментов оставляем. А промежуточные рассуждения агента («хм, попробую сначала проверить визу...») и прочую ерунду выбрасываем.

На практике эти стратегии часто работают вместе. Самая распространённая комбинация: суммаризация + скользящее окно.

Работает так: последние 5–10 сообщений храним как есть, целиком. Всё что было до них сжимаем в резюме. Агент видит подробный контекст последних шагов и краткую выжимку из всего что было раньше. И детали свежих шагов не теряются, и старый контекст не занимает всё окно.

Кстати у вас на этом месте могу возникнуть вопросы, а где храним все сообщения, контекст и т.д, храним мы во внешней памяти, об этом и будет наша вторая часть статьи.


Внешняя память ИИ-агентов

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

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

Внешнюю память по аналогии с человеческой памятью я тоже привык разделять на 3 типа:

  1. Эпизодическая – этот тип памяти использует для хранения информации в процессе работы с ИИ. Своего рода некий дневник, в который ИИ-агент записывает события, которые произошли при работе с ним. Сюда могут быть записаны все что было например как зовут вашего пользователя, где живет, чем занимается.
    Яркий пример все современные агентские системы: Chat GPT, Claude AI ну короче все чем мы пользуемся: и если вы замечали, в настройках есть пункт персонализация, включая эту персонализацию вы активируете, в т.ч у Chat GPT эпизодическую память.
    А в Claude AI это файл MEMORY.md который формируется по итогам разговора в рамках проекта или в рамках вашего профиля.

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

    Не нужна когда каждая задача уникальна и не связана с предыдущими. Генерация картинок по промпту. Перевод текста. Одноразовые вопросы типа «сколько будет 2+2». Тут прошлый опыт не поможет.

    Как человек отвечающий в компании за ряд продуктов как ИИ так и классических, могу сказать эпизодическая память это одно из лучших приемов для удержания пользователя.

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

    Когда вы слышите «RAG по документам» или «поиск по базе знаний» то речь именно о Базе знаний или по другому называются семантической памятью. Ну для удобства будем просто называть база знаний для агента.

  3. Процедурная память: правила и инструкции – это инструкции для агента. Как себя вести, в каком порядке действовать, какие правила соблюдать.

    Процедурная память часто живёт в системном промпте, но не обязательно. Может храниться в отдельных файлах и подгружаться по ситуации:
    Например в Тревел Агенте для бронирование грузим booking.md, для возврата грузим refund.md.

    Яркий пример процедурной памяти: Всякого рода "rules" который часто встречаются в кодинг агента Cursor, Claude Code. Они по разному называются где-то .cursor/rules/., а где то CLAUDE.md – но это все про одно и тоже.

    Можно ли назвать это систем промт – да, процедурная память это часть систем промпт.

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

  1. Слушай, а как "Включить" эту эпизодическую память?

  2. А где и как «хранится» память?

  3. А как хранится в одном агенте все 3 памяти? Как это происходит?

  4. Как подгружается информация из 3 источников сразу в контекст LLM? Не заполнится ли контекст ?

А как "Включить" эту эпизодическую память?

Нет кнопки «включить». Нет галочки в настройках. При построении ИИ-агентов, агентских систем вам в архитектуру нужно заложить дополнительный пайплайн извлечения и сохранения «этой» памяти и выглядит этот пайплайн примерно так: Весь диалог между юзером и ИИ сохраняется все как есть в конце сессии, или по середине когда вы де��аете суммаризацию (мы выше говорили что суммаризация будет нужна если большой диалогит.д) в этот момент вам нужно отправить весь диалог LLM с системный промптом, который не только суммаризацию сделает а вытащит еще и знания. LLM вернет вам что-то похожее на такой JSON, ну или просто тектсом получите, если вы хранить собираетесь просто текстом как это делает Claude

{
  "summary": "Юзер ID_112 попросил забронировать Барселону на 5 ночей.
              Нашли Hilton Diagonal Mar за 1180€.
              Юзер пожаловался на шум. Поменяли на Hotel Arts.",

  "extracted_memories": [
    {
      "type": "episodic",
      "content": "Март 2025: бронировали Барселону. Hilton Diagonal Mar —
                  Юзер ID_112 пожаловался на шум от стройки. Заменили на
                  Hotel Arts (1250€), остался доволен.",
      "importance": 7
    }
  ]
}

Вы берёте этот JSON, каждую запись превращаете в вектор (через embedding model) и сохраняете в базу с метаданными (type, importance, user_id, дата). Всё. При следующей сессии ищете по смыслу и подгружаете.

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

Проанализируй диалог между юзером и ассистентом.

Извлеки информацию для долговременной памяти и верни в строго указанном формате

Оцени важность от 1 до 10 (1 = рутина, 10 = критично).
Не извлекай очевидное и малоценное.
Верни JSON в формате: {"summary": "...", "extracted_memories": [...]}

Это весь пайплайн. Можно написать самому это буквально один LLM-вызов + сохранение в базу. А можно подключить готовое решение Mem0, Letta, Graphiti – которое делает то же самое под капотом. Ниже расскажу чуть детальнее про эти инструменты. По факту это готовая память для ваших агентов. Отличаются они архитектурой – где то это граф знаний (Graph RAG), а где-то классическая векторная БД

А где и как «хранится» память?

Где угодно. Серьёзно. Тип памяти определяется содержимым, а не хранилищем. Можно хранить все три типа памяти в Постгресе, в векторной БД или Графовой БД или вообще в файле типа «.md». Разница в содержимом записей.

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

Почему два формата? Потому что сырой диалог – это источник для извлечения. Из него LLM достаёт факты, эпизоды, выводы. А вектор – это уже обработанный результат, по которому потом идёт поиск. Сырой текст для анализа. Вектор для retrieval в RAG.

Все можно хранить в векторной БД или Граф БД, только храните разные типы памяти в разных коллекциях.

Почему не в одной коллекции? Потому что у разных типов памяти разная логика подгрузки. Процедуры (инструкции) грузятся всегда они маленькие и нужны в каждой сессии. Эпизоды (в нашем случае факты о юзере) тоже грузятся всегда. А вот База знаний (семантической памятью мы ее назвали) грузятся по запросу, только когда нашлось что-то похожее. Если всё в одной коллекции вы не сможете так гибко управлять.

Как подгружается информация из 3 источников сразу в контекст LLM? Не заполнится ли контекст ?

Так как мы уже все разбираем на примере Travel Agentа будет так:

Приходит запрос юзера: «Хочу в Барселону на майские». Дальше работает ваш код (оркестратор, агентский фреймворк как хотите назовите). Он делает три запроса параллельно к вашим хранилищам, то есть ищет и извлекает (retrive) Параллельно идет в файловую систему за инструкциями (процедурной памятью) и вся найденная информация склеивается в один промпт (или запрос) и отправляется LLM

{
  "model": "claude-sonnet-4-20250514",

  "system": "Ты travel-агент. Помогаешь с поездками. Отвечай с ценами.\n\n--- ПРОЦЕДУРА (из booking.md) ---\n1. Уточнить даты/бюджет\n2. Проверить визу\n3. Найти рейсы\n4. Найти отели\nПравило: если юзер жаловался на шум → предлагать только тихие отели\n\n--- ЗНАНИЯ О ДОМЕНЕ (из Graph RAG) ---\nБарселона → Испания → Шенген → виза 80€\nHilton Diagonal Mar: стройка, шум — НЕ предлагать\nHotel Arts: 4.5★, тихий район у моря, 145€/ночь\nPraktik Bakery: завтрак, тихий двор, веган-опции, 108€/ночь\n\n--- ПАМЯТЬ ЮЗЕРА (из Qdrant, user_id: ivanov_123) ---\n[факт] бюджет 1000-1500€\n[факт] веган, завтрак включён\n[факт] окно у прохода\n[факт] не переносит шум\n[эпизод] Янв 2025: Hilton Барселона — было шумно, пересилили в Hotel Arts, остался доволен",

  "tools": [
    { "name": "search_flights", "description": "Поиск рейсов" },
    { "name": "search_hotels", "description": "Поиск отелей" },
    { "name": "check_visa", "description": "Проверка визы" }
  ],

  "messages": [
    { "role": "user", "content": "Хочу опять в Барселону на майские, в этот раз потише" }
  ]
}

Все знания, которые есть в наших БД подгрузились в системный промпт.

Если еще короче и по-простому то есть функция или код который получает запрос от пользователя, запускается поиск информации в во внешней памяти это наши с вами БД будьто то это векторная база, обычная или просто текстовые файлы, код просто ищет что у нас есть по этому запросу пользователя и на этого пользователя. Дальше вся найденная информация склеивается в один промпт (или запрос) и отправляется LLM. И LLM уже в контексте что и как делать ка котвечать, отвечает пользователю ровно то, что он ожидает – все в этом вся магия.

В следующей части

Мы разобрали где и как агент хранит свои воспоминания. Но остался вопрос — а как он ищет нужное? Когда в базе тысячи записей, как найти именно те 5 которые релевантны текущему запросу?

Для этого нужно понять три вещи: как текст превращается в числа (эмбеддинги), как работает поиск по смыслу (RAG) и зачем нужны графы знаний (Graph RAG). Плюс разберём готовые фреймворки: Mem0, Letta, Graphiti — которые делают всё это под капотом. И посмотрим как устроена память у реальных агентов: Manus, Claude Code, Cursor и конечно у моего Travel Agent


Спасибо, что прочитали, если что-то было не понятно, пишите и подписывайтесь на тг канал