
Привет! Если ты, как и я, держишь инфраструктуру небольшой команды, наверняка знаком с ситуацией: разработчиков становится больше, а DevOps-отдел при этом не растёт. С приходом vibe-coding’а эта диспропорция стала особенно заметной — у нас в студии команда разработки выросла раза в полтора буквально за пару месяцев, потому что каждый продакт-менеджер захотел свой мини-аппликейшен. Параллельно подкинули головной боли участившиеся проблемы с доступностью приложения из ряда регионов.
В результате поток обращений в канал поддержки в Mattermost вырос настолько, что значительная часть рабочего дня инженера стала уходить на их разбор. И самое неприятное — далеко не каждое обращение по итогу оказывалось в зоне ответственности DevOps, но каждое требовало хотя бы поверхностной диагностики, чтобы это понять.
Так появилась идея переложить первую линию на AI-агента. К тому моменту мы уже автоматизировали анализ инцидентов по сработавшему алерту (об этом я писал в предыдущей статье), и опыт показал, что подход рабочий. Логичным продолжением стало расширение схемы на ручные обращения в чате.
В этой статье — первой из двух — расскажу, как мы:
-
классифицировали поток обращений за последний год и выделили категории, которые имеет смысл автоматизировать;
-
реализовали классификатор на n8n с интеграцией Mattermost через MCP;
-
построили ассистента по CI/CD-инцидентам как первую боевую ветку обработки;
-
научились разбирать вложения (скриншоты ошибок, файлы с логами);
-
настроили нормальную отчётность об ошибках в самих workflow.
Во второй части разберём оставшиеся ветки: помощник по инцидентам в инфраструктуре, ассистент по типовым вопросам и обработка задач на модификацию.
Все workflow и системные промпты выложены отдельно — ссылка в конце статьи.
Подготовка: классификация обращений
Прежде чем что-то автоматизировать, нужно понять, что именно. Я выгрузил историю обращений из каналов Mattermost за последний год и разнёс их по категориям. Получилась такая классификация:
-
Модификация инфраструктуры — изменения в текущей инфраструктуре, добавление типовых ресурсов.
-
Новая инсталляция — развёртывание новых систем и интеграций, которых раньше не было.
-
Инцидент — что-то перестало работать в текущей инфраструктуре.
-
CI/CD — падающие сборки, неудачные тесты и деплои.
-
Вопрос — общие вопросы по нашей инфраструктуре.
-
Анонс — сообщения информационного характера: например, о плановых техработах.
-
Другое — всё, что не попало ни в одну из категорий выше.
Основная масса запросов оказалась в категориях 3–5 — именно их мы и взяли в работу в первую очередь. Категории 1–2 требуют согласований и почти всегда участия инженера, так что автоматизировать их смысла нет. Анонсы агенту обрабатывать не нужно — достаточно корректно их распознавать и не дёргать дежурного.
Но прежде чем строить ветки обработки, нужно было научиться достоверно определять, к какой категории относится новое обращение.
Классификатор

На первый взгляд всё выглядело просто: создаём бота в Mattermost, настраиваем webhook в n8n на сообщения с упоминанием этого бота, прогоняем через LLM, получаем категорию.
Пример

Но дальше всплыл первый нюанс: пришедшее сообщение может быть как стартовым в новом треде, так и репликой внутри уже существующего. И во втором случае без контекста треда классификатор будет ошибаться — например, уточнение «а у меня та же проблема» вне контекста выглядит как нечто бессмысленное.
Вместо того чтобы дёргать Mattermost API напрямую из n8n, я переложил эту задачу на агента — благо у нас уже есть mattermost-mcp, который умеет читать сообщения в каналах и тредах. Агент сам решает, нужно ли подтянуть историю треда, и подтягивает её, если контекст того требует. В системном промпте необходимо описать как это сделать, а также еще несколько моментов
-
Описания категорий и примеры.
-
Ожидаемый формат выходных данных
{
"category": "<category_key>",
"confidence": <0.0-1.0>,
"summary": "<one sentence summary in the same language as the user message>",
"acknowledge": "<a short response that you accepted the request and started working on it>",
"is_thread": <true|false>,
"parent_post": "<post_id to use as root_id when replying — ALWAYS set>"
}
В user промпте я дополнительно передаю channel_id, channel_name, post_id, user_name это поможет классификатору лучше ориентироваться в сообщениях
В качестве модели использую Sonnet или GPT-5 Codex — на классификации обе показывают сопоставимое качество.
На этом этапе я ещё не подключал разбор вложений — скриншоты и файлы с логами появятся дальше, в логике конкретных веток.
После того как ответ агента получен и провалидирован на корректность полей, нужно определить дежурного инженера — он может понадобиться, если автоматизированная обработка не справится. Дежурства у нас ведутся в Google Calendar, поэтому пришлось настроить OAuth2-доступ к нему по документации n8n.
Пример


