1. Интеграция мульти-агентного ядра: связка LangGraph, LangChain и локальной инфраструктуры Ollama
Интеграция мульти-агентного ядра: связка LangGraph, LangChain и локальной инфраструктуры Ollama
В корпоративной среде 90% ИИ-прототипов успешно справляются с демонстрационными (happy path) сценариями, но рассыпаются при столкновении с реальными бизнес-процессами. Линейные RAG-пайплайны, какими бы сложными они ни были, не способны к саморефлексии: если база данных возвращает пустой ответ, линейная система просто транслирует этот отказ пользователю. Переход от хрупкого прототипа к отказоустойчивому MVP требует смены парадигмы — от конвейеров к конечным автоматам. На этом этапе мы синтезируем ранее изученные компоненты: вычислительную мощь локальных моделей Ollama, интеграционные абстракции LangChain и графовую оркестрацию LangGraph в единое, автономное ядро, способное принимать решения, ошибаться и самостоятельно исправлять свои ошибки до возврата ответа пользователю.
Архитектурная триада: Роли компонентов в синтезированном ядре
В мульти-агентной системе границы между библиотеками стираются, но для корректного проектирования необходимо жестко разграничить их зоны ответственности. Ошибка многих разработчиков заключается в попытке реализовать логику ветвления внутри промптов LLM или, наоборот, в написании сотен строк императивного Python-кода для парсинга ответов.
!Архитектура мульти-агентного ядра
В нашем корпоративном MVP распределение ролей выглядит следующим образом:
Runnable). LangChain оборачивает вызовы к Qdrant, форматирует промпты, валидирует Pydantic-схемы и выполняет фактический вызов Python-функций (инструментов), когда модель запрашивает действие.Такое разделение позволяет нам менять локальную модель на облачную, или PostgreSQL на MongoDB, не переписывая логику принятия решений.
Проектирование глобального состояния (Global State)
Кровеносной системой LangGraph является объект State. В линейных цепочках данные передаются от шага к шагу, мутируя в процессе. В графах состояние глобально, и каждый узел либо читает из него, либо возвращает дельту (изменения), которые сливаются с текущим состоянием через редукторы.
Для корпоративного MVP, объединяющего RAG и вызов инструментов, состояние должно быть достаточно емким, чтобы хранить контекст, но структурированным, чтобы не переполнить окно контекста модели.
Разберем анатомию этого состояния:
messages использует редуктор operator.add. Это означает, что когда узел возвращает новый словарь {"messages": [AIMessage(...)]}, LangGraph не перезаписывает историю, а добавляет новое сообщение в конец списка. Это критически важно для сохранения истории вызовов инструментов (Tool Calls).sender хранит идентификатор последнего активного агента (например, supervisor, rag_agent, sql_agent). У него нет редуктора, поэтому возврат нового значения полностью перезапишет предыдущее.documents резервируется для хранения сырых результатов из Qdrant. Выделение документов в отдельное поле, а не внедрение их сразу в messages, позволяет узлам-оценщикам (Grader Nodes) анализировать релевантность фактов независимо от сгенерированного ответа.error_count — механизм защиты от бесконечных циклов (Graceful Degradation), о котором мы поговорим ниже.Адаптация локальной Llama 3 для детерминированного Tool Calling
Главный вызов при использовании локальных моделей (в отличие от коммерческих API) — их склонность к галлюцинациям в структуре JSON. Даже Llama 3 8B может забыть закрыть скобку или придумать аргумент, не описанный в Pydantic-схеме инструмента.
Для надежной интеграции с LangChain мы используем метод bind_tools, но усиливаем его на уровне системного промпта и парсеров.
Включение format="json" на уровне Ollama заставляет движок llama.cpp отбрасывать любые токены, которые нарушают синтаксис JSON. Однако это не спасает от семантических ошибок (например, передачи строки вместо числа).
Поэтому в мульти-агентном ядре мы не полагаемся на то, что модель ответит правильно с первого раза. Мы закладываем ожидание ошибки в саму топологию графа.
Паттерн Supervisor: Маршрутизация на основе намерений
Вместо того чтобы создавать одного «бога-агента» с доступом ко всем базам данных и API, корпоративная архитектура требует паттерна Supervisor (Супервизор). Супервизор — это легковесный узел, который не выполняет работу сам, а только анализирует запрос и решает, какому специализированному агенту передать управление.
Для реализации Супервизора мы используем структурированный вывод LangChain (Structured Output). Мы заставляем модель вернуть строго один из предопределенных вариантов.
В графе функция маршрутизации будет выглядеть так:
Условное ребро (Conditional Edge) в LangGraph прочитает поле state["sender"] и физически перенаправит поток выполнения на соответствующий узел.
Циклы самокоррекции и изящная деградация (Graceful Degradation)
Самая мощная часть синтезированного ядра — способность исправлять собственные ошибки. Рассмотрим сценарий: Супервизор направил задачу в sql_agent. Агент сгенерировал SQL-запрос, но ошибся в названии таблицы. База данных PostgreSQL вернула ошибку relation "users_data" does not exist.
В линейной архитектуре пользователь получил бы сырую ошибку БД. В LangGraph мы создаем цикл самокоррекции.
!Динамика изменения состояния графа
Логика узла, выполняющего инструменты (Tool Node), оборачивается в блок try/except. Если возникает ошибка, узел не падает, а формирует специальное сообщение ToolMessage с текстом ошибки и увеличивает счетчик error_count.
Граф устроен так, что после tool_execution_node поток всегда возвращается к агенту, который этот инструмент вызвал. Агент видит в истории ToolMessage с ошибкой, анализирует её (понимает, что таблицы users_data нет), генерирует новый ToolCall с исправленным запросом, и цикл повторяется.
Если локальная модель недостаточно умна и продолжает ошибаться, срабатывает условие error_count >= 3. Это реализация паттерна Graceful Degradation (Изящная деградация). Система прекращает попытки, честно сообщает о сбое и устанавливает флаг requires_human = True, который в будущем позволит перехватить эту сессию оператору (Human-in-the-Loop).
Математика задержек в циклических графах
Синтез мульти-агентной системы неизбежно влияет на производительность. В линейном API мы боролись за оптимизацию TTFT (Time-to-First-Token). В графовой архитектуре с локальными моделями мы должны учитывать кумулятивную задержку системы.
Полная задержка мульти-агентного ответа описывается следующим образом:
Где:
FINISH.Из этой формулы вытекает важное архитектурное правило: каждый возврат по циклу (ошибка инструмента или рефлексия) увеличивает для следующего шага. Поскольку локальная Llama 3 обрабатывает контекст медленнее коммерческих API, длинные циклы самокоррекции могут привести к неприемлемому времени ожидания (Abandonment Rate). Именно поэтому жесткое ограничение error_count и использование легковесного Супервизора критически важны для сохранения юнит-экономики локального MVP.
Сборка графа: Компиляция конечного автомата
Завершающим этапом синтеза ядра является физическая сборка узлов и ребер в единый граф.
Скомпилированный app представляет собой объект Runnable, который полностью совместим с экосистемой LangChain. Мы можем вызывать его через app.invoke(), передавая начальное состояние с запросом пользователя. Внутри этого черного ящика агенты будут переговариваться, вызывать инструменты, ошибаться, исправляться и, в конечном итоге, выдавать проверенный результат.
Однако на данном этапе наше глобальное состояние AgentState живет исключительно в оперативной памяти (RAM) процесса Python. Если сервер перезагрузится во время выполнения долгого цикла или если мы захотим масштабировать систему на несколько воркеров Celery, контекст диалога и промежуточные шаги графа будут безвозвратно потеряны. Для превращения этого мощного вычислительного ядра в полноценный корпоративный бэкенд, состояние графа необходимо сделать персистентным, связав его с надежным хранилищем.