Главная / Статьи

Специалисты-агенты, маршрутизатор ключевых слов и функция прерывания(): наставник LangGraph

Создать помощника LangGraph с двумя специалистами, оснащенного детерминистическим маршрутизатором, общим состоянием, сохраняющимся при передаче задач, а также человеческой точкой контроля, основанной на прерываниях и командах.

Издатель: REACTAPP.TOP11 мин чтения2462 слов

Источники: Building Multi-Agent AI Systems with LangGraph: A Fitness & Nutrition Coach Example

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

Сценарий и тестовый диалог

Целевым продуктом является ассистент в приложении для фитнеса, который помогает как с тренировками, так и с питанием. Дизайн проверялся на примере трехэтапного диалога: запрос на план тренировок, вопрос о питании и ограничение в рационе, требующее изменения советов по приему пищи:

Scenario: A fitness app wants an assistant that helps with workouts and nutrition.

Test conversation flow (3 turns):
"Build muscle, lose fat — suggest a workout plan"
"What should I eat to support this workout plan?"
"I'm vegetarian, adjust the meal suggestion"

Каждое принятое решение при проектировании направлено на обеспечение корректной и предсказуемой работы этих трех этапов взаимодействия.

Почему универсальный чат-бот не справляется

Если попросить универсальный чат-бота составить план тренировок, а затем соответствующую диету, ответы, как правило, будут поверхностными. Модель пытается обработать все требования в рамках одного системного запроса, из-за чего советы теряют конкретику, а нюансы питания становятся однородными. Эта слабость возникает из-за того, что три разных требования одновременно влияют на один и тот же запрос:

  • Разделение областей. Планирование тренировок и питания — это разные сферы экспертизы, и рекомендации в одной из них не должны проникать в другую или противоречить ей.
  • Сохранение контекста между этапами. Ограничения, упомянутые ранее, например травма колена, о которой шла речь три сообщения назад, должны продолжать влиять на план питания или тренировок, сформированный позже.
  • Контроль со стороны человека. Система не должна работать полностью автономно; ей необходим точка, в которой человек может дать разрешение, добавить детали или изменить направление до того, как будет сгенерировано что-либо ещё.
  • Вместо того чтобы просить один запрос выполнять три задачи, архитектура предоставляет для каждого требования отдельный механизм: специалистов для разделения областей, общее состояние для контекста и человеческий узел для надзора.

    Пять основных компонентов

    Система состоит из пяти частей, каждая из которых имеет одну конкретную функцию:

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

    Это иерархическая или оркестрационная модель. Координатор делегирует задачи специализированным подагентам вместо того, чтобы один агент пытался выполнять все роли одновременно.

    Правило, обеспечивающее предсказуемость передачи задач

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

    Как сообщение перемещается по графу

    Следуется одно сообщение от пользователя. Оно сначала попадает на узел человека, затем передается маршрутизатору, который считывает текст сообщения; если сам по себе текст недостаточен для принятия решения, маршрутизатор обращается к полю last_active_agent в общем состоянии, чтобы выбрать между советником по тренировкам и советником по питанию. Выбранный советник может вызвать соответствующий инструмент своей области, после чего возвращает управление узлу человека, который снова приостанавливается и ждет следующего сообщения. Таким образом, цикл всегда состоит из узла человека, маршрутизатора, советника, необязательного инструмента и снова узла человека.

    Модель: LLM с открытыми весами на Groq

    Оба советника используют модель с открытыми весами openai/gpt-oss-120b, предоставляемую Groq, настроенную с параметром temperature=0:

    • Почему именно эта модель: у нее развитая встроенная функция вызова инструментов, от которой зависит рабочий процесс, основанный на инструментах.
  • Почему температура 0: это делает результаты максимально воспроизводимыми, так что график следует заложенным правилам, а не меняется от одной экспериментальной серии к другой. Это важно, когда кто-то неоднократно тестирует или оценивает один и тот же диалог. На практике даже при температуре 0 гарантия строгого детерминизма у хостинговых моделей отсутствует, поэтому в тестах следует проверять маршрутизацию и структуру, а не точные формулировки.
  • Почему Groq: его аппаратное обеспечение для инференса LPU позволяет сохранять низкую задержку, что имеет значение, когда один запрос пользователя запускает несколько этапов обработки (человек, маршрутизатор, советник, инструмент, человек) прежде чем появится ответ.
  • Ничто в графе не зависит непосредственно от Groq. Могут использоваться любые чат-модели с возможностью вызова инструментов, например Google Gemini или Anthropic Claude, при наличии соответствующего API-ключа (такого как GEMINI_API_KEY или ANTHROPIC_API_KEY) и указании модулем обертки на этого поставщика. Доступность моделей на хостинговых платформах меняется со временем, поэтому необходимо проверять идентификатор модели в актуальном каталоге поставщика.

    Общее состояние: где фактически хранится память

    Каждый узел читает и записывает данные в один объект типизированного состояния MultiAgentState, содержащий два поля:

    • messages: полный текст текущего диалога, унаследованный от MessagesState LangGraph; этот объект также предоставляет функцию для добавления новых сообщений вместо их перезаписи.
  • last_active_agent: значение должно быть "workout_advisor" или "nutrition_advisor"; маршрутизатор обращается к этому значению, когда не может определить, к какому домену относится запрос вроде "расскажите больше">.
  • Именно из-за этого состояния ограничение, указанное в одном ходе, продолжает действовать и в последующих ходах, даже если ответ генерируется другим узлом. Модели ничего не сводится к краткому изложению и не объясняется заново. Каждый советник просто получает тот же накопленный список messages при каждой своей работе, а механизм проверки сохраняет состояние между ходами в рамках одного диалога.

    Ключевым решением в проектировании является то, что память централизована, а не привязана к каждому агенту. Если бы у каждого советника была собственная закрытая история, советник по питанию никогда бы не узнал о цели, которую пользователь указал советнику по тренировкам.

    Один узкий инструмент на советника

    Каждый агент может вызывать ровно один инструмент, причем этот инструмент относится к его области деятельности. В данной версии инструменты основаны на правилах и сопоставляют ключевые слова, что делает их поведение предсказуемым и удобным для демонстрации. В производственной среде их можно заменить на реальные API для оценки фитнеса или питания, либо позволить модели полностью генерировать ответы. Поскольку остальная часть графа зависит только от интерфейса инструмента, замена реализации не требует других изменений.

    Сохранение каждого советника в своей зоне

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

    • Советнику по тренировкам указано не давать советы по питанию, поскольку граф направляет такие запросы советнику по питанию.
  • Советнику по питанию запрещено создавать программы тренировок, поскольку это относится к обязанностям Советника по тренировкам.
  • Основная идея заключается в том, что модель не сама определяет маршрут. Благодаря заданным ограничениям каждый агент предпочитает действовать в рамках своей компетенции, а не импровизировать, причем именно граф, а не большая языковая модель, прямо определяет все решения о маршрутизации.

    Маршрутизатор, основанный на ключевых словах, а не на повторном вызове большой языковой модели

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

    • Такие слова, как спортзал, повторения, кардио или мышцы, направляют запрос к Советнику по тренировкам.
    • Такие слова, как диета, пища, еда, белок или вегетарианство, направляют запрос к Советнику по питанию.
    • Все, что не соответствует ни одному из списков, передается тому советнику, который хранится в last_active_agent.

    Детерминистское маршрутизирование предсказуемо и легко проверяется: одно и то же сообщение всегда приводит к одному и тому же шагу, и его можно протестировать в виде модульных тестов без вызова модели. Однако у такого подхода есть недостаток — его хрупкость. Списки ключевых слов упускают синонимы, и сообщение, в котором упоминаются оба домена (например, вопрос о еде перед тренировкой), требует явного правила разрешения коллизий. Когда формулировки становятся слишком разнообразными для использования ключевых слов, разумным решением является применение простой системы классификации, но это снижает степень предсказуемости.

    Участие человека с возможностью прерывания и командами

    Самым важным элементом дизайна является не маршрутизатор, а человеческий узел. После каждого хода советника граф намеренно останавливается. Он не предполагает следующего вопроса пользователя и не продолжает генерацию. Для реализации паузы используются два примитива LangGraph:

    • interrupt(...) останавливает выполнение прямо в месте вызова и возвращает управление вызывающему коду вместе с переданными ему значениями.
    • Command(resume=...) продолжает работу приостановленного графа, передавая входные данные человека в качестве значения возврата от вызова interrupt в том же узле.

    Для реализации перерывов необходимо сохранение состояния: граф должен сохранять свое состояние в момент паузы, чтобы иметь возможность продолжить работу позже, поэтому скомпилированный граф требует указателя на точку сохранения и идентификатора потока.

    Для помощника по фитнесу и питанию такая пауза — это не просто удобство интерфейса. Это момент, когда пользователь может указать ограничения, связанные с безопасностью, такие как травма, аллергия или диетические ограничения, до того, как продолжится диалог, а не после того, как система уже выдала советы, игнорирующие их.

    Пошаговый анализ трех этапов

    Этап 1: начало диалога

    Пользователь указывает две цели — наращивание мышечной массы и снижение жира — и просит план тренировок. Каждый новый диалог всегда начинается с сервиса Workout Advisor. Он берет во внимание понятия «мышцы» и «жир», запускает соответствующий инструмент и возвращает структурированный план тренировок, например четырехдневную схему тренировок верхней/нижней части тела с 8–12 повторениями в 3–4 подхода, сочетающую три дня силовых тренировок и два занятия кардио.

    Этап 2: передача задачи на питание

    Далее пользователь хочет узнать, какие продукты питания помогут реализовать этот план. Узел, отвечающий за человека, пересылает сообщение, маршрутизатор находит опцию «есть», и выполнение передается советнику по питанию. Тот возвращает ежедневный план с калорийностью примерно 2500 ккал, ориентированный на нежирный белок и сложные углеводы, с приемами пищи каждые три–четыре часа.

    Этап 3: применение ограничения из общего состояния

    Наконец, пользователь упоминает, что является вегетарианцем, и просит скорректировать рекомендации по питанию, добавив перекус. Сообщение снова направляется консультанту по питанию, и здесь согласуются два фактора: само слово «вегетарианец» является ключевым термином в области питания, а last_active_agent по-прежнему указывает на вопросы питания, поэтому даже более расплывчатая просьба вроде «скорректируйте это и добавьте перекус» попадает в тот же раздел. Консультант перечитывает всю историю общения, соблюдает цель, поставленную в первом запросе, добавляет ограничение на вегетарианское питание и составляет план наращивания мышечной массы без мяса, основанный на тофу, панире и чечевице, с перекусами в виде эдамамэ и стаканчиков хумуса. Никому не пришлось повторять цель, поскольку она уже содержалась в текущем состоянии данных.

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

    Сборка машины состояний

    Структурно результатом является небольшая графика состояний с тремя узлами (двумя советниками и человеческим узлом) плюс маршрутизатор, выполняющий функцию условной логики между ними, а также одна фиксированная точка входа:

    • Каждый новый поток начинается с советника по тренировкам. Этот стандарт отражает особенности продукта: люди, которые открывают приложение для фитнеса, обычно сначала спрашивают о тренировках, а уже потом — о питании.
    • После первого ответа человеческий узел и маршрутизатор определяют каждый последующий шаг.
  • Граф строится с использованием контрольного указателя MemorySaver, и каждый диалог идентифицируется по thread_id. Каждый раз, когда вызывающий код снова использует тот же thread_id, граф автоматически восстанавливает сообщения и значение last_active_agent; вызывающий код никогда не передает историю заново.
  • MemorySaver хранит контрольные точки в памяти процесса, что идеально подходит для ноутбуков, но приводит к потере всей информации при перезагрузке. В реализациях, развернутых в производстве, следует использовать постоянный контрольный указатель с хранением в базе данных.

    Полная рабочая реализация доступна в виде сопутствующего ноутбука. Для других способов структурирования шагов маршрутизации и утверждения см. пять шаблонов LangGraph, охватывающих маршрутизацию, распространение данных, критику и утверждение.

    Повторное использование шаблона вне контекста фитнеса

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

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

    Принципы проектирования

    • Оркестрация вместо монолитов. Используйте граф для обеспечения разделения задач между специалистами вместо того, чтобы полагаться на одного агента для выполнения всего.
    • Централизация состояния. Один объект MultiAgentState, сохраняемый с помощью чекпоинта, обеспечивает плавную передачу задач и постоянство ограничений.
  • Контроль имеет решающее значение. Избегайте полностью автономных циклов в производственной среде; используйте interrupt(), чтобы обеспечить участие человека в ключевых моментах.
  • Основные выводы

    • Разделяйте работу по областям и предоставляйте решения по маршрутизации детерминистической логике графа, а не модели.
    • Сохраняйте одну общую историю messages вместе с информацией о last_active_agent, чтобы ограничения, установленные ранее, влияли на ответы, генерируемые позже другим агентом.
    • Рассматривайте interrupt() и Command(resume=...) как механизмы, позволяющие человеку добавлять ограничения безопасности перед следующим ответом, и помните, что для этого требуются указатель состояния и идентификатор потока.
    • Сохраняйте узкий набор инструментов за стабильным интерфейсом, чтобы логика, основанная на правилах, позже могла превратиться в настоящий API или полноценную модель для принятия решений без изменения структуры графа.
    • Самый быстрый способ усвоить эту структуру — это найти двух хорошо определенных специалистов среди своих сотрудников и создать полноценную рабочую схему; топология остается неизменной, в то время как подсказки и инструменты — это те элементы, которые можно адаптировать под свои нужды.