Checkpointer и память сессии | Курс LangChain Agents урок 2
Цель урока: понять, как 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, агент:
- при первом вызове - создаёт новый thread и сохраняет checkpoint
- при следующих вызовах - загружает предыдущее состояние и продолжает с него
Именно так агент "помнит" предыдущие сообщения разговора.
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:
- Спросите курс доллара к рублю
- Уточните: "Сколько это в евро?" (агент должен понять, что речь о сумме из предыдущего ответа)
- Спросите: "А какую сумму я назвал в самом начале?" (агент должен вспомнить из истории)
Затем запустите тот же третий вопрос с другим thread_id и убедитесь,
что агент не помнит ничего из первой сессии.
Подписывайтесь на мой Telegram канал
Если вам нужен ментор и вы хотите научиться разрабатывать AI агентов, пишите, обсудим условия
Авторизуйтесь, чтобы оставить комментарий.
Нет комментариев.
Тут может быть ваша реклама
Пишите info@aisferaic.ru