Что такое LangGraph и зачем он нужен?

Краткий тезис

LangGraph — это фреймворк для построения циклических графов вычислений в экосистеме LangChain. В отличие от линейных цепочек LangChain (DAG), LangGraph позволяет создавать агентов с петлями, ветвлениями и состоянием, общим для всех узлов. Он нужен там, где требуется сложное принятие решений: reasoning|мультишаговый reasoning, инструменты, обратная связь и динамическое изменение плана — то есть для production-grade агентов и RAG|Agentic RAG.


1. Термин: LangGraph

LangGraph — это библиотека, расширяющая LangChain для создания направленных графов с циклами (cyclic graphs). Она разработана компанией LangChain Inc. и тесно интегрирована с экосистемой LangChain (модели, инструменты, память). Основная идея: вы определяете узлы (nodes — функции/LLM вызовы) и рёбра (edges — связи), а LangGraph управляет потоком данных и состоянием. Ключевое отличие от обычного LangChain — возможность циклов: узел может передавать управление обратно предыдущему узлу или самому себе, что необходимо для итеративных процессов.

На практике LangGraph позволяет описывать поведение агента как граф: каждый шаг — это узел, а решение, куда перейти дальше, принимается либо статически, либо через conditional edges (edges|условные рёбра). Это даёт гибкость, недостижимую в линейных цепочках.


2. Отличие от LangChain: DAG vs Cyclic Graph

LangChain по умолчанию работает с DAG (Directed Acyclic Graph) — направленный ациклический граф. Вы задаёте последовательность шагов, и выполнение идёт вперёд без возврата. Для простых RAG (Retrieval-Augmented Generation) или цепочек «запрос → поиск → ответ» этого достаточно. Но для агентов, которые должны:

  • вызывать инструменты,
  • оценивать результаты,
  • при необходимости переформулировать запрос или повторить действие,

нужны циклы. LangGraph разрешает циклы за счёт того, что граф может содержать обратные рёбра.

Сравнение

ХарактеристикаLangChainLangGraph
Тип графаDAG (ациклический)Направленный граф с циклами
Поток выполненияЛинейный, предсказуемыйВетвистый, может зацикливаться
СостояниеПередаётся через цепочкуЕдиное состояние (shared state)
Гибкость принятия решенийНизкая (только статические маршруты)Высокая (conditional edges)
Сложность разработкиНизкаяСредняя
Production-агентыОграниченноДа, основное применение

LangGraph не заменяет LangChain, а дополняет: он использует те же модели, промпты и инструменты, но даёт новый уровень контроля над логикой агента.


3. Ключевые концепции: Nodes, Edges, State

Разберём три базовых строительных блока LangGraph.

  • Nodes (узлы) — это вычислительные единицы. Каждый узел получает текущее состояние графа, выполняет некоторую логику (например, вызов LLM, запуск инструмента, проверку условия) и возвращает обновлённое состояние. Узлы могут быть:

    • function (Python-функция),
    • Runnable (объект LangChain),
    • моделью (LLM),
    • цепочкой LangChain.
  • Edges (рёбра) — это связи между узлами. Они определяют, в каком порядке выполняются узлы. Рёбра бывают:

    • обычные: после узла A всегда идёт узел B;
    • условные (conditional edges): после узла A решение, какой узел выполнить следующим, принимается на основе текущего состояния (например, если LLM вернул tool_call, то идти к узлу инструмента, иначе к узлу ответа).
  • State (состояние) — это структура данных, которая передаётся по графу и доступна всем узлам. Оно обновляется по мере прохождения. Каждый узел может читать и изменять состояние. Состояние задаётся в виде TypedDict или Pydantic BaseModel. LangGraph автоматически обрабатывает обновления состояния (append, overwrite и т.д.).


4. Как работает State (Shared Memory между узлами)

State — сердце LangGraph. Это единое хранилище, к которому имеют доступ все узлы графа. Оно гарантирует, что информация не теряется между шагами.

Пример определения состояния:

from typing import TypedDict, List

class AgentState(TypedDict):
    messages: List[dict]  # история диалога
    next_action: str      # действие, которое нужно выполнить
    result: str           # результат последнего шага

В узлах вы можете читать и модифицировать поля состояния. LangGraph поддерживает разные режимы обновления:

  • Overwrite — заменить поле новым значением.
  • Append — добавить элемент в список (полезно для истории сообщений).
  • Merge — слить словари.

Пример узла, добавляющего сообщение:

def call_model(state: AgentState) -> dict:
    # state["messages"] – список сообщений
    response = llm.invoke(state["messages"])
    return {"messages": [response]}  # append по умолчанию для списков

