Условные рёбра в LangGraph: маршрутизация графа через состояние и решения
Как работают условные рёбра в LangGraph: пишем роутер, который сохраняет решение в состоянии, сопоставляем исходы с узлами и отправляем каждый путь туда, куда нужно.
Прежде чем добавлять решения, полезно коротко вспомнить первую часть этой серии про LangGraph. По сути, LangGraph — это набор связанных функций, каждая из которых делает одну работу. Идею несут три элемента.
Состояние (State) — объект, который движется по графу и хранит всю информацию. Любой узел может читать из него и записывать в него.
state = {
'a': 2,
'b': 3,
'sum': None
}
Узлы (Nodes) — обычные функции. Узел получает состояние, выполняет какую-то работу и возвращает только те значения, которые хочет изменить.
def add(state):
total = state['a'] + state['b']
return {'sum': total}
Рёбра (Edges) определяют, какой узел запустится следующим, например:
add → multiply → final
Состояние проходит по этому пути, а узлы по очереди выполняются. Узел к тому же не обязан быть обычной функцией на Python: он может оборачивать вызов LLM. Но до сих пор каждый граф шёл по одному фиксированному маршруту. Следующий вопрос — как дать графу выбирать между маршрутами, и ответ на него — условные рёбра.
Идея условного ребра
Значительная часть пользы программ — в том, что они делают выбор: да или нет, то или это. Графам это тоже нужно, поэтому некоторые функции существуют только для того, чтобы принимать решения.
Допустим, граф приветствует человека в зависимости от времени суток. Утром вывод такой:
Good Morning
Ночью такой:
Good Night
В результате меняется форма графа. Это уже не прямая линия
A → B → C
а развилка:
→ Morning → Good Morning
/
User → Router
\
→ Night → Good Night
Ветку выбирает роутер (Router). В двух словах, это и есть условное ребро.
Сначала пробуем на чистом Python
Прежде чем подключать LangGraph, вот та же идея в виде обычной функции. Ей нужно текущее время:
from datetime import datetime
А вот сама функция:
def router(): hour = datetime.now().hour
if 5 <= hour < 12:
return "Morning"
else:
return "Night"
Обратите внимание, как мало эта функция делает. Никакой большой операции она не выполняет. Она принимает решение. Если текущий час от 5 утра до полудня, она возвращает
Morning
а в любом другом случае возвращает
Night
Внутри графа это возвращаемое значение может выбирать следующий узел. Есть и второй приём: решение можно сохранить в состоянии. Добавьте для него поле:
state = {
'time_per': None
}
Теперь роутер может записать свой выбор в состояние. Он принимает состояние как аргумент:
def router(state):
И внутри вычисляет час и записывает результат:
hour = datetime.now().hour
if 5 <= hour < 12:
return {'time_per': 'Morning'}
else:
return {'time_per': 'Night'}
После этого в состоянии хранится решение:
{
'time_per': 'Morning'
}
В этом и суть. Состояние хранит информацию, а условное ребро читает её, чтобы направлять граф. Две вещи происходят друг за другом:
Router
↓
updates state
↓
time_per = "Morning"
↓
conditional edge checks it
↓
Morning node
Здесь состояние и рёбра начинают работать вместе.
То же самое в LangGraph
Сначала состоянию нужно типизированное определение. Импортируйте TypedDict:
from typing import TypedDict
Затем объявите схему:
class State(TypedDict):
time_per: str
Дальше идут узлы. Роутер решает, утро сейчас или ночь, и ему нужны часы:
from datetime import datetime
Сам узел-роутер:
def router(state): hour = datetime.now().hour if 5 <= hour < 12:
return {'time_per': 'Morning'}
else:
return {'time_per': 'Night'}
Ещё два узла обрабатывают каждый исход. Один отвечает за утро:
def morning(state):
return {'time_per': 'Good Morning'}
Другой — за ночь:
def night(state):
return {'time_per': 'Good Night'}
Теперь важная часть. LangGraph нужна функция, которая говорит, по какому пути идти. Она получает состояние:
def decide_time(state):
и просто возвращает сохранённое решение:
return state['time_per']
То есть она возвращает либо
Morning
либо
Night
Эта функция подключается к условному ребру:
graph.add_conditional_edges(
"router",
decide_time,
{
"Morning": "morning",
"Night": "night"
}
)
Разберём, что означает каждая часть. Начало вызова
graph.add_conditional_edges(
"router",
говорит LangGraph, что после завершения узла router граф не должен переходить к одному фиксированному узлу. Дальше управление берёт решение. Следующий аргумент
decide_time,
— это функция, которая принимает решение. Последний аргумент
{
"Morning": "morning",
"Night": "night"
}
— соответствие между каждым возможным решением и узлом, который должен идти следом. Если decide_time() возвращает
Morning
граф идёт по
router → morning
а если возвращает
Night
граф идёт по
router → night
Теперь полный граф выглядит так:
→ morning → END
/
START → router
\
→ night → END
В этом и разница между двумя видами рёбер. Обычное ребро всегда говорит
A → B
без исключений, а условное ребро говорит
A → ?
и позволяет решению подставить ответ вместо вопросительного знака.
Почему это важно
Здесь LangGraph становится по-настоящему интересным. Поток работает как цикл зависимостей:
updates
State
↑
|
Router
|
↓
Conditional Edge
↙ ↘
Morning Night
Узел может обновить состояние. Функция маршрутизации может прочитать это состояние. А затем функция маршрутизации решает, какой узел запустится следующим. Граф перестаёт быть грудой связанных функций и превращается в граф, где текущее состояние может менять собственный путь. Сложным рабочим процессам нужно именно это.
Реалистичный пример — роутер вопросов:
User Question
↓
Classifier
↓
State
↓
┌────┴─────┐
↓ ↓
Coding General
↓ ↓
Code Normal
Answer Answer
Классификатор может записать в состояние что-то такое:
{
'type': 'coding'
}
Условное ребро читает это значение и решает, отправить запрос в узел для кода или в общий. На этом этапе рабочий процесс уже не простая последовательность. Это граф, который принимает решения. Следующий шаг — циклы, когда граф возвращается назад, повторяет шаг и пробует снова.