К содержимому
Завалищев
База знаний
Исследования ИИ 8 мин чтения

Orchestration engine без LangChain: Executor как контракт между вероятностным и детерминированным мирами

Разработчик построил orchestration engine с нуля и обнаружил ключевой паттерн: LLM остаётся ненадёжным до Executor-слоя. Всё до него — вероятностное, всё после — детерминированное. Executor — контракт между двумя мирами.

На r/LangChain появился пост, который набрал десятки апвотов и резонансную дискуссию: разработчик построил AI orchestration engine без LangChain и поделился архитектурными выводами. Ключевой инсайт оказался не про код, а про границу между двумя принципиально разными мирами — вероятностным и детерминированным. И про Executor, который стоит на этой границе как контракт.

Проблема: LangChain скрывает главное решение

LangChain делает реальные вещи: шаблоны промптов, управление памятью, подключение инструментов, обёртки для API. Но всё это — не сложная часть. Сложная часть — понять, когда можно доверять тому, что выдал LLM, а когда нельзя.

LangChain трактует выход LLM как программный вывод. Отправил запрос — получил результат — передал дальше. Три слоя абстракции делают отладку мучительной: вместо своего кода читаешь внутренности фреймворка.

Автор Reddit-поста описал конкретный инцидент: LangChain-агент вызвал DELETE-эндпоинт, который не предполагался к использованию. Фреймворк передал выбор инструмента от LLM напрямую к исполнению без валидационного слоя, в который можно было бы вмешаться, не ломая абстракции. Это стало последней каплей.

Но дело не в багах LangChain. Дело в архитектурном паттерне, который фреймворк скрывает за удобством.

Главный инсайт: probabilistic/deterministic boundary

Вот центральная идея: LLM должен оставаться «ненадёжным» до Executor-слоя. Всё до Executor — вероятностное. Всё после — детерминированное.

Это не метафора. Это архитектурный принцип.

LLM — генеративная модель. Она производит правдоподобный текст, не гарантированно корректный. Каждый её выход — вероятностный: может быть правильным, может быть галлюцинацией, может быть частично верным. Температура, top-p, контекстное окно — всё это параметры стохастического процесса.

Executor — это слой, где вероятности заканчиваются. Здесь вызываются реальные API, меняются данные в базе, отправляются emails, списываются деньги. Каждое действие детерминировано и необратимо.

Между этими мирами нужен контракт. Не «промпт, который просит LLM быть аккуратным». Контракт в инженерном смысле: формальная спецификация того, что может пройти от вероятностного мира в детерминированный.

Трёхслойная архитектура

Автор реализовал это через три слоя:

Planner LLM — принимает решения, но ничего не исполняет. Его единственная задача — выдать структурированный JSON с названием инструмента, аргументами и обоснованием выбора. Никаких побочных эффектов.

Trust Boundary (валидационный слой) — функция между вызовом LLM и вызовом инструмента. Pydantic-схема проверяет структуру. Allowlist проверяет, что инструмент разрешён. Бизнес-правила проверяют аргументы. Это слой, который большинство разработчиков пропускают.

Executor — исполняет только валидированные планы. Никакой интерпретации, никакого «додумывания». Получил проверенную структуру — выполнил.

В коде это выглядит обманчиво просто: plan = call_planner(query) → validated = validate_plan(plan) → result = execute(validated). 80 строк Python. Без фреймворка.

Executor как контракт: пять свойств

Почему Executor — именно контракт, а не просто «исполнитель»?

  1. Строгая типизация входа. Executor принимает не произвольный текст, а валидированную структуру с фиксированной схемой. Если Planner выдал невалидный JSON — это ошибка планирования, не ошибка исполнения. Граница ответственности чёткая.
  2. Allowlist, не denylist. Executor знает конечный список инструментов и допустимых операций. Всё, что не в списке, — отклонено. Это принципиально отличается от подхода «запретим опасное» (denylist), который всегда пропускает что-то непредвиденное.
  3. Идемпотентность проверок. Один и тот же валидированный план при повторной проверке даёт тот же результат. Нет зависимости от состояния LLM, контекста или настроения модели.
  4. Аудитабельность. Каждый переход через границу логируется: что Planner предложил, что прошло валидацию, что исполнено. В production это единственный способ разобраться, почему агент сделал то, что сделал.
  5. Fail-safe по умолчанию. Если валидация падает — Executor не вызывается. Система останавливается, не «пробует другой подход». Для production-систем с реальными побочными эффектами это единственно правильное поведение.

Что LangChain делает не так (и почему это важно)

