LangGraph на практике: агенты как граф состояний
LangGraph превращает цепочку вызовов LLM в управляемый граф с циклами, ветвлениями и памятью. Разбираем, чем он отличается от простых цепочек LangChain и когда стоит его брать.

LangChain годами предлагал строить приложения на LLM как линейные цепочки: промпт — модель — парсер — следующий шаг. Как только появлялась задача с циклом, повтором при ошибке или ветвлением по решению модели, линейная цепочка ломалась. LangGraph — библиотека от той же команды — переносит логику в граф состояний: узлы делают работу, рёбра решают, куда идти дальше, а общее состояние живёт между шагами. Первый стабильный выпуск библиотека получила в 2024 году, и к концу года она стала основным способом собирать агентов в экосистеме LangChain.
Что такое граф состояний в контексте агентов
Идея простая. У вас есть объект состояния — обычно словарь или типизированная структура. Каждый узел графа получает состояние на входе, что-то с ним делает (вызывает модель, лезет в базу, дёргает инструмент) и возвращает обновление. Рёбра описывают, какой узел выполняется следующим. Рёбра бывают обычными (всегда из A в B) и условными: функция смотрит на состояние и решает маршрут.
Ключевое отличие от линейной цепочки — циклы. Агент может вызвать инструмент, посмотреть на результат, решить, что данных мало, и вызвать инструмент снова. В графе это выражается ребром, которое возвращает управление обратно к узлу модели, пока не сработает условие выхода. Именно циклы делают возможным паттерн ReAct*, где модель чередует рассуждение и действия.
Цепочка отвечает на вопрос «что делать по порядку». Граф отвечает на вопрос «что делать дальше в зависимости от того, что получилось». Для агентов важнее второе.
Состояние и редьюсеры
Состояние в LangGraph описывается схемой. В Python это чаще всего TypedDict с аннотациями полей. Для полей, которые накапливаются (например, история сообщений), задают редьюсер — функцию, которая объединяет старое значение с новым. Для сообщений это обычно добавление в список, а не перезапись. Без редьюсера каждый узел затирал бы предыдущее значение поля.
from typing import Annotated, TypedDict
from langgraph.graph import StateGraph, END
from langgraph.graph.message import add_messages
class State(TypedDict):
messages: Annotated[list, add_messages]
def call_model(state: State):
response = llm.invoke(state["messages"])
return {"messages": [response]}
def should_continue(state: State):
last = state["messages"][-1]
return "tools" if last.tool_calls else END
graph = StateGraph(State)
graph.add_node("model", call_model)
graph.add_node("tools", tool_node)
graph.set_entry_point("model")
graph.add_conditional_edges("model", should_continue)
graph.add_edge("tools", "model")
app = graph.compile()
Здесь узел model вызывает LLM, условное ребро проверяет, запросила ли модель инструмент, и либо уходит в tools, либо завершает граф. После инструментов управление возвращается модели — это и есть цикл. Такой скелет покрывает большинство агентов, а дальше вы наращиваете узлы под свою задачу.
Чекпоинты, память и человек в контуре
Отдельная сильная сторона — чекпоинтеры. Если при компиляции графа передать хранилище (в памяти, SQLite, Postgres), LangGraph сохраняет состояние после каждого шага. Из этого вырастает сразу несколько возможностей.
- Постоянная память диалога. Каждому разговору присваивается идентификатор потока (
thread_id), и граф подхватывает историю при следующем запросе. - Human-in-the-loop. Граф можно поставить на паузу перед узлом — например, перед отправкой платежа или письма — дождаться подтверждения человека и продолжить с того же места.
- Возврат назад. Поскольку сохранён каждый шаг, можно отмотать состояние к прошлому чекпоинту и пойти по другой ветке. Полезно для отладки и для сценариев «а что, если».
Без графа с чекпоинтами все эти вещи приходится собирать вручную, и код быстро превращается в клубок условий и глобальных переменных.
LangGraph против альтернатив
LangGraph — не единственный способ строить агентов. Вот как он соотносится с ближайшими подходами. Возможности библиотек меняются, поэтому детали сверяйте с их документацией.
| Критерий | LangGraph | Линейные цепочки LangChain (LCEL) | Мультиагентные фреймворки (CrewAI, AutoGen) |
|---|---|---|---|
| Модель управления | Явный граф состояний с циклами | Линейный или ветвящийся конвейер | Роли агентов, обмен сообщениями |
| Циклы и повторы | Нативно, через рёбра | Только обходными путями | Через диалог агентов |
| Персистентное состояние | Встроенные чекпоинтеры | Нет из коробки | Зависит от фреймворка |
| Human-in-the-loop | Прерывания на узлах | Реализуется вручную | Ограниченно |
| Порог входа | Средний: надо думать графом | Низкий | Низкий для типовых ролей |
| Контроль над потоком | Высокий, всё явно | Средний | Ниже: логика скрыта в абстракции ролей |
Грубое правило: чем больше вам нужен точный контроль над тем, кто и когда выполняется, тем сильнее LangGraph выигрывает. Чем ближе задача к «поставить пару агентов-ролей и дать им поговорить», тем комфортнее в CrewAI или AutoGen.
Кому это пригодится и кому нет
LangGraph оправдан, когда контроль над потоком важнее скорости прототипа.
- Продуктовые агенты в проде. Поддержка, которая ведёт клиента по сценарию с ветвлениями, откатами и передачей оператору, — почти учебный случай для графа.
- Пайплайны с проверкой качества. Узел генерирует ответ, узел-критик его проверяет, и при неудаче граф возвращает управление на доработку — до N попыток.
- RAG** со сложной логикой. Когда после первого поиска нужно решить, достаточно ли документов, переформулировать запрос и искать снова.
- Сценарии с обязательным подтверждением человека — финансовые операции, отправка наружу, необратимые действия.
А вот когда граф — избыточная сложность:
- Один вызов модели с промптом. Классификация, суммаризация, извлечение полей — линейная цепочка короче и понятнее.
- Быстрый прототип на выходные. Пока вы не уверены в форме решения, граф заставляет заранее описывать состояние и рёбра, которые вы пять раз перепишете.
- Команда без опыта в LangChain. Порог входа выше, чем у простого скрипта с вызовом API напрямую, и это надо честно закладывать в сроки.
Как начать без боли
- Соберите сначала минимальный цикл модель — инструменты — модель, как в примере выше, и убедитесь, что он работает.
- Добавьте чекпоинтер в памяти и проверьте, что состояние переживает несколько запросов в рамках одного
thread_id. - Только потом усложняйте: добавляйте узлы-критики, условные рёбра, прерывания.
- Держите состояние плоским и понятным. Раздувшийся словарь состояния — первый признак того, что часть логики надо вынести в отдельные узлы.
- Для визуальной отладки используйте встроенный экспорт графа в диаграмму — видно, куда на самом деле уходят рёбра.
LangGraph не делает агента умнее — он делает его управляемым. Если ваша боль в том, что агент ведёт себя непредсказуемо и код невозможно поддерживать, граф состояний — прямой ответ. Если боль в качестве ответов модели, никакая оркестрация её не решит.
* ReAct — паттерн, при котором модель чередует шаги рассуждения (reasoning) и действия (acting), вызывая инструменты и осмысляя их результат перед следующим шагом.
** RAG (retrieval-augmented generation) — подход, при котором модель перед ответом подтягивает релевантные документы из внешнего хранилища и опирается на них, а не только на веса.
Prompt-инженер: Идеальные запросы для Midjourney, ChatGPT и других моделей.
Спросить за 15 ₽Источники: LangGraph — официальная документация
Частые вопросы
LangGraph заменяет LangChain или дополняет его?
Нужно ли платить за LangGraph?
Обязательно ли использовать чекпоинтер?
Можно ли собрать мультиагентную систему на LangGraph?
Стоит ли переписывать существующую линейную цепочку на граф?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.