Когда мы начинали строить продукт для распознавания и обработки первичных бухгалтерских документов — OCR, классификация, LLM‑извлечение полей, проверка человеком — казалось, что рынок оркестраторов уже всё решил: есть n8n для визуальных workflow, есть LangGraph для агентных пайплайнов, есть Temporal для durable‑исполнения. Мы попробовали примерить каждый — и в итоге написали свой: AgentsGraph, встраиваемую Java‑библиотеку декларативной оркестрации агентов. Ниже — почему.

Требование № 1: enterprise в private cloud, без внешних сервисов

Наши заказчики — компании, у которых документы не могут покидать контур: банковская первичка, персональные данные, коммерческая тайна. «Отправьте ваш счёт‑фактуру в наш облачный API» — это не разговор. Поэтому базовое требование к оркестратору сформулировалось жёстко:

всё, что нужно для работы графа, должно жить внутри приложения заказчика.

В AgentsGraph это выполнено буквально:

  • Хранилища — ваш PostgreSQL. Конфигурация графов (agentsgraph_graph_config), реестр процессоров (agentsgraph_processor), журнал исполнений и пошаговые трейсы (agentsgraph_traceagentsgraph_step_trace) — обычные таблицы в базе приложения, разворачиваются тем же SQL‑скриптом, что и остальная схема. Ни очередей в чужом облаке, ни телеметрии наружу, ни «позвоните нам за license key».

  • Модели — ваши. Процессоры ходят в те LLM и OCR, которые стоят в стойке заказчика. Наш боевой пайплайн работает на локальном OCR‑сервисе и локальном инференсе qwen — наружу не уходит ни байта. Клиент к LLM говорит и на OpenAI‑, и на Anthropic‑диалекте (автоопределение), так что «своя модель» не означает «свой зоопарк адаптеров».

  • Библиотека, а не платформа. AgentsGraph — набор jar‑модулей (contextconfigenginetracecontrolcoreinteraction), которые подключаются в ваше Spring‑приложение как обычная зависимость. Никакого отдельного сервера‑оркестратора, который нужно лицензировать, обновлять и защищать. Админ‑панель (просмотр графов, исполнений, пошаговых трейсов, перезапуск шага) — опциональный модуль, поднимающийся внутри вашего же Boot‑приложения.

Для enterprise это не «фича», а критерий отбора: security‑аудит проходит ваш продукт целиком, и оркестратор в нём — просто ещё одна библиотека в pom‑е, а не внешняя система со своим периметром.

Почему не n8n

n8n — отличная вещь для того, для чего она сделана: связать десяток SaaS‑ов без программиста. Сотни готовых коннекторов, триггеры, визуальный low‑code редактор. Если задача — «когда приходит письмо, положи вложение в Drive и напиши в Slack», n8n закрывает её за вечер, и мы бы не стали писать ради этого ни строчки.

Но у нас другая задача — и два несовместимых с n8n обстоятельства. Первое: n8n — это отдельно стоящая платформа, а не библиотека. Его нельзя растворить внутри своего продукта: это самостоятельный сервис со своим UI, своей моделью пользователей и своим жизненным циклом, рядом с которым ваше приложение — лишь один из «коннекторов». Второе — лицензия: n8n распространяется под Sustainable Use License, которая прямо ограничивает встраивание в коммерческие продукты — за embedding нужно идти за отдельной коммерческой лицензией. AgentsGraph же изначально спроектирован как встраиваемый и лицензирован под Apache 2.0 — встраивание в коммерческий продукт свободно: граф — деталь реализации вашей системы. Процессор — это ваш Java‑класс с вашим DI, вашими транзакциями и вашими юнит‑тестами; шаг пайплайна и сервисный слой приложения — один и тот же код, а не HTTP‑мостик между двумя мирами. Для вендора, продающего свой софт on‑premise, это разница между «поставляем продукт» и «поставляем продукт плюс чужую платформу с отдельным лицензионным договором».

Почему не LangGraph

LangGraph ближе всех по духу: те же агентные графы, состояние, ветвления, human‑in‑the‑loop. Если ваш стек — Python и вы готовы жить в его экосистеме, это сильный выбор. Наши расхождения с ним — про инженерную дисциплину на длинной дистанции.

