LangChain

Checkpointer и память сессии | Курс LangChain Agents урок 2

Checkpointer и память сессии | Курс LangChain Agents урок 2
Mikhail
Автор
Mikhail
Опубликовано 23.03.2026
0,0
Views 6

Цель урока: понять, как create_agent устроен внутри и добавить агенту память, чтобы он помнил предыдущие сообщения в разговоре.


Теория

Граф внутри create_agent

В уроке 1 мы запускали агента через create_agent. Внутри этот вызов строит граф LangGraph с двумя узлами:

START → model → tools → model → tools → ... → END

Узел model - вызов языковой модели. Она смотрит на историю сообщений, думает и либо вызывает инструмент, либо выдаёт финальный ответ.

Узел tools - выполнение инструментов, которые запросила модель. Результат добавляется в историю и управление возвращается в узел model.

Граф работает в цикле до тех пор, пока модель не решит, что ответ готов, или не достигнет лимита итераций.

Состояние

На каждом шаге граф работает с состоянием (state), словарём с историей сообщений. Минимальное состояние:

{"messages": [HumanMessage(...), AIMessage(...), ToolMessage(...), ...]}

После каждого шага состояние обновляется и новые сообщения добавляются в список. Это и есть то, что модель видит на следующем шаге.

Checkpoint

Каждый раз, когда граф заканчивает шаг (super-step), LangGraph сохраняет снимок состояния - checkpoint. Это похоже на автосохранение в игре, если что-то пошло не так, можно вернуться к любому сохранению.

Checkpoint позволяет:

  • продолжить разговор с того места, где он остановился
  • реализовать паузу для подтверждения от пользователя (урок 8)
  • откатиться назад при ошибке

По умолчанию create_agent не сохраняет checkpoint'ы, каждый вызов .invoke() начинается с чистого состояния.

Thread

Набор checkpoint'ов одного разговора называется thread. Каждый thread идентифицируется через thread_id, произвольную строку.

Когда вы передаёте thread_id, агент:

  1. при первом вызове - создаёт новый thread и сохраняет checkpoint
  2. при следующих вызовах - загружает предыдущее состояние и продолжает с него

Именно так агент "помнит" предыдущие сообщения разговора.

InMemorySaver

Для хранения checkpoint'ов нужен checkpointer. В разработке используют InMemorySaver он хранит всё в оперативной памяти процесса. После перезапуска программы все данные теряются.

Для production нужен PostgresSaver или RedisSaver. Это тема урока 6.


Примеры кода

Агент без памяти

import os
from dotenv import load_dotenv
from langchain.agents import create_agent
from langchain_openai import ChatOpenAI

load_dotenv()

model = ChatOpenAI(model=os.getenv("MODEL_NAME", "gpt-4o-mini"))
agent = create_agent(model=model, tools=[])

# Первый вызов
agent.invoke({"messages": [{"role": "user", "content": "Меня зовут Алексей."}]})

# Второй вызов, агент не помнит первый
response = agent.invoke({
    "messages": [{"role": "user", "content": "Как меня зовут?"}]
})
print(response["messages"][-1].content)
# Вывод: Не знаю вашего имени. Как вас зовут?

Каждый .invoke() это отдельный разговор с нуля. Агент не видит предыдущие вызовы.

Агент с памятью сессии

import os
from dotenv import load_dotenv
from langchain.agents import create_agent
from langchain_openai import ChatOpenAI
from langgraph.checkpoint.memory import InMemorySaver

load_dotenv()

model = ChatOpenAI(model=os.getenv("MODEL_NAME", "gpt-4o-mini"))

agent = create_agent(
    model=model,
    tools=[],
    checkpointer=InMemorySaver(),
)

config = {"configurable": {"thread_id": "session-1"}}

# Первый вызов
agent.invoke(
    {"messages": [{"role": "user", "content": "Меня зовут Миша."}]},
    config,
)

# Второй вызов, агент помнит первый
response = agent.invoke(
    {"messages": [{"role": "user", "content": "Как меня зовут?"}]},
    config,
)
print(response["messages"][-1].content)
# Вывод: Вас зовут Миша.

thread_id - это ключ. Все вызовы с одним и тем же thread_id видят общую историю сообщений.

Две независимые сессии

import os
from dotenv import load_dotenv
from langchain.agents import create_agent
from langchain_openai import ChatOpenAI
from langgraph.checkpoint.memory import InMemorySaver

load_dotenv()

model = ChatOpenAI(model=os.getenv("MODEL_NAME", "gpt-4o-mini"))
checkpointer = InMemorySaver()

agent = create_agent(model=model, tools=[], checkpointer=checkpointer)

# Сессия пользователя A
config_a = {"configurable": {"thread_id": "user-alice"}}
agent.invoke(
    {"messages": [{"role": "user", "content": "Меня зовут Алиса."}]},
    config_a,
)

# Сессия пользователя B
config_b = {"configurable": {"thread_id": "user-bob"}}
agent.invoke(
    {"messages": [{"role": "user", "content": "Меня зовут Боб."}]},
    config_b,
)

# Каждый пользователь получает свой ответ
response_a = agent.invoke(
    {"messages": [{"role": "user", "content": "Как меня зовут?"}]},
    config_a,
)
response_b = agent.invoke(
    {"messages": [{"role": "user", "content": "Как меня зовут?"}]},
    config_b,
)

print(response_a["messages"][-1].content)  # Вас зовут Алиса.
print(response_b["messages"][-1].content)  # Вас зовут Боб.

