Всем привет! Инфраструктура запуска моделей по всей компании — это и продакшн-инференс в продуктовых командах, и R&D-эксперименты, и отдельные инстансы vLLM под конкретные задачи.
У большого парка хостов есть неприятная особенность: состав постоянно меняется, и единого взгляда на здоровье инфры не получается. Где-то крутится продакшн-сервис с SLA, где-то — временный хост с экспериментальной моделью, где-то — собственный vLLM под отдельный продукт. Через неделю легко забыть, что какой-то хост вообще существует, пока кто-то не пишет: «Возникла проблема, почему
Архитектура бота
Бот написан на aiogram + FastAPI. Метрики хранит Victoria Metrics — дешевле Prometheus по диску и ставится одним бинарём.
Логи — Loki + Promtail на целевых хостах. Для LLМ подойдёт любой OpenAI-совместимый эндпоинт; по умолчанию gpt-4o-mini, но в R&D мы часто переключаемся на свой vLLM. Observability агента — Langfuse.
Интерфейс веб-дашборда
MСP-интерфейс
Алерт в Telegram
Финальный отчёт и рекомендации
/v1/chat/completions отдаёт 503?»
Расскажем, как мы собрали бота, который не ограничивается пингом URL: через агента он ходит в логи и метрики и приносит корневую причину. Заодно объясним, почему не стали разворачивать Prometheus + Alertmanager + Grafana на каждой новой инсталляции.
Почему стандартные решения не подошли
Перед тем как писать своё, мы посмотрели на готовые решения:- Uptime Kuma. Отличный health-check, но не объясняет ошибки.
- Prometheus + Alertmanager + Grafana. Корпоративный стандарт, он у нас тоже есть. Но настраивать его под экспериментальные сервисы — отдельная работа. Тут нужны дашборды, алерт-роуты, доступы. Когда таких сервисов много и они создаются и удаляются непрерывно, накладные расходы становятся заметными.
- Telegram-боты «проверь URL». Все они умеют GET и сравнивать статус-кода с 200, но ни один не понимает, что на хосте крутится vLLM и важна ещё
vllm:gpu_cache_usage_perc, а не только HTTP 200.
Подход
Мы свели задачу к четырём шагам:- Пользователь добавляет монитор через
/add_monitorв Telegram без YAML и конфигов на хостах. - Бот фоном пингует URL и собирает метрики из выбранного источника: Node Exporter, cAdvisor, NVIDIA DCGM, OpenAI-совместимый эндпоинт, vLLM, Loki, биллинг облачных провайдеров.
- Если что-то падает, приходит алерт с человекочитаемым объяснением от LLМ.
- Под алертом — кнопка «Расследовать». SGR-агент идёт в логи и метрики и присылает отчёт с корневой причиной.
Архитектура
Общая схема