Типизация и конфигурация. В LangGraph граф — это код на Python: состояние — словарь или TypedDict, рёбра — функции, ошибки конфигурации всплывают в рантайме у пользователя. В AgentsGraph граф — декларативный JSON с жёсткой схемой, который валидируется при деплое: GraphDefinition → ноды со стратегиями роутинга → рёбра со списками шагов. Поток данных между шагами описан явно: output_to_next говорит, какие ключи поедут дальше по пайплайну, output_to_save — какие уйдут на персистенцию. Контекст (ExecutionContext) иммутабелен, а обязательные входы шаг забирает через require(key) — если ключа нет, вы получаете не NullPointerException тремя шагами позже, а немедленную ошибку с перечнем доступных ключей. Целый класс багов «кто‑то переименовал поле в словаре» здесь просто не компилируется или ловится на деплое графа, а не в проде.

А те баги, что всё же случаются, — журналируются и чинятся перезапуском. Каждый шаг исполняется под трейсером: в debug‑режиме (или точечно, для шагов с флагом "snapshot": true, — прямо в проде) в базу пишется полный входной контекст шага, его выход, тайминги, а при падении — стектрейс. Упавший flow — это не строчка в логе, а разборный объект: describeFlow печатает пошаговый отчёт, resumeFrom(flowId, seq) перезапускает граф ровно с упавшего шага на тех же данных — дорогой OCR не выполняется повторно, — а resumeFrom(flowId, seq, overrides) позволяет перед ретраем поправить данные. Дамп трейса скармливается тестовому харнессу, и прод‑инцидент воспроизводится в CI с замоканными ответами внешних сервисов — без сети и без токенов. Ретраи есть и на нижнем этаже: транспортные сбои LLM/OCR (таймаут, обрыв) повторяются с настраиваемым числом попыток, а обрезанный по лимиту токенов ответ — это явная ошибка, а не полу‑JSON, уехавший дальше по пайплайну.

Пример: обработка документов с human‑in‑the‑loop

Покажем, как это выглядит на нашем боевом пайплайне первички. Граф — ромб: документ распознаётся, и дальше маршрут зависит от того, можно ли доверять распознаванию без человека.

{
  "id": "ocr-accounting",
  "nodes": [
    { "id": "accuracy_router", "routing_strategy": "rules",
      "routing_table": {
        "accuracyOk==false": "edge_review",
        "default": "edge_llm_pipeline" } },
    { "id": "review_router", "routing_strategy": "rules",
      "routing_table": {
        "reviewPending==true":  "edge_review_pending",
        "reviewPending==false": "edge_llm_pipeline" } }
  ],
  "edges": [
    { "id": "edge_ocr_pipeline",
      "steps": [
        { "id": "step_ocr", "processor_id": "docscan-ocr", "output_to_next": ["json"] },
        { "id": "step_ocr_visualize", "processor_id": "docscan-ocr-visualize",
          "output_to_next": ["json", "accuracyOk", "accuracyScore", "lowProbItems"] } ],
      "next_node_id": "accuracy_router" },
    { "id": "edge_review",
      "steps": [
        { "id": "step_human_review", "processor_id": "human-review", "snapshot": true },
        { "id": "step_apply_corrections", "processor_id": "apply-corrections",
          "output_to_next": ["json"] } ],
      "next_node_id": "review_router" },
    { "id": "edge_review_pending",
      "steps": [ { "id": "step_review_pending", "processor_id": "noop" } ],
      "tags_to_add": ["review_pending"] }
  ]
}

Работает это так. OCR‑сервис возвращает по каждому распознанному элементу вероятность prob (0..1); шаг визуализации агрегирует их в оценку точности: если хоть один элемент слабее порога (по умолчанию 0.55) или средняя ниже 0.85 — accuracyOk=false. Пороги — параметры процессора, меняются в конфигурации без пересборки. Нода accuracy_router обычным правилом уводит такой flow в review‑ветку.