InMemorySaver хранит все threads в одном экземпляре. Важно создавать checkpointer один раз и передавать его в агента, а не создавать новый на каждый запрос.

Просмотр состояния thread

После разговора можно посмотреть, что сохранено:

config = {"configurable": {"thread_id": "user-alice"}}
state = agent.get_state(config)

print(state.values["messages"])  # список всех сообщений
print(state.next)                # следующий узел (пусто, если разговор завершён)

Это полезно для отладки, можно посмотреть, что агент "видел" на каждом шаге.

Сквозной проект: добавляем память агенту-аналитику

import os
from dotenv import load_dotenv
from langchain.agents import create_agent
from langchain.tools import tool
from langchain_openai import ChatOpenAI
from langgraph.checkpoint.memory import InMemorySaver

load_dotenv()

@tool
def search_web(query: str) -> str:
    """Ищет информацию в интернете по запросу. Используй для поиска актуальных
    данных, новостей, фактов о компаниях, событиях и людях."""
    return f"[Результаты поиска по запросу '{query}']: найдено 5 источников."

@tool
def read_file(path: str) -> str:
    """Читает содержимое текстового файла по указанному пути. Используй, когда
    нужно проанализировать локальный документ."""
    try:
        with open(path, "r", encoding="utf-8") as f:
            return f.read()
    except FileNotFoundError:
        return f"Файл '{path}' не найден."

SYSTEM_PROMPT = """Ты аналитик-исследователь. Твоя задача - собирать информацию
по запросу пользователя, используя доступные инструменты, и давать структурированный
ответ с выводами.

Если информации недостаточно, сделай несколько поисковых запросов, уточняя каждый
следующий на основе предыдущих результатов."""

model = ChatOpenAI(model=os.getenv("MODEL_NAME", "gpt-4o-mini"))

agent = create_agent(
    model=model,
    tools=[search_web, read_file],
    system_prompt=SYSTEM_PROMPT,
    checkpointer=InMemorySaver(),
)

session_id = "research-session-1"
config = {"configurable": {"thread_id": session_id}}

# Первый запрос
response = agent.invoke(
    {"messages": [{"role": "user", "content": "Найди информацию о компании Anthropic."}]},
    config,
)
print(response["messages"][-1].content)

# Уточняющий вопрос в том же thread, агент помнит контекст
response = agent.invoke(
    {"messages": [{"role": "user", "content": "А кто основатели?"}]},
    config,
)
print(response["messages"][-1].content)

Теперь агент ведёт полноценный диалог, второй вопрос "А кто основатели?" агент понимает в контексте первого запроса про Anthropic.


Частые ошибки

Забыли передать thread_id

# Неправильно: checkpointer есть, но thread_id не передан
agent = create_agent(model=model, tools=[], checkpointer=InMemorySaver())
agent.invoke({"messages": [...]})  # ошибка или поведение без памяти

# Правильно
agent.invoke(
    {"messages": [...]},
    {"configurable": {"thread_id": "my-session"}},
)

Если checkpointer задан, но thread_id не передан, LangGraph не сможет загрузить или сохранить состояние. Проверяйте оба условия.


InMemorySaver в production

# Так не стоит делать в prod: данные исчезнут при перезапуске
agent = create_agent(model=model, tools=[], checkpointer=InMemorySaver())

InMemorySaver хранит данные в оперативной памяти. При перезапуске сервера все сессии теряются. Для production используйте PostgresSaver (урок 6).


Один thread_id на всех пользователей

# Неправильно: все пользователи видят один разговор
GLOBAL_CONFIG = {"configurable": {"thread_id": "main"}}

def handle_user_message(user_id: str, message: str):
    return agent.invoke({"messages": [{"role": "user", "content": message}]}, GLOBAL_CONFIG)

# Правильно: отдельный thread_id на пользователя или на сессию
def handle_user_message(user_id: str, message: str):
    config = {"configurable": {"thread_id": user_id}}
    return agent.invoke({"messages": [{"role": "user", "content": message}]}, config)

thread_id - это разделитель контекста. Генерируйте его на уровне сессии пользователя, а не глобально.


Создание нового checkpointer на каждый запрос

# Неправильно: каждый запрос получает пустую память
def handle_request(message: str):
    agent = create_agent(
        model=model,
        tools=[],
        checkpointer=InMemorySaver(),  # новый объект = пустая память
    )
    ...

# Правильно: создаём один раз при старте приложения
checkpointer = InMemorySaver()
agent = create_agent(model=model, tools=[], checkpointer=checkpointer)

def handle_request(thread_id: str, message: str):
    config = {"configurable": {"thread_id": thread_id}}
    ...

Задание

Возьмите агента конвертации валют из задания урока 1 и добавьте ему память.

Проведите диалог из трёх шагов в одном thread:

  1. Спросите курс доллара к рублю
  2. Уточните: "Сколько это в евро?" (агент должен понять, что речь о сумме из предыдущего ответа)
  3. Спросите: "А какую сумму я назвал в самом начале?" (агент должен вспомнить из истории)

Затем запустите тот же третий вопрос с другим thread_id и убедитесь, что агент не помнит ничего из первой сессии.

<< урок 1

урок 3 >>


Подписывайтесь на мой Telegram канал

Если вам нужен ментор и вы хотите научиться разрабатывать AI агентов, пишите, обсудим условия

Авторизуйтесь, чтобы оставить комментарий.

Комментариев: 0

Нет комментариев.

Тут может быть ваша реклама

Пишите info@aisferaic.ru

Похожие туториалы