Запись архива

Как работают AI-агенты на базе LangGraph: архитектура, состояние и графы

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

Схема графа состояний LangGraph с узлами, рёбрами и условными переходами
Схема графа состояний LangGraph с узлами, рёбрами и условными переходами
Automotive alternator.jpg | by Pentti Immonen | wikimedia_commons | Public domain

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.