Главная / Статьи / Пять стратегий памяти в LangChain: от буфера до памяти сущностей

Пять стратегий памяти в LangChain: от буфера до памяти сущностей

Почему чат-модели забывают всё между вызовами и пять паттернов памяти для LangChain и LangGraph: буфер, окно, сводка, буфер со сводкой и сущности, с их плюсами и минусами.

1667 слов

Многие думают, что чат-модель помнит, что вы ей сказали. Это не так. Вызовите GPT-5, GPT-4o, Claude или Gemini, и результат одинаковый: каждый запрос начинается с нуля, и если вы заново не отправите прошлые реплики, модель понятия не имеет, что они были. Вот самая короткая демонстрация:

from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="gpt-4o-mini")llm.invoke("Hi, my name is Asha.")
llm.invoke("What's my name?")

Второй вызов ничего не знает о первом. Имя Аши так и не было отправлено, поэтому модель не может его назвать.

Что такое память в LangChain

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

Главное противоречие

Допустим, пользовательница представляется Ашей, получает дружелюбный ответ, а позже спрашивает, как её зовут. Чтобы ответить правильно, то первое сообщение должно попасть в новый промпт.

Проблемы начинаются, когда чаты разрастаются до 10, 100 или 1000 сообщений. Если каждый раз отправлять всё заново, растут расход токенов, счета за API, задержка и нагрузка на контекстное окно. Каждый выбор меняет одно на другое:

  • Помните больше — растёт стоимость.
  • Помните меньше — стоимость падает, но страдает точность.
  • Отправляете больше контекста — ответы замедляются.
  • Отправляете меньше контекста — ответы ускоряются, но теряется информация.

Стратегия 1: буферная память

Буферная память хранит весь диалог целиком и отправляет его при каждом вызове. Представьте чат, который никогда ничего не удаляет. Код короткий:

from langchain_core.messages import HumanMessage
history = []def chat(user_input):
    history.append(HumanMessage(user_input))    response = llm.invoke(history)    history.append(response)    return response.content

Пример разговора:

User: I live in Chennai.
Assistant: That's great.

Чуть позже:

User: Which city do I live in?
Assistant: Chennai

Модель правильно называет Ченнаи, потому что та строчка всё ещё есть в истории, которую она получает.

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

Стратегия 2: оконная память

Здесь хранятся только последние K сообщений. Всё, что старше, просто удаляется — как на доске, где каждая новая запись стирает самую старую.

from langchain_core.messages import trim_messages
trimmed = trim_messages(
    history,
    strategy="last",
    max_tokens=10,
    token_counter=len
)

Попробуйте такой обмен:

User: My name is Asha.
Assistant: Nice to meet you.
User: Message 1
User: Message 2
User: Message 3
User: Message 4User: What's my name?

Если сообщение с именем уже выпало из окна, модели придётся признать поражение:

Assistant: I don't know your name.

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

Стратегия 3: память на сводках

Допустим, вы не хотите копить всё подряд, но и забывать всё тоже не хотите. При памяти на сводках модель время от времени пишет краткий пересказ старых реплик, и этот пересказ заменяет исходный текст.

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

После долгого чата сохранённое состояние может выглядеть так:

Summary:
User is building a shipment tracking application.
The discussion focused on Azure deployment and performance optimization.

Вместо 100 сообщений вы отправляете это. Примерная реализация начинается с пустой текущей сводки:

running_summary = ""

Затем, когда история превышает порог, старые сообщения сжимаются:

if len(history) > 6:
    old_messages = history[:-2]    running_summary = llm.invoke(
        f"Summarize this conversation:\n{old_messages}"
    ).content    history[:] = history[-2:]

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

Стратегия 4: буфер со сводкой

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

Хороший личный ассистент руководителя работает так же. Вчерашнюю просьбу он помнит точно:

You asked for the deployment status yesterday.

А общий фон держит в сжатом виде:

This project started in January and focuses on Azure migration.

Свежие детали остаются чёткими, а старые превращаются в сжатое знание. Минимальная версия задаёт лимит на число хранимых последних сообщений:

RECENT_LIMIT = 4
running_summary = ""def chat(user_input):
    global running_summary    history.append(HumanMessage(user_input))    if len(history) > RECENT_LIMIT:        older_messages = history[:-RECENT_LIMIT]        running_summary = llm.invoke(
            f"""
            Update this summary:
            {running_summary}            Using:
            {older_messages}
            """
        ).content        history[:] = history[-RECENT_LIMIT:]    context = [
        SystemMessage(
            content=f"Summary: {running_summary}"
        )
    ] + history    response = llm.invoke(context)    history.append(response)    return response.content

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

Стратегия 5: память сущностей

Иногда хранить нужно не сам диалог, а факты. Возьмём такие утверждения:

User: My sister's birthday is in March.
User: I work at Contoso.
User: My manager is Priya.

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

{
    "Priya": [
        "User's manager"
    ],
    "Contoso": [
        "User works here"
    ]
}

Извлечение начинается с пустого хранилища:

entity_store = {}

а функция просит модель вытащить сущности и факты:

def extract_entities(text):    prompt = f"""
    Extract entities and facts:
    {text}
    """    return llm.invoke(prompt).content

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

Как это делается в LangGraph

В старых туториалах встречаются знакомые классы:

ConversationBufferMemory
ConversationSummaryMemory
ConversationBufferWindowMemory

Теперь это устаревшие паттерны, и современная работа с LangChain опирается на LangGraph. Раньше создавали объект памяти:

memory = ConversationBufferMemory()

В новом стиле используется чекпойнтер, например

MemorySaver()

или

PostgresSaver()

для хранения состояния разговора. Обрезка, сводки и обработка этого состояния — уже ваша забота. Настройка графа начинается с таких импортов:

from langgraph.graph import (
    StateGraph,
    MessagesState,
    START
)

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

from langgraph.checkpoint.memory import (
    MemorySaver
)from langchain_core.messages import (
    trim_messages
)def call_model(state: MessagesState):    trimmed = trim_messages(
        state["messages"],
        strategy="last",
        max_tokens=10,
        token_counter=len
    )    response = llm.invoke(trimmed)    return {
        "messages": [response]
    }graph = StateGraph(MessagesState)graph.add_node("model", call_model)graph.add_edge(START, "model")app = graph.compile(
    checkpointer=MemorySaver()
)

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

Можно ли хранить память в Azure Blob Storage?

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

Redis
    ↓
PostgreSQL
    ↓
Azure Blob Storage

В такой схеме Redis хранит активные разговоры, PostgreSQL — историю сессий, а Blob Storage архивирует долгосрочные данные.

Как выбрать стратегию

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

Напоследок

Устройство памяти — одно из самых важных решений в ИИ-продукте. Бот без памяти кажется забывчивым, а бот с безграничной памятью становится дорогим и медленным. Цель не в том, чтобы помнить всё, а в том, чтобы помнить нужное.

Для большинства продакшен-приложений сейчас лучший баланс масштаба, стоимости и пользовательского опыта даёт связка из буфера со сводкой, памяти сущностей и чекпойнтов LangGraph. Если вы строите ассистентов на LangChain или LangGraph, правильно устроенная память улучшит качество и производительность сильнее, чем почти любой трюк с промптами. Самые умные системы — не те, что помнят всё, а те, что знают, что сохранить.