После определения категории и дежурного запускается соответствующий sub-workflow. Параллельно с этим в Mattermost уходит сообщение-acknowledge, чтобы автор обращения видел: запрос принят и обрабатывается. Это важная деталь — без неё человек продолжает писать в тред «эй, кто нибудь смотрит?», что портит всю задумку.

Помощник по CI/CD
CI/CD — одна из самых частых категорий обращений, поэтому с неё и начал. Внушительная доля таких проблем решается без участия инженера: упавшие из-за временной недоступности репозитория сборки, флаки-тесты, неправильно сконфигурированные пайплайны, истёкшие токены.

На входе sub-workflow ожидает структуру с данными об обращении:
{
"message": "Запрос в чате",
"post_id": "id поста в Mattermost",
"channel_id": "id канала в Mattermost",
"channel_name": "Название канала в Mattermost",
"user_name": "Имя отправителя",
"user_id": "id отправителя",
"file_ids": ["Список id вложений"],
"category": "Одна из категорий",
"confidence": "Уровень уверенности в выборе",
"summary": "Краткое описание обращения",
"is_thread": "Сообщение пришло из треда или нет",
"thread_root_id": "Начальное сообщение в треде, если оно есть",
"on_call_user": "Имя дежурного"
}
Разбор вложений
Чаще всего обращение по CI приходит в формате «упала сборка» + скриншот ошибки. Такого описания явно недостаточно, чтобы найти конкретную сборку, поэтому перед вызовом основного агента стартует вспомогательный sub-workflow — attachmentsAnalyzer.
Он разбирает прикреплённые файлы:
-
Изображения (скриншоты ошибок) — отправляются в
gpt-4o-miniдля извлечения текста и описания контекста. -
Текстовые файлы (логи) — если размер не превышает заданный в
Config-ноде лимит, содержимое прокидывается дальше как дополнительный контекст.
На выходе формируется компактный текстовый блок:
{
"attachments_context": "...human-readable block...",
"attachments_count": 1
}
Я вынес этот функционал в отдельный workflow намеренно — он переиспользуется и в других ветках обработки. Если attachmentsAnalyzer падает с ошибкой, основной workflow продолжает работу без доп. контекста, а не валится целиком.

