Блог AST-SoftPro
LangGraph: как строить агентов с циклами и ветвлением
Введение в LangGraph и построение AI-агентов
LangGraph — это фреймворк для создания сложных AI-агентов на основе графовой структуры. В отличие от линейных цепочек обработки (как в LangChain), где действия выполняются последовательно, LangGraph позволяет моделировать поведение агента через граф переходов, где узлы представляют функции или состояния, а рёбра — правила перехода между ними.
Такой подход особенно полезен при решении задач с циклической логикой и ветвлением: проверка условий, повторные попытки, сбор информации по частям. Например, агент может: проверить доступ к ресурсу → получить данные → обработать результат → повторить запрос при ошибке → отправить финальный ответ.
В этой статье рассмотрим, как использовать LangGraph для построения агентов с:
-
условными переходами (ветвление)
-
циклами обратной связи
-
проверкой результатов выполнения шагов
-
гибким управлением потоком через состояние и сообщения
Мы сосредоточимся на технических деталях реализации: структурах StateGraph, использовании messages/events, определении правил перехода, обработке ошибок. Цель — понять механизм управления сложным поведением агента без усложнения логики.
Основные концепции LangGraph
1. Граф состояний (StateGraph) как основа
В LangGraph поведение агента описывается через граф состояния, где:
-
Узлы — это вызовы функций (LLM, API, действия)
-
Рёбра — правила перехода между узлами на основе текущего state и результатов выполнения
Каждый шаг может возвращать не только ответ модели, но и метаданные: статус выполнения (success, retry, error) или новые данные для проверки.
Пример базового графа:
from langgraph.graph import StateGraph
from typing import TypedDict
class AgentState(TypedDict):
input_data: dict
result: str | None = None
step_count: int = 0
def validate_input(state: AgentState) -> tuple:
if not state['input_data'].get('required_field'):
return 'error', {'message': 'Missing required field'}
elif len(str(state['input_data']['value'])) > 100:
return 'retry', {'message': 'Input too long, retrying with subset'}
# Если всё в порядке — продолжить дальше
return 'continue', state
Здесь validate_input возвращает тип перехода (error, retry, continue) и возможные данные для следующего шага. Это позволяет строить не линейные, а ветвящиеся потоки.
2. Управление переходами через условные правила
LangGraph поддерживает два способа определения путей:
-
Через явное перечисление всех возможных состояний (edges = {})
-
Через динамическое определение правил на основе результата выполнения (рекомендуется для сложных случаев)
Пример: динамический выбор следующего шага
from langgraph.checkpoint import MemorySaver
from langgraph.states import StateGraph
from typing import Literal, TypedDict
import operator
class AppState(TypedDict):
query: str = ''
results: list[str] | None = None
error: bool = False
stage: Literal['start', 'searching', 'processing', 'done'] = 'start'
def route_query(state: AppState) -> tuple:
"""
Определяет следующий шаг на основе текущего состояния.
"""
if state["stage"] == "start":
return ("search", {})
elif state.get("results") and not state.get("error"): # Успешный поиск
return ("process_results", {})
elif state.get("error"): # Ошибка — повторить запрос
return ("retry_query", {})
else:
raise ValueError(f"Unknown stage: {state['stage']}")
Здесь route_query анализирует текущее состояние и возвращает имя следующего узла (search, process_results) вместе с данными для передачи. Это позволяет реализовать логическое ветвление без жёсткой привязки к порядку.
3. Реализация циклов обратной связи
Одной из ключевых возможностей LangGraph является возможность создания циклических процессов, где агент повторяет действия до достижения условия.
Пример: сбор данных по частям (например, загрузка большого файла)
def fetch_chunk(state: AgentState) -> tuple[Literal["continue", "done"], dict]:
chunk = download_next_part(state['input_data']['file_id'])
if not chunk:
return 'done', state
# Обновляем состояние — добавляем новый фрагмент в result
updated_state = {
**state,
'result': f"{state['result']} {chunk.text}"
}
return "continue", updated_state
def complete_task(state: AgentState) -> tuple:
if len(str(state['result'])) > 500:
return "success", state
else:
# Не хватает данных — повторить запрос на следующий фрагмент
raise RuntimeError("Data not fully collected yet. Resuming from last point.")
В этом примере fetch_chunk работает в цикле: пока не загружен весь файл (или достигнут лимит), агент продолжает вызывать этот шаг. При достижении условия (len(result) > 501) — завершается успешно.
Важно: LangGraph сам управляет выполнением цикла, если вы возвращаете 'continue'. Это предотвращает бесконечные петли при неправильной логике.
4. Обработка ошибок и повторные попытки (retry logic)
LangGraph поддерживает механизм retry через специальное значение в результатах шагов ("retry", data). Это позволяет реализовать стратегию повтора без необходимости вручную управлять счётчиком попыток.
Пример: безопасный вызов API с ограничениями по частоте
def call_vulnerable_api(state: AgentState) -> tuple:
try:
response = api_request(state['input_data'])
return 'success', {"result": response.data}
except RateLimitError:
# Ограничение частоты — повтор через 5 секунд
import time
time.sleep(5)
return "retry", state
except Exception as e:
print(f"Unexpected error: {e}\")
raise RuntimeError(f"Failed after retry logic. Error: {str(e)}") from e
Когда call_vulnerable_api возвращает 'retry', LangGraph автоматически повторит этот узел, пока не будет возвращён 'success'. Это работает только при использовании checkpointing (см. ниже).
⚠️ Без MemorySaver или аналогичного механизма состояние теряется между попытками — важно всегда использовать сохранение состояния.
Хранение и восстановление состояния (checkpointing)
Для работы с циклами, повторными попытками и ветвлением необходимо сохранять текущее состояние агента. LangGraph предоставляет встроенный механизм MemorySaver:
from langgraph.checkpoint import MemorySaver memory_saver = MemorySaver() graph = StateGraph(AgentState).add_conditional_edge(route_query, graph) app = graph.compile(checkpointer=memory_saver)
Это позволяет:
-
Восстановить процесс после прерывания (например, при тайм-ауте)
-
Продолжить с того же места после ошибки или повторной попытки
-
Реализовать долгосрочные агенты, которые работают часами/днями
Пример использования checkpoint: сбор данных в несколько этапов — каждый шаг сохраняет состояние. При перезапуске приложение начинает с последнего сохранённого состояния.
Практический пример: агент для обработки пользовательского запроса
Рассмотрим задачу:
Пользователь отправляет запрос, содержащий имя файла и тип операции (upload, download). Агент должен: 1. Проверить наличие файла (если download) — если нет, вернуть ошибку 2. Если файл есть — загрузить его по частям до достижения нужного размера или полного завершения 3. При ошибке загрузки — повторить попытку (до 5 раз) 4. После успешной загрузки — отправить результат пользователю
Структура состояния:
class ProcessingState(TypedDict):
user_id: str
file_name: str
operation_type: Literal["upload", "download"] = "download"
current_chunk: int = 0
total_size_required: int | None = None
downloaded_size: int = 0
error_count: int = 0
status: Literal[
"checking_existence",
"downloading_part",
"processing_finished",
"error" "] = "checking_existence"
Шаги агента:
-
check_file_exists — проверяет, есть ли файл на сервере
-
start_download_loop — запускает цикл загрузки по частям (до 5 попыток)
-
aggregate_results_and_send — собирает и отправляет финальный ответ
-
При ошибке — переходит в состояние error, где можно отправить уведомление о сбое
Реализация цикла с ограничением:
def download_part(state: ProcessingState) -> tuple[Literal, dict]:
if state['error_count'] >= 5:
return "finished_with_error", state
try:
chunk = fetch_file_chunk(state['file_name'], offset=state['current_chunk'])
downloaded_size += len(chunk.text)
# Обновляем состояние — добавляем новый фрагмент в result (если нужно)
updated_state = {
**state,
"downloaded_size": downloaded_size
}
if chunk.is_last:
return "finished_successfully", updated_state
else:
return "continue_downloading", updated_state
except Exception as e:
state["error_count"] += 1
print(f"Retry {state['error_count']}: {e}")
return "retry_download", state
В этом случае download_part возвращает 'continue_downloading', и LangGraph повторит шаг, пока не будет достигнут лимит попыток.
Заключение: ключевые преимущества подхода на основе графов
Использование LangGraph для построения AI-агентов даёт следующие преимущества: | Преимущество | Описание | |-------------|--------| | Гибкость потока | Возможность ветвления, циклов и повторных попыток без изменения кода основных узлов | | Ясность логики | Графовая структура делает поведение агента прозрачным — видно все возможные пути | | Устойчивость к ошибкам | Механизм retry и checkpointing обеспечивает надёжное восстановление после сбоев | | Масштабируемость | Легко добавлять новые узлы (шаги), правила перехода, условия остановки |
Ресурсы для дальнейшего изучения
-
LangGraph Documentation — официальные туториалы и примеры
-
langraph GitHub repository — исходный код с примерами использования
-
LangChain + Graphs: сравнительный анализ подходов к построению агентов (блоговые статьи)
-
Примеры в Colab: поиск по ключевым словам "LangGraph workflow control"
Контакты и поддержка
Если у вас возникли вопросы при реализации своих агентов, вы можете:
-
Посмотреть примеры на GitHub
-
Обратиться к сообществу LangChain/LangGraph (Discord, Stack Overflow)
-
Изучить официальные туториалы по шагам: от простого графа до сложных бизнес-процессов
Лицензия и использование материалов
Данный материал подготовлен для образовательных целей. Все примеры кода можно использовать свободно при соблюдении условий лицензии LangChain/LangGraph (MIT).