Как разделить агента поддержки на узлы intake, knowledge и support
Почему агент поддержки LEPA устроен как граф узких специалистов, а не одна большая функция, как LangGraph передаёт работу через общее состояние и что здесь значит «мультиагентность».
К этому моменту агент поддержки 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: настоящих шлюзов одобрения в проекте пока нет, и это не то же самое, что уточняющие вопросы.