Шаг human-review — чистый процессор без всякой магии «пауз»: не найдя в контексте ответа человека, он формирует задачу (вопрос, оценка точности, список слабых элементов) и flow штатно завершается с тегом review_pending. Ключевое здесь — флаг "snapshot": true: полный входной контекст этого шага записан в трейс прямо в проде, поэтому шаг рестартуем. Отдельный модуль interaction превращает такие завершённые flow в задачи для человека и доставляет их адаптерами в любой канал — у нас это чат: пользователь видит карточку «точность 42%, проверьте выделенные поля», правит распознанные данные штатным редактором и нажимает «Продолжить».

Ответ человека — это вызов всё того же resumeFrom: шаг human-review перезапускается, на этот раз видит исправления и пропускает их дальше; apply-corrections кладёт их в контекст под тем же ключом json, что выдаёт OCR, — и review_router возвращает flow в общий LLM‑пайплайн. Пост‑обработка не знает и не должна знать, побывал ли документ у человека: дорогой OCR не выполняется повторно, LLM получает проверенные данные. Движку для всего этого не понадобилось ни одного нового состояния — только те же трейсы и перезапуск; повторный ответ на уже закрытую задачу отклоняется, дедлайны обрабатываются sweep‑ом.

Визуализация

Смотреть на граф глазами тоже есть чем: в комплекте — веб‑панель agentsgraph‑ui (Angular + d3 поверх модуля admin-server). Она рисует граф как есть из его конфигурации: ноды‑роутеры и рёбра‑пайплайны, подписи условий на стрелках, fallback‑связи пунктиром, HITL‑рёбра подсвечены розовым, снапшот‑шаги помечены. Вершины можно перетаскивать по холсту, клик по ноде или ребру открывает панель с деталями — таблицей правил роутинга или списком шагов с параметрами. Там же — журнал исполнений с пошаговыми трейсами упавших flow и кнопкой «перезапустить с этого шага».

Что в итоге

Мы не строили «убийцу n8n» и не соревнуемся с LangGraph в ширине экосистемы. AgentsGraph — это узкий и глубокий инструмент: оркестрация LLM‑пайплайнов внутри enterprise‑Java‑приложения, работающего в private cloud, с декларативной конфигурацией, строгими контрактами данных, пошаговой наблюдаемостью и перезапуском с любого шага — включая перезапуск руками человека. Для продуктов, которые продаются on‑premise и обязаны объяснять аудитору каждый байт, покидающий контур, такой инструмент оказался не роскошью, а условием существования.

Библиотека открыта и распространяется под лицензией Apache 2.0 — встраивайте в свои коммерческие системы без ограничений и отдельных договоров. Код разбит на независимые модули, ядро совместимо с Java 11, админ‑панель — Spring Boot 3 / Angular:

Если вам знакома боль из этой статьи — попробуйте.

