Опубликовано 30 июля 2026 г.
Оркестратор аварий - это граф с чекпойнтами, а не скрипт
Почему не скрипт
Сценарий обработки аварии магистрального линка выглядит линейно ровно до момента, когда его пробуешь написать. Мониторинг заводит тикет, надо разобрать описание, сходить на оба конца линка, снять оптику и конфигурацию интерфейса, сделать вывод, деактивировать порт, поставить задачу полевой службе - и ждать. Ждать можно час, а можно три дня: пока бригада доедет до муфты.
Скрипт этого не переживает. Он умрёт на первом деплое сервиса, а вместе с ним умрёт знание о том, что порт уже деактивирован и кто-то едет его чинить.
Поэтому сценарий собран как граф состояний на LangGraph, а состояние графа лежит в чекпойнтах PostgreSQL. Рестарт сервиса - это не потеря контекста, а пауза: процесс поднимается ровно на том узле, где остановился.
Решение 1: LLM не ходит на устройства
Соблазн отдать модели доступ к CLI огромный, и это ровно то, чего делать не надо. В системе жёсткое разделение:
- детерминированный код - подключение по NETCONF/PyEZ, парсинг вывода, применение конфигурации;
- модель - только текст и компактная JSON-выжимка диагностики, из которой она формирует заключение для инженера.
Всё, что меняет сеть, проходит через код, который можно прочитать и покрыть тестами. Модель влияет на формулировки и на вывод «похоже на обрыв волокна» - но не на commit.
Побочный эффект приятный: выжимка вместо сырых портянок вывода - это ещё и в разы меньше токенов.
Решение 2: commit confirmed вместо надежды
Junos умеет откатывать конфигурацию сам:
# таймер автоотката ставится на самом устройстве
cu.commit(confirm=10, comment="incident <ticket>: deactivate port")
# ... инженер подтверждает в интерфейсе ...
cu.commit() # фиксация; без неё устройство откатится само
Это меняет модель отказа. Если оркестратор упал, потерял сеть или ошибся - порт вернётся в исходное состояние без нас, потому что таймер живёт на устройстве, а не в нашем процессе. Человек в контуре при этом остаётся: подтверждение изменения - его действие, а не галочка в конфиге.
Решение 3: сначала dry-run
Первый этап проекта намеренно запущен без единого реального commit. Вместо применения конфигурации оркестратор считает diff и пишет в лог, что он собирался сделать:
WOULD COMMIT on <device-a>:
[edit interfaces xe-0/0/0]
+ inactive: gigether-options
Скучно - и именно поэтому работает. За время dry-run видно, как парсер спотыкается о нестандартные описания тикетов, где диагностика даёт ложный вывод, и какие связки «оба конца линка» отсутствуют в инвентаре. Всё это лучше узнать из лога, чем из инцидента.
Что осталось за скобками
Формат описания тикета не контрактный - его формирует шаблон системы мониторинга, и он меняется. Парсер устойчив к виденным вариантам, но при пустом списке интерфейсов останавливается и зовёт человека, а не угадывает. Это, пожалуй, главный принцип всей системы: автоматика имеет право отказаться работать.