Как запустить циклические AI-агенты на Java
Большинство туториалов по мультиагентным системам с LLM написаны на Python. Если вы пишете бэкенд на Java и хотите собрать что-то сложнее линейной цепочки вызовов, раньше приходилось изобретать свои конечные автоматы или костылить ветвления поверх LangChain4j.
В экосистеме Python стандартом для таких задач стал LangGraph от команды LangChain. Он решает простую проблему: реальные диалоговые агенты почти никогда не работают по прямой линии (DAG). Агенту нужно вызвать инструмент, посмотреть на ошибку, переспросить пользователя или запустить подзадачу заново. Это циклы.
Проект LangGraph4j переносит эту концепцию в мир Java. Библиотека дружит со Spring AI и LangChain4j, поддерживает сохранение состояния в реальные базы данных и дает возможность строить сложные графы исполнения.
Что внутри и как это устроено
В основе библиотеки лежит класс StateGraph. Вы описываете граф как набор узлов (nodes), ребер (edges) и общего состояния (AgentState), которое передается между шагами.
Каждый узел принимает текущее состояние, выполняет кусок логики (например, дергает LLM или обращается к базе данных) и возвращает словарь с обновлениями. Эти обновления склеиваются с общим состоянием через так называемые редьюсеры (reducers). Например, новые сообщения можно дописывать в конец списка, а флаг статуса просто перезаписывать.
Базовый пример
Для работы нужен Java 17 или новее. Подключаем зависимость:
<dependency>
<groupId>org.bsc.langgraph4j</groupId>
<artifactId>langgraph4j-core</artifactId>
<version>1.8.24</version>
</dependency>
Опишем простейший граф с двумя узлами и общим состоянием для сообщений:
import org.bsc.langgraph4j.StateGraph;
import org.bsc.langgraph4j.state.AgentState;
import org.bsc.langgraph4j.state.Channels;
import org.bsc.langgraph4j.state.Channel;
import static org.bsc.langgraph4j.action.AsyncNodeAction.node_async;
import static org.bsc.langgraph4j.StateGraph.START;
import static org.bsc.langgraph4j.StateGraph.END;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
// 1. Описываем структуру состояния
class SimpleState extends AgentState {
public static final String MESSAGES_KEY = "messages";
public static final Map<String, Channel<?>> SCHEMA = Map.of(
MESSAGES_KEY, Channels.appender(ArrayList::new)
);
public SimpleState(Map<String, Object> initData) {
super(initData);
}
public List<String> messages() {
return this.<List<String>>value("messages").orElse(List.of());
}
}
public class SimpleApp {
public static void main(String[] args) throws Exception {
// 2. Собираем граф
var graph = new StateGraph<>(SimpleState.SCHEMA, SimpleState::new)
.addNode("greeter", node_async(state ->
Map.of(SimpleState.MESSAGES_KEY, "Привет от первого узла!")))
.addNode("responder", node_async(state ->
Map.of(SimpleState.MESSAGES_KEY, "Ответ получен.")))
.addEdge(START, "greeter")
.addEdge("greeter", "responder")
.addEdge("responder", END)
.compile();
// 3. Запускаем стриминг шагов
for (var step : graph.stream(Map.of(SimpleState.MESSAGES_KEY, "Старт"))) {
System.out.println("Шаг выполнен: " + step);
}
}
}
Здесь stream() возвращает асинхронный генератор. Вы получаете состояние графа после каждого отработавшего узла, что удобно для вывода прогресса клиенту в реальном времени.
Чем проект интересен на практике
1. Условные переходы и циклы
Линейные цепочки легко собрать и обычным кодом. Сила графов раскрывается, когда вы добавляете addConditionalEdges. Вы вешаете на ребро функцию, которая смотрит на результат работы LLM и решает, куда идти дальше: в узел вызова тулов, на повторную генерацию или на завершение диалога.
2. Сохранение состояния и Time Travel
Если агент общается с пользователем в несколько итераций или процесс занимает часы, держать всё в памяти JVM нельзя.
В LangGraph4j есть модуль чекпоинтов (CheckpointSaver). Доступны готовые адаптеры под PostgreSQL, Redis, MySQL, SQLite, OracleDB, Hazelcast и DynamoDB. Вы можете:
- Сохранять состояние после каждого шага;
- Восстанавливать выполнение с нужной точки после перезапуска сервиса;
- Реализовывать Human-in-the-loop, когда граф ждет подтверждения действия от человека, а потом продолжает работу;
- «Отматывать» граф назад к предыдущему снимку состояния.
3. Нативная дружба со Spring AI и LangChain4j
Вам не придется переписывать вызовы моделей под отдельный API. В репозитории уже есть модули интеграции.
Вот так выглядит запуск ReAct-агента со связкой LangGraph4j и LangChain4j:
var model = OllamaChatModel.builder()
.modelName("qwen2.5:7b")
.baseUrl("http://localhost:11434")
.build();
var agent = AgentExecutor.builder()
.chatModel(model)
.toolsFromObject(new TestTool())
.build()
.compile();
for (var item : agent.stream(Map.of("messages", "Проверь статус и верни число потоков"))) {
System.out.println(item);
}
Для Spring AI синтаксис практически идентичный, используются аннотации @Tool и бины Spring.
4. LangGraph Studio и визуализация
Отлаживать сложные графы вслепую тяжело. LangGraph4j умеет генерировать диаграммы графа в форматах PlantUML и Mermaid.
Также авторы сделали веб-интерфейс LangGraph4j Studio, который можно встроить прямо в свое Spring Boot, Quarkus или Jetty приложение, чтобы наглядно запускать и инспектировать узлы графа в браузере.
Подводные камни
Библиотека активно развивается (на момент обзора версия 1.8.x), поэтому некоторые редкие API могут меняться между минорными релизами.
Часть туториалов в папке how-tos/ оформлена в виде Jupyter-ноутбуков для Java. Для запуска этих примеров авторы требуют Java 22, хотя само ядро библиотеки спокойно работает на стабильной Java 17+.
Кому пригодится
Если вы строите enterprise-сервисы на Spring Boot или Quarkus и хотите внедрить агентные сценарии (техническая поддержка, автоматизация CI/CD, обработка документов в несколько этапов), LangGraph4j избавляет от необходимости писать свой планировщик задач для LLM.
Библиотека дает зрелую архитектурную базу, не заставляя команду переходить на стек Python только ради оркестрации агентов. Начать эксперименты можно с локальной модели через Ollama и простого графа из двух-трех узлов.
