Главная / Статьи / Как разделить агента поддержки на узлы intake, knowledge и support

Как разделить агента поддержки на узлы intake, knowledge и support

Почему агент поддержки LEPA устроен как граф узких специалистов, а не одна большая функция, как LangGraph передаёт работу через общее состояние и что здесь значит «мультиагентность».

792 слов

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

Представьте стройку дома

Слово "мультиагентный" звучит как научная фантастика. Полезнее другая картинка: стройплощадка, где над одним проектом работают несколько бригад:

Electrician
Plumber
Painter
Inspector

Разные задачи, разные навыки, один дом. Процесс поддержки можно разделить точно так же:

Someone understands the request
Someone finds the handbook page
Someone writes the reply

Такое разделение оправдано, только если задачи действительно разные. Лишние прямоугольники на схеме в презентации не в счёт.

Граф в этом проекте

Скомпилированный граф работает так:

START
  → intake
  → classify
  → conditional
        ├─ ask_clarification → END
        └─ knowledge → support → END

Intake, knowledge и support в структуре проекта называются агентами, потому что у каждого одна понятная задача. Classify и шаг с уточняющим вопросом лежат в папке nodes/: идея графа та же, просто папки названы чуть иначе. "Мультиагентность" здесь означает специализированные узлы внутри одного скомпилированного StateGraph. Это не отдельные развёрнутые сервисы и не агенты, которые спорят друг с другом. Названия папок важны меньше, чем разделение ответственности.

Чем занят каждый специалист

Intake

Intake берёт последнее сообщение пользователя и выдаёт user_role:

"I'm a teacher — how do I take attendance?"
        ↓
user_role = teacher

Он не ищет по документации и не пишет итоговый ответ. Его единственная задача — подготовить контекст для маршрутизации.

Classify и ветка уточнения

Classify определяет тему и решает, хватает ли данных, чтобы идти дальше:

"help" → ask for the LEPA area
"How do I enter grades?" → continue to knowledge

Эта развилка и есть оркестрация: система та же, меняется только следующий специалист.

Knowledge

Knowledge никогда не общается с пользователем. Он только достаёт документы:

query + issue_category
        ↓
search_knowledge_impl  (keyword Markdown search)
        ↓
retrieved_documents

В том же модуле есть обёртка @tool под названием search_knowledge для будущих экспериментов, но граф вызывает саму реализацию напрямую.

Support

Support никогда не лазит по файловой системе. Он читает retrieved_documents и формирует final_answer: по умолчанию из шаблона, а если LLM настроена, она может этот ответ причесать.

Как LangGraph держит порядок

Никакая LLM-менеджер не решает на ходу, кто говорит следующим. Связи заданы явно:

graph.add_edge(START, "intake")
graph.add_edge("intake", "classify")
graph.add_conditional_edges("classify", route_after_classify, {...})
graph.add_edge("knowledge", "support")

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

Intake does not call Support.
Knowledge does not call Classify.
They write to state.
The graph decides the next node.

Почему не один агент?

Можно было обойтись одной функцией:

def handle(message):
    detect role
    classify
    maybe clarify
    search docs
    write answer

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

  • Тестировать классификацию, не трогая поиск по документам
  • Отлаживать неверный ответ отдельно от неверного маршрута
  • Позже заменить один шаг, например поставить LLM-классификатор или поиск получше, не переписывая всё остальное
  • Объяснять систему по простой схеме

Главный выигрыш мультиагентного подхода здесь — ясность.

Чем это не является

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

Вся идея в трёх строках

Мультиагентная оркестрация в агенте поддержки LEPA сводится к следующему:

Specialize the jobs.
Share state.
Let the graph choose the next job.

Intake, knowledge и support разделены, потому что у них разные обязанности, а LangGraph делает передачу работы между ними видимой. Дальше по теме — human-in-the-loop: настоящих шлюзов одобрения в проекте пока нет, и это не то же самое, что уточняющие вопросы.