
LangChain — одна из самых популярных библиотек для создания приложений на больших языковых моделях. Но в классическом виде LangChain строит линейные цепочки: один шаг за другим, без ветвления и возврата. Реальные AI-агенты на базе LangGraph — особенно те, что принимают решения, используют инструменты и исправляют ошибки — требуют циклов, состояний и условных переходов. Для этого и был создан LangGraph.
В этой статье разбираем, как устроен LangGraph, чем он отличается от обычных цепочек LangChain, и как на нём строить агентов с контролируемой логикой. Мы не будем отвлекаться на сторонние темы — только архитектура, код и практические примеры.
Что такое LangGraph и зачем он нужен
LangGraph — это библиотека от команды LangChain, которая добавляет в экосистему графовую модель выполнения. Вместо последовательности шагов (chain) вы определяете граф: узлы (nodes) — это функции, рёбра (edges) — переходы между ними. Граф может содержать циклы, что принципиально важно для агентов, которые должны перепроверять результаты, возвращаться к предыдущему шагу или запускать инструменты несколько раз.
Ключевое различие: в обычном LangChain каждый вызов chain — это детерминированный поток. В LangGraph вы управляете потоком сами, а состояние передаётся между узлами и может изменяться в процессе.
Основные концепции LangGraph
StateGraph
StateGraph — главный класс для описания графа. Он параметризуется типом состояния (state). Состояние — это объект, который передаётся между узлами и хранит все данные, которые агент накопил к текущему моменту: сообщения, результаты вызовов инструментов, флаги завершения.
python
from typing import TypedDict, Annotated
from langgraph.graph import StateGraph, END
class AgentState(TypedDict):
messages: list
next_step: str
Узлы (nodes)
Узел — это функция, которая принимает текущее состояние и возвращает обновлённое состояние. Каждый узел должен быть чистой функцией: без побочных эффектов, только модификация состояния.
python
def call_model(state: AgentState):
# обращение к LLM
response = llm.invoke(state[“messages”])
return {“messages”: state[“messages”] + [response]}
Узлы могут быть любыми: вызов LLM, запуск инструмента, проверка условия, форматирование вывода.
Рёбра (edges)
Рёбра определяют, в какой узел переходит выполнение после завершения текущего. Бывают обычные (безусловные) и условные. Условное ребро — это функция, которая смотрит на текущее состояние и возвращает имя следующего узла.
python
def decide_next(state: AgentState):
if “tool_call” in state[“messages”][-1].content:
return “tools”
return “end”
Это позволяет агентам принимать решения: если модель решила вызвать инструмент — перейти к узлу выполнения инструмента; если ответ готов — завершить.
Циклы и завершение
Граф может содержать циклы: например, узел «инструмент» может возвращать результат в узел «модель», чтобы модель сформировала окончательный ответ. Завершение графа обозначается специальным узлом END. Если граф доходит до END, выполнение останавливается.
Как строится типичный агент на LangGraph
Рассмотрим простого агента, который умеет вызывать инструмент для получения данных. Граф будет состоять из трёх узлов: вызов модели, выполнение инструмента, проверка необходимости продолжать.
Шаг 1. Определяем состояние
python
from typing import TypedDict, Sequence
from langchain_core.messages import BaseMessage
class AgentState(TypedDict):
messages: Sequence[BaseMessage]
Шаг 2. Создаём граф
python
from langgraph.graph import StateGraph, END
graph = StateGraph(AgentState)
Шаг 3. Добавляем узлы
python
def call_model(state):
response = llm.invoke(state[“messages”])
return {“messages”: [response]}
def execute_tool(state):
# парсим вызов инструмента и выполняем
tool_result = some_tool.invoke(…)
return {“messages”: [tool_result]}
graph.add_node(“model”, call_model)
graph.add_node(“tools”, execute_tool)
Шаг 4. Определяем рёбра
python
def should_continue(state):
last_message = state[“messages”][-1]
if last_message.additional_kwargs.get(“tool_calls”):
return “tools”
return END
graph.set_entry_point(“model”)
graph.add_conditional_edges(“model”, should_continue)
graph.add_edge(“tools”, “model”)
Теперь граф работает циклически: модель → проверка → если нужен инструмент → выполнение → снова модель. Когда модель даёт окончательный ответ без вызова инструмента — граф завершается.
Шаг 5. Компиляция и запуск
python
app = graph.compile()
result = app.invoke({“messages”: [HumanMessage(“Какой курс доллара?”)]})
State и checkpointing: почему это важно
LangGraph автоматически сохраняет состояние на каждом шаге — это называется checkpointing. Если выполнение прервалось (ошибка, тайм-аут, перезапуск), граф можно возобновить с последнего сохранённого состояния. Это критично для долгих агентских сессий.
Checkpointing позволяет:
– возвращаться к предыдущим шагам для отладки
– прерывать и возобновлять агентов
– строить интерфейсы с ручным подтверждением (human-in-the-loop)
Сравнение LangGraph и линейных цепочек LangChain
| Параметр | LangChain (LCEL) | LangGraph |
|---|---|---|
| Тип выполнения | Линейный, детерминированный | Графовый, с циклами и ветвлениями |
| Управление состоянием | Переменные через RunnablePassthrough | Явное состояние через TypedDict |
| Поддержка циклов | Нет | Да, через условные рёбра |
| Checkpointing | Нет | Встроенный, на каждом шаге |
| Сложность | Низкая, для простых цепочек | Средняя, для агентов |
Когда LangGraph избыточен
LangGraph не заменяет LangChain Expressions Language (LCEL). Для простых линейных сценариев, где агент делает ровно один вызов LLM и возвращает ответ, граф избыточен. Пример: генерация краткого текста без инструментов — используйте LCEL.
Граф не выполняет параллельные узлы из коробки — для этого нужна отдельная логика с asyncio или ThreadPoolExecutor.
Библиотека всё ещё развивается: API может меняться между версиями. Следите за changelog в репозитории LangChain.
Ограничения и подводные камни
При работе с LangGraph учитывайте:
– Состояние хранится в памяти. Для долгих сессий используйте внешние хранилища (SQLite, Redis) через BaseCheckpointSaver.
– Условные рёбра не должны быть слишком сложными — выносите логику в отдельные функции.
– При большом количестве узлов граф становится трудно отлаживать. Используйте LangSmith для трассировки.
– Не все модели поддерживают tool_calls. Для старых моделей (GPT-3.5 до обновления) парсите вызовы вручную.
Практические выводы
LangGraph подходит для сценариев, где агент должен многократно обращаться к инструментам, исправлять ошибки, запрашивать уточнения или работать с длинными диалогами. Если ваш агент делает ровно один вызов LLM и возвращает ответ — граф избыточен.
Попробуйте начать с простого графа из двух узлов и добавить условное ребро. Постепенно усложняйте: добавляйте инструменты, проверки, человеческое подтверждение. LangGraph даёт контроль, которого не хватало в линейных цепочках.
Полезные источники для дальнейшего изучения: официальная документация LangGraph, репозиторий LangChain с примерами агентов и раздел «Agent architectures» в блоге LangChain.