Сбор контекста и вызов агента
Перед формированием запроса к LLM в ноде SetVars собираю всё необходимое:
-
исходные данные обращения;
-
результат работы
attachmentsAnalyzer; -
вспомогательный контекст для системного промпта: имена организаций в GitHub, контексты и namespace’ы в Kubernetes, названия источников данных в Grafana.
Сам агент работает с набором MCP-инструментов:
-
GitHub MCP — доступ к логам сборок в Actions, PR, исходному коду.
-
Mattermost MCP — чтение сообщений в треде (если контекста изначального запроса недостаточно).
-
Grafana / Kubernetes MCP — поиск логов и событий кластера при проблемах с деплоем.
Что следует осветить в системном промпте:
-
Какие есть команды и какие группы репозиториев им принадлежат – это поможет быстрее определить репозиторий с ошибкой
-
Как искать дополнительный контекст в треде Mattermost
-
Описание зоны ответственности DevOps отдела – если ошибка попадает в эту зону ассистент будет дополнительно тэгать дежурного инженера в конце расследования
-
Общее описание доступных инструментов и когда их использовать.
-
Несколько примеров
-
Формат выходных данных
В качестве пользовательского промпта используется конструкция
Расследуй проблему от {{ $json.user_name }} в канале {{ $json.channel_name }}{{ $json.is_thread ? ' (сообщение в треде, thread_root_id=' + $json.thread_root_id + ' — сначала прочитай историю через Mattermost get_thread)' : '' }}
{{ $json.message }}{{ $json.attachments_context && $json.attachments_context.trim().length > 0 ? 'nnДополнительная информация из вложений:n' + $json.attachments_context : '' }}
Здесь помимо самого сообщения передается от кого и в каком канале пришло сообщение, пришло ли сообщение из треда, а также вложения, если они есть.
Костыль с HTTP-проверками
Одна из частых причин падения сборок — недоступность внешнего ресурса: репозитория с зависимостями, прокси, registry. Логично было бы дать агенту встроенный HTTP Request-tool и попросить проверить доступность. На практике это не сработало: встроенная нода n8n не умеет нормально обрабатывать таймауты и сетевые ошибки — в случае проблем она роняет всю цепочку с агентом.
Пришлось обернуть проверку в отдельный sub-workflow httpProbeTool, который всегда возвращает структурированный результат: успешно, неуспешно с описанием причины, или таймаут. Агент использует его как обычный инструмент.

После получения ответа от агента идёт короткая валидация формата, и сообщение уходит в тред Mattermost.
Обработка ошибок самого workflow
Когда строишь систему, через которую идут реальные обращения, надёжность критична. Если workflow упадёт по любой причине — закончилась квота у LLM, отвалился MCP-сервер, прилетел невалидный JSON — никто в чате об этом не узнает, и обращение зависнет в подвешенном состоянии.
Особенно это актуально в первые недели после запуска, когда постоянно что-то правишь и докручиваешь. Решение простое: отдельный workflow, который указан в настройках основных workflow как Error Workflow.

Что он делает:
-
Получает информацию о сломавшемся execution от n8n API (для этого надо сгенерировать API-ключ).
-
Через ноду
Extract Thread Contextопределяетchannel_idиroot_idисходного сообщения. -
Отправляет краткое сообщение об ошибке прямо в тред обращения, чтобы автор не висел в неведении.
Пример



Дополнительно в более развёрнутом виде ошибка летит в служебный канал DevOps-команды — это позволяет быстро реагировать на регрессии.
Что получилось
После пары месяцев тестирования в боевом режиме картина такая:
-
Среднее время ответа — до 3 минут с момента появления сообщения в канале.
-
~25% обращений закрываются полностью без участия инженера.
-
~40% обращений решаются быстрее обычного — агент проводит предварительную диагностику, дежурный получает уже готовый контекст.
-
Стоимость при нашем потоке (несколько десятков обращений в неделю) — до 250$ в месяц на оплату LLM.
Расследование упавшего билда

Расследование инцидента по скриншоту

Ответ на вопрос об инфраструктуры

Создание задачи по запросу


На фоне часовой ставки инженера это выглядит как очень выгодное пополнение в команде — особенно с учётом того, что агент работает круглосуточно и не отвлекает дежурного от основной работы.
Чего пока не удалось
-
Агент иногда «теряется» в длинных тредах с десятками сообщений — приходится в системном промпте отдельно ограничивать глубину контекста.
-
Категория
Инцидентпока даёт самый низкий процент автономного разрешения — там слишком много нестандартных ситуаций. Работаем над расширением набора MCP-инструментов. -
Сложно объективно оценить качество ответов на «вопросы по инфраструктуре» — нужен механизм фидбэка от инженеров (планирую реакции в Mattermost как простейший сигнал).
Во второй части разберём оставшиеся ветки обработки обращений: ассистента по инфраструктурным инцидентам, ассистента по типовым вопросам с RAG-поиском по нашей документации, и обработку задач на модификацию инфраструктуры с автоматическим заведением тикетов.
Репозиторий с workflow: https://github.com/javdet/automagicops-workflows
А как вы у себя разгружаете первую линию? Используете готовые продукты или строите своё? Какие категории обращений у вас лучше всего автоматизируются — поделитесь в комментариях, очень интересно сравнить распределение.
Автор: javdet12


