← Все статьи
Новости11 мин чтения

LangGraph AI agent: граф, checkpoint и состояние агента

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

Обложка статьи: LangGraph AI agent: граф, checkpoint и состояние агента

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

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

Границу доказательства обозначу сразу. Состав графа, инструмента и checkpoint я сверяю с официальным репозиторием и документацией LangGraph по состоянию на 2026-07-18. Как ведёт себя конкретная API-обёртка и где именно хранится checkpoint у тебя, до прямой проверки в установленном пакете неизвестно; это ограничение сценария, а не претензия к LangGraph.

Подключите AI-агентов с оплатой в рублях на provod.ai

Почему повторный запуск не равен восстановлению

Кто собирает langgraph ai agent, обычно хочет одного: чтобы агент пережил сбой. Но «пережить сбой» распадается на два разных требования. Первое: продолжить с места остановки, не повторяя уже оплаченные шаги. Второе: объяснить, на каком решении агент стоял в момент падения. Инструментальный цикл без явного состояния закрывает разве что первое, и то не всегда.

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

Моя позиция здесь не выводится из документации LangChain, а является инженерным выбором: действия агента стоит вводить в граф только вместе с явным состоянием и checkpoint. Такое требование усложняет граф: reducer, схему состояния и конфигурацию сейвера нужно продумывать заранее, а не встраивать по ходу. Но эта сложность окупается тем, что граф становится восстановимым, у сбоя появляется адрес, к которому можно вернуться, а не просто кнопка «запустить ещё раз».

Что такое минимальный граф в LangGraph

Официальный репозиторий LangGraph описывает проект как «низкоуровневый фреймворк оркестрации для построения, управления и развёртывания долгоживущих stateful-агентов». Он собран LangChain Inc., распространяется под MIT, модель исполнения вдохновлена Pregel от Google и Apache Beam, а публичный интерфейс построен по образцу библиотеки NetworkX. Это факты со страницы проекта, не мои выводы.

Исполнение идёт «суперстепами». По документации graph API, суперстеп - это одна итерация по активным узлам: узлы, работающие параллельно, попадают в один суперстеп, последовательные - в разные. Узел активируется, когда получает обновление состояния по входящему ребру или каналу. Отсюда и берётся наблюдаемость границы перехода.

Само состояние описывается схемой. StateGraph строится над пользовательской State (TypedDict, dataclass или Pydantic-модель), и у каждого ключа своя reducer-функция, которая решает, как обновление узла сливается с состоянием. Reducer по умолчанию перезаписывает значение, а кастомный может накапливать, например дописывать в список сообщений. Граф обязательно компилируется перед запуском.

Минимальный набор для проверки: одна схема состояния, один узел с моделью, один ToolNode, checkpointer и журнал переходов. Больше для фальсификации тезиса не нужно. В самом репозитории на langgraph github примеров куда больше, чем нужно для этой проверки, но их количество ничего не доказывает, доказывает только различимость границ.

Диаграмма минимального графа LangGraph: START, узел модели, ToolNode и checkpoint, соединённые обновлениями состояния

Как checkpoint сохраняет состояние, а не просто факт запуска

Checkpointer сохраняет снимок состояния графа. На уровне reference-API checkpoint он описан как содержащий значения каналов, версии каналов и отслеживание версий по узлам, и привязан к thread_id, который передаётся через {"configurable": {"thread_id": ...}}. Каждый тред ведёт свою независимую последовательность checkpoint. Так восстановление становится адресным: ты возвращаешься не «к началу», а к конкретному снимку конкретного треда.

Разные сейверы дают разную прочность, и это не деталь оформления. По той же документации, InMemorySaver не переживает рестарт процесса и годится только для тестов; SqliteSaver и AsyncSqliteSaver подходят для лёгкого или демо-сценария; PostgresSaver и AsyncPostgresSaver рассчитаны на прод-долговечность. Сериализация по умолчанию идёт через JsonPlusSerializer (ormsgpack с откатом на JSON), а для чувствительного состояния доступен EncryptedSerializer.

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

Сравнительная таблица сейверов checkpoint в LangGraph: InMemory, Sqlite, Postgres и сериализаторы

Вот скелет проверяемого графа. Это иллюстрация замысла, а не эталон: имена полей StateSnapshot и семантику отдельных параметров нужно сверять с установленной версией пакета, потому что документация в 2026 активно переезжала на docs.langchain.com и reference.langchain.com.