Благодаря единому состоянию граф может «помнить» предыдущие действия и результаты, что необходимо для циклов: например, агент может вызывать инструмент несколько раз, накапливая наблюдения в state.


5. Conditional Edges — ветвление в графе

Conditional Edges позволяют динамически выбирать следующий узел на основе текущего состояния. Это главный механизм для реализации принятия решений у агента.

Условное ребро задаётся функцией, которая принимает состояние и возвращает имя узла, к которому перейти.

Пример:

def router(state: AgentState) -> str:
    last_message = state["messages"][-1]
    if "tool_call" in last_message.get("additional_kwargs", {}):
        return "call_tool"
    else:
        return "generate_answer"

graph.add_conditional_edges(
    source="agent",
    condition=router,
    path_map={"call_tool": "execute_tool", "generate_answer": "final_output"}
)

Здесь после узла agent выполняется router, и в зависимости от того, запросил ли LLM вызов инструмента, граф идёт либо на execute_tool, либо на final_output. Из execute_tool может быть ребро обратно в agent (цикл). Таким образом, агент может вызывать несколько инструментов, пока не получит достаточно информации для ответа.

Conditional Edges превращают граф из статического конвейера в настоящую систему с логикой.


6. Пример: Agentic RAG на LangGraph

Agentic RAG (агентный RAG) — классический сценарий, где LangGraph оправдывает себя. Обычный RAG: запрос → поиск → ответ. Агентный RAG: агент решает, нужно ли уточнить запрос, искать ещё один источник, переписать запрос или сразу ответить.

Схема графа:

  1. Узел analyze_query: LLM определяет намерение (уточнить, искать, ответить).
  2. Условное ребро:
    • Если «искать» → узел retrieve (выполняет поиск по векторной БД).
    • Если «уточнить» → узел clarify (запрос к пользователю).
    • Если «ответить» → узел answer (генерация на основе имеющегося контекста).
  3. Узел retrieve: добавляет найденные чанки в state.
  4. Условное ребро после retrieve: если чанков достаточно → answer, иначе → analyze_query (цикл, возможно уточнить запрос).
  5. Узел answer: LLM генерирует финальный ответ, используя state.

Такой граф позволяет агенту самостоятельно решать, когда остановиться, и не тратить ресурсы на поиск, если ответ уже есть в истории.


7. Код: простой пример графа с ветвлением

Ниже — минимальный рабочий пример на основе LangGraph (предполагается, что используется Python 3.10+ и установлены langgraph, langchain-openai).

from langgraph.graph import StateGraph, END
from typing import TypedDict, List

# 1. Определяем состояние
class GraphState(TypedDict):
    input: str
    question: str
    context: List[str]
    output: str

# 2. Определяем узлы
def classify(state: GraphState) -> dict:
    # Допустим, мы смотрим, есть ли слово "search" в вопросе
    if "search" in state["input"].lower():
        return {"question": state["input"], "context": []}
    else:
        return {"output": "No search needed, just answer directly."}

def retrieve(state: GraphState) -> dict:
    # Заглушка поиска
    docs = ["doc1 about search", "doc2 about query"]
    return {"context": docs}

def generate(state: GraphState) -> dict:
    context = "\n".join(state["context"])
    # В реальности здесь вызов LLM
    state["output"] = f"Answer based on: {context}"
    return {"output": state["output"]}

# 3. Строим граф
builder = StateGraph(GraphState)

builder.add_node("classify", classify)
builder.add_node("retrieve", retrieve)
builder.add_node("generate", generate)

builder.set_entry_point("classify")

# Условное ребро из classify
def router(state: GraphState) -> str:
    if "context" in state and not state["context"]:
        return "retrieve"
    else:
        return "generate"

builder.add_conditional_edges("classify", router, {
    "retrieve": "retrieve",
    "generate": "generate"
})

# После retrieve идём в generate
builder.add_edge("retrieve", "generate")
builder.add_edge("generate", END)

# Компилируем
graph = builder.compile()

# Запуск
result = graph.invoke({"input": "I need to search something"})
print(result["output"])

Этот пример демонстрирует: узел classify решает, нужно ли искать, и направляет либо на retrieve, либо на generate. Обратите внимание, что после retrieve граф идёт в generate, а затем завершается. Для полноценного агента с циклами нужно добавить обратное ребро из generate в classify или другой узел.


8. Сравнение с альтернативами (AutoGen, CrewAI)

LangGraph — не единственная библиотека для построения агентов. Основные конкуренты:

