Главная / Статьи / Что такое graph engineering: узлы, рёбра и LangGraph на практике

Что такое graph engineering: узлы, рёбра и LangGraph на практике

Новое ли это явление? Разбираем prompt, context, harness и loop engineering, что делает систему графом, и пример агента на LangGraph из 11 узлов.

997 слов

Каждые несколько месяцев в ИИ-разработке появляется новый ярлык с "-engineering", и последний из них — graph engineering. Термин широко разошёлся после поста Питера Штайнбергера, создателя открытого автономного агента OpenClaw, который теперь работает в OpenAI, так что он попал в очень многие ленты. Разумная реакция — спросить, называет ли он что-то новое или это просто очередное модное слово.

Короткий ответ: по большей части это новое название того, что создатели агентов уже делают. А теперь подробнее.

Краткая история ярлыков

Область уже пережила несколько волн названий.

Первым был prompt engineering. Это умение писать промпты настолько точно и ясно, чтобы модель поняла, что вам нужно.

Затем появился context engineering, когда одного промпта перестало хватать, чтобы передать всё, что нужно агенту перед началом задачи. Вы прикладываете дополнительные материалы, например скриншоты, markdown-файлы и код, чтобы агент понял и ваше намерение, и ваше окружение.

Harness engineering, пожалуй, стал самым большим шагом. Вместо того чтобы просто передавать агенту информацию, вы строите вокруг него среду: детали проекта, доступные ему инструменты, память о прошлой работе, а также правила и ограничители, которые держат его в рамках.

Дальше идёт loop engineering. Вы говорите агенту продолжать итерации над задачей, пока он не достигнет критерия успеха. Типичный пример — суммаризатор, которого просят написать отчёт на какую-то тему, а потом раз за разом дорабатывать собственный черновик до планки качества, которую он проверяет сам, вместо того чтобы отдавать его отдельному шагу ревью.

Смысл в том, чтобы сократить собственное участие, передав агенту этап проверки. Вы больше не смотрите каждую итерацию и не даёте обратную связь. Агент сам оценивает текущий результат по критериям и корректирует свой подход. Есть и обратная сторона: агент, оставленный без присмотра, может пойти вразнос и сжечь ваши токены. И всё же, если идеал — система без человека в цикле, это шаг к нему.

Большинство разработчиков агентов делали так задолго до появления ярлыка. Нова лексика, а не принцип. Graph engineering следует той же схеме.

Что делает систему графом

Граф определяют три составляющие.

  1. Узлы. Узел — это отдельная единица вычислений с одной понятной задачей: функция, которая принимает текущее состояние и возвращает обновление. В LangGraph узел регистрируется через graph.add_node(name, fn).
  2. Рёбра. Ребро передаёт управление от одного узла к другому. В LangGraph graph.add_edge(a, b) означает, что узел b может запуститься, как только завершится узел a. Направление важно: a ведёт к b, а не наоборот. Бывают и условные рёбра. С graph.add_conditional_edge(a, func) функция выполняется после узла a и выбирает, какой узел пойдёт следующим.
  3. Форма. Узлы и рёбра вместе образуют рисунок, и эта структура проявляется, только когда смотришь на связанное целое.

Graph engineering на практике

Чтобы прочувствовать подход, возьмём агента на LangGraph, который выбирает место встречи для компании из двух–пяти человек, живущих далеко друг от друга и спорящих, какой район подходит. Он учитывает реальные факторы, например время в пути и число пересадок на автобусах для каждого, а не просто берёт середину карты.

Граф состоит из одиннадцати узлов, и каждый делает что-то одно. Они сужают область поиска, проверяют доступность для каждого человека, отбирают районы, ищут заведения, оценивают их и в конце проверяют результат. Условных рёбер нет, так что на бумаге структура линейная: каждый шаг ведёт к следующему, без ветвлений.

Код на LangGraph, который её задаёт, прост:

# Initialize the graph with our shared state schema.
graph = StateGraph(MeetingState)
graph.add_node(”node0_search_area”, search_area_node)

# list the nodes
# register one reachability node per possible party member
for index, node in enumerate(REACHABILITY_NODES):
graph.add_node(f”reach_person{index + 1}”, node)
graph.add_node(”nodeC_shortlist”, shortlist_node)
graph.add_node(”nodeD_spots”, spot_finder_node)
graph.add_node(”nodeE_score”, scorer_node)
graph.add_node(”nodeF_review”, reviewer_node)

# list the edges
graph.add_edge(START, “node0_search_area”)
for index in range(len(REACHABILITY_NODES)):
graph.add_edge(”node0_search_area”, f”reach_person{index + 1}”)
graph.add_edge(f”reach_person{index + 1}”, “nodeC_shortlist”)
graph.add_edge(”nodeC_shortlist”, “nodeD_spots”)
graph.add_edge(”nodeD_spots”, “nodeE_score”)
graph.add_edge(”nodeE_score”, “nodeF_review”)
graph.add_edge(”nodeF_review”, END)

# Compile into a runnable graph.
compiled = graph.compile() Engineering w/ a Project

Это и есть весь граф, и читается он как обычный пайплайн.

Что даёт такая структура

Даже у линейного графа есть практическая польза. Каждый узел читает и пишет в один общий объект состояния, так что видно, что именно изменил каждый шаг. Упавший шаг легко изолировать и перезапустить, потому что это небольшая функция с определённым входом. Можно заменить или улучшить один узел, скажем оценку заведений, не трогая остальные. А когда понадобится ветвление или цикл повторных попыток, добавить условное ребро — небольшое изменение, а не переписывание.

Ничего экзотического в этом нет. Это обычное проектирование ПО, применённое к рабочим процессам агентов, поэтому ярлык звучит драматичнее самой идеи.

Так что же это?

Graph engineering — не новая концепция. Это свежее название подхода, который стал привычным по мере того, как всё больше людей строят агентов. Если вы его ещё не пробовали, попробуйте на LangGraph или на любом фреймворке, который вам уже нравится. Ярлык важен меньше, чем привычка: разбивайте работу на небольшие шаги с одной задачей, делайте связи между ними явными и держите общее состояние понятным.