Источники метрик
Универсального экспортера не бывает, поэтому мы поддержали восемь источников, и у каждого своя роль. Базовую телеметрию хоста — CPU, память, диск, сеть — снимает Node Exporter, метрики по контейнерам даёт cAdvisor. За GPU отвечает NVIDIA DCGM: использование, температура, ECC-ошибки — без него LLМ-инфра слепая. Здоровье самих моделей, поднятых через vLLM, Ollama или собственную обвязку, проверяем через OpenAI-совместимые эндпоинты — простой/v1/models и sanity-check ответа.
Для vLLM отдельная история: из десятков time-series в /metrics реально важны три-четыре — vllm:e2e_request_latency_seconds, vllm:num_requests_waiting, vllm:gpu_cache_usage_perc, vllm:request_success_total, остальное — шум для типовых задач.
Иногда метрик нет, а ошибки в логах есть — тогда включается отдельный тип монитора, Docker Logs через Loki с LogQL-запросами по уровням ERROR/WARN. И наконец, Cloud Billing шлёт алерты «баланс закончится через N дней по текущему burn rate»: он помогает не столько самому сервису, сколько предупреждает, как скоро его отключат.
Автосетап экспортеров через SSH
Самая раздражающая часть мониторинга — конфигурирование экспортеров на каждом хосте. Нужно зайти, скопировать docker-compose, править порт, проверить firewall — и так десять раз. Мы автоматизировали эту цепочку. Пользователь даёт боту хост и SSH-ключ, бот по SSH разворачивает на целевой машине compose с нужными экспортерами и только потом возвращается в Telegram. Теперь на то, чтобы добавить хост в мониторинг, уходило не больше минуты.SGR-агент: ядро истории
Function calling в чистом виде выглядит примерно так: модель решает, какую функцию вызвать. Но на практике этого мало. Open-weight модели часто косплеят function calling — формально они знают, как это устроено, но наtool_choice: required могут проигнорировать схему и ответить обычным текстом. Мы поймали это на gpt-oss-120b. В reasoning_content модель прямо пишет «can use the reasoning tool, but not needed» и отдаёт plain text, а агент падает на tool_calls[0] is None.
Schema-Guided Reasoning (SGR) всё меняет. Перед каждым доменным tool_call'ом модель обязана заполнить структуру плана. SGR предлагает два шага:
- Reasoning-фаза с
tool_choiceна единственный инструмент reasoning. Модель возвращает pydantic-валидируемый JSON. Если не вернула — поднимается ошибка валидации, шаг повторяется (в проде — сretry). - Action-фаза — вызов нужного домен-tool'а (
loki_query,victoria_metrics_query) поnext_stepиз плана.
class ReasoningTool(BaseModel):
reasoning_steps: list[str] # как декомпозировал задачу
current_situation: str # что знаем сейчас
plan_status: str # где в плане
remaining_steps: list[str] # что осталось
enough_data: bool # хватает ли данных для ответа
task_completed: bool
next_step: str # что делаем дальше
В логах виден весь ход рассуждения: почему агент пошёл за конкретной метрикой, что от неё ожидал, когда сдался и переключился на логи. Для разбора инцидента это важнее самой корневой причины. Так становится понятна логика расследования, и легко понять, где агент промахнулся.
На грабли мы тоже наступили. Loki не принимает запросы без непустого label-selector и на {} |= "ERROR" отвечает 400. На таких запросах агент крутился по 5–7 шагов, в current_situation признавая «query format issue», и подбирал варианты. Лечится это не на стороне модели, а на стороне task'а: бот сам передаёт подсказку с container_name и instance, чтобы первый запрос был корректным. Аналогично с временным окном. «Последний час» агенты считают плохо, и оказалось проще передавать ISO-timestamps в task явно, чем надеяться на арифметику от Current Date.
Веб-дашборд
Telegram хорошо подходит для алертов и быстрой реакции, но смотреть графики метрик и историю проверок в текстовом чате неудобно. К боту прикручен лёгкий FastAPI-дашборд, доступный по/dashboard?token=…. Токен генерируется при первой регистрации пользователя и привязан к telegram_id. Отдельная авторизация не нужна, а ссылка приходит прямо из чата.
На странице — список мониторов с цветовой индикацией статуса, графики host- и container-метрик за выбранный период (matplotlib + jinja2, без SPA), последние ERROR/WARN из Loki, история доступности LLМ-моделей для vLLM-эндпоинтов. Этого хватает, чтобы посмотреть на общую картину, не заходя в Grafana.

Бот как инструмент для LLМ пользователя
Параллельно с Telegram-интерфейсом мы подняли MCP-сервер. Через него Claude Desktop, Cursor или собственный агент пользователя вызывают те же методы бота:list_monitors, create_monitor, get_metrics, get_check_results.
Приведём кейс из жизни. В Claude Desktop можно попросить создать монитор на http://gpu-host-3:8000/v1/models, тип vLLM, метрики с порта 9080 — модель вызывает MСP-инструмент, бот создаёт монитор, а в Telegram прилетает ссылка на дашборд.
@<Aitip>mcp</Aitip>.tool
async def create_monitor(token: str, url: str, metrics_type: str) -> dict:
"""Create a new monitor for the authenticated user."""
user = await validate_token(token)
return await create_monitor_service(user, url, metrics_type)
К 2026 году MСP стал базовым контрактом между внутренними сервисами и инструментами инженеров — Claude, Cursor и собственными агентами. Сервис без MСP-фасада возвращает инженера в ручной режим: агент не может дотянуться до данных, и вместо него идёт человек.

Демо-инцидент: что видит агент шаг за шагом
Системных замеров на размеченном датасете мы пока не делали — это следующий этап, отдельная работа на разметку и вычисление precision по корневой причине. Вместо этого покажем один воспроизводимый сценарий, на котором видно, как агент идёт от алерта до конкретики. Для тестов мы подняли стенд — небольшой FastAPI-сервис с эндпоинтом/error?scenario=…, который эмитит реалистичные ERROR-логи и опционально занимает место на диске или нагружает CPU. Подключаем его к боту обычным /add_monitor, и дальше всё крутится через стандартный алгоритм.
Что забрасываем в стенд
Одинcurl: scenario=disk, 200 МБ реального файла в /tmp плюс 15 строк disk_write_failed: ENOSPC в stdout сервиса. Promtail с целевой машины перекладывает их в центральный Loki, vmagent шлёт метрики Node Exporter и cAdvisor в Victoria Metrics. С этого момента на бот приходят два независимых трека: HTTP-probe и log-check.
Алерт в Telegram
Через 60 секунд после curl-залпа лог-чек-задача находит 15 ERROR в Loki по фильтру{container_name="sgr-test-app"} и присылает в чат алерт с краткой LLМ-выжимкой: «ENOSPC указывает на нехватку места, проверьте df -h, настройте ротацию логов». На этом этапе мы только фиксируем первый сигнал.