from typing import Annotated, TypedDict from operator import add from langgraph.graph import StateGraph, START, END from langgraph.prebuilt import ToolNode from langgraph.checkpoint.sqlite import SqliteSaver

class State(TypedDict): messages: Annotated[list, add]  # reducer накапливает, а не перезаписывает

def call\_model(state: State) -> dict: # модель с bind\_tools возвращает tool\_calls внутри AIMessage return {"messages": [model\_with\_tools.invoke(state["messages"])]}

builder = StateGraph(State) builder.add\_node("model", call\_model) builder.add\_node("tools", ToolNode([my\_tool])) builder.add\_edge(START, "model") builder.add\_edge("model", "tools") builder.add\_edge("tools", END)

with SqliteSaver.from\_conn\_string("checkpoints.db") as saver: graph = builder.compile(checkpointer=saver)  # компиляция обязательна cfg = {"configurable": {"thread\_id": "run-1"}} graph.invoke({"messages": [("user", "...")]}, cfg) snapshot = graph.get\_state(cfg)  # источник журнала переходов

Как инструмент попадает в состояние, а не мимо него

Интеграция инструмента в LangGraph двухшаговая и узловая. Модель, к которой инструменты привязаны через bind_tools(), возвращает tool_calls внутри AIMessage, но сама ничего не исполняет. Отдельный ToolNode читает tool_calls из последнего AIMessage, вызывает подходящие Python-функции и пишет результаты обратно в состояние как ToolMessage под ключом messages по умолчанию. Результат инструмента становится частью состояния, а значит, попадает в checkpoint.

Обработка ошибок инструмента - место, где легко ошибиться в ожиданиях. По умолчанию ToolNode ловит ошибки вызова, например неверные аргументы, и возвращает описательный ToolMessage вместо исключения. Но это поведение настраивается параметром handle_tool_errors (булевым, строкой, типом исключения или колбэком). Значит, что считать «восстановимым» сбоем инструмента, а что жёстким, определяет настройка конкретного графа, а не одни дефолты LangGraph.

Если нужен готовый цикл рассуждения и действия, в экосистеме есть предсобранный langgraph react agent (реализация паттерна ReAct). Он экономит код, но прячет ровно то, что мы здесь вскрываем вручную: границу между решением модели и исполнением инструмента. Для доказательства управляемости мне нужен видимый шов, поэтому в минимальном графе узлы собираются явно.

Отдельный механизм спасает частичную работу. «Pending writes» сохраняют записи узлов, которые успешно завершились, даже если соседний узел в том же суперстепе упал. Именно это позволяет возобновлённому прогону пропустить уже выполненные узлы, а не перезапускать граф целиком. Без него любой сбой откатывал бы весь прогон к старту.

Диаграмма pending writes: успешный узел сохранён, упавший сосед переисполняется при возобновлении с checkpoint

Что checkpoint не решает

Документированный паттерн восстановления такой: при сбое LangGraph использует последний сохранённый checkpoint, привязанный к thread_id, и продолжает исполнение с этой точки, а не с начала. Метод update_state() может изменить сохранённое состояние и создать новый checkpoint, после чего повторный вызов графа с этим тредом продолжает или ответвляет исполнение. Это механизм: он не превращается в автоматическую гарантию восстановления при любом крахе процесса.

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

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

Вот как читать сбой, когда он уже случился.

Что произошлоВосстановимо через checkpointЧто делать
Упал соседний узел суперстепа, свой завершёнДа, через pending writesВозобновить прогон с треда
Крах процесса на InMemorySaverНетВзять Sqlite или Postgres-сейвер
Ошибка аргументов инструментаЗависит от handle_tool_errorsНастроить параметр под нужную политику
Инструмент зафиксировал внешний эффект, потом крахНет, эффект уже committedИдемпотентность на стороне инструмента
Нет журнала переходаЗапуск воспроизведёшь, решение - нетЛогировать get_state на каждой границе

Таблица здесь не украшение: она и есть регрессионная фикстура, ради которой затевается журнал переходов. Каждая строка описывает сценарий, который граф должен различать, иначе управляемость мнимая. В справке по langgraph api все эти сценарии сводятся всего к трём методам: get_state, update_state и invoke с конфигом треда. Если сценарий из таблицы нельзя пройти этими методами по журналу, он остаётся непокрытым.