Комментарии (7)


  1. openbpm_pm
    11.08.2026 09:45

    Добрый день. А почему вы не воспользовались бесплатными open-source библиотеками BPM-движков, там оркестрация AI решается элементарно: унифицированный бин на базе SpringAI и все, больше ничего делать не нужно. Аналитик просто указывает нужную спецификацию на кубике схемы процесса и не погружается в реализацию. Если пробовали такой подход, то пожалуйста расскажите с какими ограничениями столкнулись?


    1. budaevv Автор
      11.08.2026 09:45

      Добрый день! Спасибо за вопрос.  наши причины в другом.

      Не всё можно встроить в свой продукт. Мы поставляем софт on-premise, и оркестратор должен раствориться в нём как обычная библиотека. Camunda 8 — отдельный кластер под source-available лицензией, Camunda 7 Community — EOL, у n8n лицензия запрещает embedding в коммерческие продукты. Остаётся по сути только Flowable — и тут вторая причина.

      BPM-движки тяжёлые и тащат легаси, которое дальше поддерживаете вы: job executor, десятки таблиц истории, семантика BPMN 2.0 с компенсациями и boundary events — спецификация под документооборот 2000-х, а не под LLM-пайплайны. Всё это надо обновлять, объяснять на security-аудите и держать под это экспертизу — ради маршрутизируемого DAG из пяти шагов. А ошибки всё равно всплывают в рантайме: process variables — нетипизированный мешок, и «шаг B ждёт ключ, который шаг A перестал класть» аналитик на схеме не увидит.

      Для LLM-инцидентов нам важнее другое: явные контракты данных между шагами, полный входной снапшот упавшего шага (включая собранный промпт) и перезапуск с этого шага на исправленных данных. Это и есть ядро AgentsGraph — несколько небольших jar (Java 11, Apache 2.0) поверх вашего же Postgres. Если в организации уже живёт Flowable и BPM-команда — ваш путь рабочий; у нас содержать движок оказалось дороже, чем написать тонкое ядро под задачу.


  1. openbpm_pm
    11.08.2026 09:45

    Спасибо. Не убедили конечно. Современных встраиваемых BPM-движков очень много, практически под любые языки разработки. И занимают эти библиотеки по объему порядка 3 Мб. Ничего там отдельно обновлять и обслуживать не надо, так как это полностью встраивается в ваш прикладной агентский проект, просто как одна из зависимостей. И как раз прелесть готового оркестратора в том, что все, что связано с исполнением (стек облака переменных, управление токеном-указателем шага, мониторинг ресурсов, ретраи и разрешение инцидентов) уже доступно из коробки. Поэтому можно всецело сосредоточиться на паттернах проектирования мультиагентных систем, а не на технике исполнения.


    1. budaevv Автор
      11.08.2026 09:45

      Спасибо! нужно больше фактов. По мне так BPM больше для интеграции отдельных веб сервисов , чем как встраиваемая библиотека работает.


    1. budaevv Автор
      11.08.2026 09:45

      Перечитал Ваши статьи вот более подробный ответ:

      OpenBPM — сильный вариант: Apache 2.0, встраивается, в реестре ПО. Различия глубже — в архитектуре:

      1. Наследие движка. OpenBPM несёт всю архитектуру Camunda 7: job executor, таблицы истории, семантику BPMN 2.0. Это зрелость, но и вес — миграции, экспертиза BPMN в команде. Ядро AgentsGraph — несколько небольших jar под одну задачу: DAG из LLM/OCR-шагов.

      2. Spring AI — кодовый, а не декларативный. Промпты, цепочки и tools собираются в Java-коде: правка = пересборка, аудит = чтение кода. Tools — это @Tool-бины внутри приложения; чтобы переиспользовать их между процессами или дать наружу, их скорее всего придётся выносить в отдельные сервисы — и вы снова строите инфраструктуру вокруг «простого бина». У нас промпты, пороги и параметры процессоров — строки конфигурации в БД, меняются без пересборки.

      3. Контракты данных. Process variables — нетипизированный мешок. У нас поток данных объявлен в конфиге шага (output_to_next/output_to_save), require(key) падает сразу, со списком доступных ключей.

      4. Разбор LLM-инцидентов. Retry в BPM повторяет делегат на текущем состоянии. У нас — полный входной снапшот шага (включая промпт), перезапуск с упавшего шага на исправленных данных, реплей в CI. Плюс LLM-специфика: таймауты от maxTokens, обрезка по токенам = ошибка.

      5. Честно: user tasks и BPMN Modeler для аналитиков — зрелое преимущество BPM. Наш HITL другой: правка полей в чат-UX и продолжение с того же шага.

      Итог: есть BPM-практика и процессы шире AI — берите OpenBPM + Spring AI. Нужны именно LLM-пайплайны с декларативной конфигурацией, контрактами, трейсингом и продолжением после правок человека — тонкое ядро дешевле в поддержке, чем BPMN-движок с наследием.


  1. Igor_hab
    11.08.2026 09:45

    почему язык Java?


    1. budaevv Автор
      11.08.2026 09:45

      Java все еще является корпоративным и enterprise стандартом в большинстве финансовых организаций РФ и аудируется. К тому, же была первоначальная цель сделать прототипирование чтобы доказать гипотезу, а потом, не без помощи ИИ можно переписать на другие языки. LangChain4j  библиотека не подошла т.к. использует кодовый подход вместо декларативного и аудируется тяжелее, чем декларативная конфигурация + LangChain4j — это в первую очередь клиентский слой (модели, RAG, tools), а не оркестратор; он не конкурент, а другой уровень — его вполне можно использовать внутри процессора AgentsGraph. Противопоставлять его целиком — уязвимая позиция; противопоставляйте подход. Весь стек продукта уже на Java — оркестратор обязан жить в одном процессе с DI и транзакциями приложения. И совместимость ядра с Java 11 — для консервативных организаций это не мелочь.