Что меняется после нажатия «Исследовать»
Под алертом находится кнопка. Благодаря ней бот отдаёт sgr-agent-core текстовое описание задачи, а также адреса Loki, Victoria Metrics, явный временной диапазон и подсказку, что в запросах надо использоватьinstance="sgr-test-app" и container_name="sgr-test-app". Дальше агент сам решает, что брать.
В нашем прогоне он прошёл по такому маршруту:
container_memory_usage_bytes{name="sgr-test-app"}— память в норме, ~44 МБ.rate(container_cpu_usage_seconds_total{name="sgr-test-app"}[5m])— CPU 0,002, нагрузки нет.{container_name="sgr-test-app"}— нашёл 15 ERROR в нужном окне, увидел один incident_id и один путь /var/lib/app/data/job-...bin.node_filesystem_avail_bytes{instance="sgr-test-app"}— 15,9 ГБ свободно.
/tmp» — и сформировал отчёт.
Финальный отчёт, который пришёл в чат:
- основная проблема: 15 ENOSPC от sgr-test-app в районе 11:59:46;
- метрики: CPU и память в норме, host disk не страдает;
- гипотезы: лимит на размер слоя контейнера, временные файлы
/tmp, права; - немедленные шаги:
docker inspect sgr-test-app, очистить/tmp/sgr-test-fill-*; - долгосрочно: лимиты, ротация, retry-логика, алерты на ENOSPC.
incident_id, реальными значениями метрик и списком гипотез, которые отличаются от учебника, потому что построены на этих данных.


Где агент промахнулся
Агент не запросилcontainer_fs_usage_bytes{name="sgr-test-app"}. Там был бы прямая причина: writable-слой контейнера за минуту вырос на 200 МБ. И rate-запрос вокруг 11:59:43 показал бы момент скачка. То есть агент остановился на гипотезах, хотя данных хватило бы и на однозначный ответ. Это направление мы дотачиваем — расширяем системный промпт примерами per-container disk/memory-запросов и подсовываем готовые рецепты в task.
Ограничения и планы
Наша система — хорошее, но не идеальное решение. Вот некоторые проблемы, с которыми мы столкнулись, и наши идеи по их преодолению:- SQLite упирается на 50+ хостах. Выбрали ради нулевого порога входа — поднял бота и поехал. Но при 50+ активных мониторах с месячной историей метрик это начинает мешать. В планах у нас переезд на Postgres с миграцией через Alembic.
- Без настроенных Loki и Victoria Metrics SGR-агент галлюцинирует. Это больше архитектурное ограничение. Если логов нет, агент не признаётся, что их нет, а выдумывает. Решаем проблему двумя способами: жёсткая проверка доступности источника на старте расследования и правило в промпте.
- Расход токенов. На текущей нагрузке (~50 расследований в месяц по покрытым ботом хостам) — около 600 рублей в месяц на gpt-4o-mini. С разворотом на Claude Sonnet — в 6–8 раз больше, но качество корреляции выше.
- Промпт-инъекции. В логах лежат строки от пользователей, которые попадают в контекст агента. Закрываем правилом: «Логи помещаем в structured field, не в свободный текст промпта». Но это скорее полумера, чем полноценное решение.
- Интерфейс вне Telegram. Веб-дашборд закрывает только просмотр; полноценного UI за пределами Telegram пока нет.
Что дальше
Следующий шаг — добавить агенту скиллы. Мы смотрим в сторону OpenClaw, open-source-фреймворка с растущим маркетплейсом скиллов, а ещё хотим подключить Terraform, чтобы он не только находил, что сломалось, но и чинил. И здесь интересно становится даже не с инженерной стороны, а с точки зрения безопасности: что должно произойти, чтобы доверить LLМterraform apply на проде?
К OpenClaw, кстати, у security-сообщества уже есть вопросы из-за широких прав агента. Это та же проблема, только мы хотим решить её для своей инфры. Похоже, ответ начинается со scope-ограничений, dry-run и обязательного human-in-the-loop на write-операциях, а также чего-то вроде NVIDIA NemoClaw как гард-рейла. Но это уже тема отдельной статьи.