LangChain — не плохой инструмент. Это инструмент с другой философией. Он оптимизирует скорость прототипирования: быстро собрать цепочку, подключить memory, добавить tools. Для демо и PoC — идеально.

Проблема начинается, когда прототип переезжает в production.

LangChain смешивает вероятностный и детерминированный слои. Выход LLM подаётся в tool executor через абстракции, в которые сложно вставить валидацию. Можно, но приходится бороться с фреймворком, а не работать вместе с ним.

Конкретные боли:

  • Отладка через три слоя абстракции. Когда агент ведёт себя неожиданно, нужно пройти через chain → agent → tool execution, чтобы понять, где именно проблема. Каждый слой добавляет собственные трансформации данных.
  • Неявные решения. LangChain принимает решения за вас: как форматировать промпт, как парсить ответ, когда ретраить. Пока это совпадает с вашими потребностями — удобно. Когда перестаёт — начинается борьба.
  • Версионная хрупкость. Быстрые релизы фреймворка ломают существующий код. То, что работало на прошлой неделе, может не работать сегодня из-за изменений во внутренних абстракциях.

LangGraph частично решает эти проблемы, давая графовую структуру без тяжёлых абстракций. Но для 80% use cases самописная трёхслойная архитектура проще и прозрачнее.

Production-данные о надёжности

Автор приводит конкретику из production:

  • LLM выдаёт невалидный JSON в 2-5% случаев (зависит от модели и сложности промпта)
  • LLM называет несуществующий инструмент в ~1% случаев
  • LLM передаёт неверные аргументы при корректном инструменте в ~3% случаев

Суммарно: без валидационного слоя примерно 6-9% вызовов привели бы к ошибкам исполнения или непредсказуемым побочным эффектам.

С Trust Boundary эти ошибки ловятся до Executor. Невалидный JSON вызывает retry с более строгим промптом. Неизвестный инструмент отклоняется с логом. Неверные аргументы фильтруются Pydantic-валидацией.

Система не становится надёжнее на стороне LLM — LLM остаётся вероятностным. Система становится надёжнее на стороне контракта.

Когда строить с нуля, а когда брать фреймворк

Строить orchestration engine с нуля стоит, когда:

  • Вам нужен полный контроль над границей probabilistic/deterministic. Если ваши инструменты имеют необратимые побочные эффекты (платежи, удаление данных, отправка сообщений), прозрачность этой границы — не опция, а требование.
  • Ваш агент делает одну цепочку хорошо. Planner → Validate → Execute. Если это описывает ваш use case — фреймворк добавляет сложность без пользы.
  • Вы готовы писать инфраструктуру. Retry-логика, логирование, мониторинг, graceful degradation — это ваша ответственность. Фреймворк даёт часть из этого бесплатно.

Брать фреймворк (LangGraph, не LangChain) стоит, когда:

  • Мультиагентная система с разделяемым состоянием. Координация нескольких агентов, branching логика, параллельное исполнение — здесь графовая структура LangGraph реально помогает.
  • Прототип, который нужен завтра. Если цель — проверить гипотезу, а не строить production-систему, скорость разработки важнее прозрачности.
  • Команда без опыта в AI-инфраструктуре. Фреймворк кодифицирует паттерны, которые иначе пришлось бы изобретать самостоятельно.

Аналогия с распределёнными системами

Executor как контракт — это не новая идея. Это адаптация паттерна из распределённых систем.

В микросервисной архитектуре API Gateway валидирует входящие запросы перед маршрутизацией к сервисам. Он не доверяет клиенту. Он проверяет схему, авторизацию, rate limits — и только потом пропускает запрос дальше.

LLM — это «клиент» вашей системы. Умный, способный, но ненадёжный. Как любой внешний клиент, его запросы нужно валидировать на границе, а не внутри исполнителя.

Ещё ближе аналогия с database transaction boundaries. Внутри транзакции — последовательность операций, которая либо выполняется целиком, либо откатывается. Trust Boundary работает так же: план от LLM либо проходит валидацию целиком и исполняется, либо отклоняется целиком.

Практический вывод

Если вы строите AI-агента для production, начните не с выбора фреймворка. Начните с рисования границы.

Слева — всё, что вероятностное: промпты, генерация, рассуждения модели, выбор инструментов. Это территория LLM, и здесь нормально ошибаться.

Справа — всё, что детерминированное: API-вызовы, изменения данных, отправка сообщений. Здесь ошибаться нельзя.

На границе — Executor-контракт. Типизированная схема входа. Allowlist операций. Логирование каждого перехода. Fail-safe при любой аномалии.

Эта граница — единственная архитектурная деталь, которая определяет надёжность вашего агента в production. Всё остальное — детали реализации.