КритерийLangGraphAutoGen (Microsoft)CrewAI
Модель вычисленийГраф с циклами и состояниемАсинхронный обмен сообщениями между агентамиИерархия ролей (Crew, Agent, Task)
Управление потокомПрограммное (условные рёбра)Через события и обратные вызовыСтатическое (задачи в порядке)
СостояниеЕдиное shared stateКаждый агент имеет своё состояниеState передаётся через задачи
Интеграция с LangChainРоднаяСлабая (можно через LLM)Средняя (есть adapter)
ГибкостьОчень высокаяВысокая (много паттернов)Средняя (ориентир на роли)
Порог входаСреднийВысокий (асинхронность)Низкий
Production readinessВысокая (LangSmith, мониторинг)СредняяСредняя

Когда выбирать LangGraph:

  • Нужна тонкая настройка потока (conditional edges, циклы).
  • Уже используете LangChain (легко интегрировать).
  • Требуется единое состояние для всех шагов.
  • Планируете использовать LangSmith для отладки и мониторинга.

Когда альтернативы:

  • AutoGen — если нужно много агентов, общающихся друг с другом (multi-agent conversation).
  • CrewAI — если хотите быстро прототипировать ролевую команду без программирования сложной логики.

9. Когда стоит использовать LangGraph, а когда нет

Используйте LangGraph, если:

  • Ваш агент должен выполнять последовательность шагов с принятием решений (выбор инструмента, повторный поиск, уточнение).
  • Нужны циклы: например, итеративное улучшение ответа или повторные попытки при ошибках.
  • Требуется единое состояние, доступное на всех этапах.
  • Вы строите production-систему и хотите мониторить выполнение графа через LangSmith.
  • Нужна гибкость в маршрутизации (conditional edges).

Не используйте LangGraph, если:

  • Ваша логика укладывается в линейную цепочку (простой RAG, один LLM вызов).
  • Нужна простая координация нескольких независимых агентов (лучше AutoGen).
  • У вас очень простой сценарий «запрос → ответ» — хватит обычного LangChain или даже прямого вызова API.
  • Команда не знакома с графами и хочет минимизировать сложность.

10. Интеграция с LangChain и экосистема

LangGraph не работает изолированно. Он использует:

  • LangChain — промпты, модели, инструменты, цепочки.
  • LangSmith — отладка, трейсинг, мониторинг графа (вы можете видеть, как данные проходят по узлам).
  • LangServe — деплой графа как REST API.
  • LangHub (опционально) — шаблоны графов.

Пример интеграции: в узле можно использовать любой Runnable из LangChain:

from langchain.chains import LLMChain
from langchain.prompts import ChatPromptTemplate

prompt = ChatPromptTemplate.from_messages([("system", "You are a helpful assistant."), ("user", "{input}")])
chain = prompt | llm

def node_function(state):
    response = chain.invoke({"input": state["question"]})
    return {"output": response}

LangGraph также поддерживает параллельные узлы и fan-out/fan-in (разветвление и схождение), что полезно для выполнения нескольких инструментов одновременно.


Пет-проект для закрепления

Задача: Реализовать агента, который отвечает на вопросы по документации LangGraph, используя Agentic RAG с двумя источниками (векторная БД и поиск в интернете). Агент должен решать, какой источник использовать, а при необходимости комбинировать.

Инструменты:

  • Python 3.10+, langgraph, langchain-openai, chromadb (векторная БД), duckduckgo-search (инструмент поиска).
  • LangSmith (опционально для отладки).

Шаги:

  1. Создать векторное хранилище с документацией LangGraph (например, 20 страниц).
  2. Определить граф с узлами:
    • router — LLM решает, нужна ли внешняя информация.
    • vector_search — поиск в Chroma.
    • web_search — поиск в DuckDuckGo.
    • answer — генерация ответа на основе контекста.
  3. Добавить conditional edges для выбора источника.
  4. Реализовать цикл: если после поиска контекста недостаточно, идти в другой источник.
  5. Протестировать на вопросах из документации и общих вопросах о LangGraph.

Ожидаемый результат: Рабочий граф, который корректно выбирает источник и даёт ответ. Вы сможете визуализировать граф через graph.draw_mermaid() и отслеживать шаги в LangSmith. Проект покажет понимание state, conditional edges и циклов.


Связь с другими вопросами

ВопросТема
41Что такое AI-агент и в чём его отличие от цепочки вызовов LLM?
43Как работает AutoGen и для каких сценариев он подходит?
44Что такое CrewAI и чем отличается от LangGraph?
45Как реализовать Agentic RAG с помощью графов?
30Что такое LangChain и какие у него основные компоненты?
31В чём разница между Chain и Agent в LangChain?

Навигация