Временная шкала переходов агента с выделенной границей восстановления на последнем checkpoint

Где здесь совместимый API и зачем он отдельно от состояния

Состояние надо спроектировать до того, как в граф добавляется вызов модели, поэтому тема и раскрывается в таком порядке. Когда граф проверен и граница восстановления доказана, узел с моделью можно направить на любой совместимый endpoint, не трогая логику состояния и checkpoint.

Для команды из России это обычно вопрос доступа. provod.ai подключается как OpenAI- или Anthropic-совместимый API сменой ключа и base_url; так же его подхватывают поддерживаемые клиенты, агенты, IDE и боты, которые уже понимают такие endpoint. По этой же логике его называют provod.ai, российский аналог OpenRouter: каталог моделей собран в одной точке доступа, и узел call_model можно переключать между провайдерами Claude, GPT, Gemini, DeepSeek и Qwen, не меняя контракт состояния и checkpoint.

На практике подключение занимает одну строку в конфиге узла:

from openai import OpenAI client = OpenAI( api\_key="provod-key", base\_url="https://api.provod.ai/v1",  # тот же SDK, другой base\_url )

Стабильная мультиканальная маршрутизация помогает прогону не встать, когда один upstream-канал временно недоступен. Для агента с журналом переходов это ровно тот класс сбоя, который приятнее пережить возобновлением с checkpoint, чем полным рестартом.

Короткий FAQ

LangGraph сам гарантирует отказоустойчивость? Нет. Документация описывает возобновление с последнего checkpoint треда как механизм, а не гарантию от любого класса сбоя. Прочность зависит от выбранного сейвера.

Можно проверить восстановление на InMemorySaver? Только внутри живого процесса. Рестарт он не переживает; так он и задокументирован.

Кто решает, что ошибка инструмента «мягкая»? Параметр handle_tool_errors у ToolNode. По умолчанию ошибка вызова возвращается как ToolMessage, но политику задаёт автор графа.

Зачем журнал переходов, если есть checkpoint? Checkpoint возвращает состояние, журнал через get_state объясняет, на каком решении агент стоял. Перезапуск воспроизводит работу, но не решение.

Этот минимальный граф доказывает автономность других агентов? Нет. Карта показывает восстановимые границы только для проверенного графа и не подтверждает производительность или универсальность LangGraph.

Что дальше

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

Открытый вопрос остаётся открытым: как поведут себя именно твой сейвер, твой инструмент и твой сценарий сбоя, без прямого прогона в установленном пакете неизвестно. Собери минимальный граф, запиши переходы через get_state, урони процесс на Sqlite- или Postgres-сейвере и проверь, восстановилась ли граница. Другого доказательства, кроме этого прогона, здесь нет.

provod.ai: подключи модель к проверенному графу LangGraph по совместимому API

provod.ai — прозрачная стоимость рабочего AI-продукта

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

В одном каталоге — актуальные модели для текста и медиа: GPT от OpenAI, Claude от Anthropic, Gemini от Google, Grok от xAI, DeepSeek, Qwen, GLM, Kimi и MiniMax; для изображений — Nano Banana 2 Pro и GPT Image; для видео — последние версии Seedance, Kling, Veo и Google Omni. Также доступны модели для reasoning, поиска, документов, эмбеддингов, музыки и аудио.

База расчёта не меняется по дороге в production: официальный тариф применяется 1:1, без собственной наценки provod.ai.

Рассчитайте экономику своего сценария: форма регистрации · цены на модели · защита данных по 152-ФЗ · реквизиты для договора

Источники

  • LangGraph, официальный репозиторий, github.com/langchain-ai/langgraph, 2026-07-18 (описание, лицензия, модель исполнения).
  • LangGraph persistence, docs.langchain.com/oss/python/langgraph/persistence, 2026-07-18 (checkpoint, thread_id, границы восстановления).
  • LangGraph graph API, docs.langchain.com/oss/python/langgraph/graph-api, 2026-07-18 (суперстеп, State, reducer).
  • LangGraph checkpoints, reference.langchain.com/python/langgraph/checkpoints, 2026-07-18 (сейверы, сериализаторы, pending writes).
  • LangGraph ToolNode, reference.langchain.com/python/langgraph.prebuilt/tool_node/ToolNode, 2026-07-18 (исполнение и обработка ошибок инструмента).