Skip to main content
Glama

ML On-Call Agent

«Почему вчерашний прогон регрессировал?» — мультиагентная система, которая отвечает на этот вопрос, сопоставляя отчёт о дрейфе, eval-прогон и журнал деплоя, и цитирует каждое своё утверждение.

Доступно через MCP, поэтому инструменты работают из любого MCP-клиента.

Статус: 65 тестов. Диагноз вычисляется детерминированно на основе взвешенных свидетельств, поэтому каждое число ниже — это утверждение, а не демонстрация.


Настоящая проблема

ML-система деградирует незаметно. Когда кто-то наконец замечает, улики разбросаны по трём местам, которые не общаются друг с другом:

  • отчёт о дрейфе — сдвинулось ли распределение входных данных?

  • eval-прогон — ухудшились ли офлайн-метрики, и на каких срезах?

  • журнал деплоя — выпускал ли кто-нибудь что-нибудь?

Сопоставление их — это работа, и люди делают её плохо в 3 часа ночи, потому что это значит держать в голове три JSON-файла одновременно.

Эти три артефакта не выдуманы для этого репозитория. Это то, что уже производят model-drift-monitor, llm-eval-pipeline и ai-code-review-bot. Читатель был написан на основе реального артефакта, а не документации.


Результат

python -m oncall.cli evaluate
 scenario         truth           verdict          conf  margin  steps  action
 healthy          healthy         healthy          0.57     2.0      9  no action
 data_shift       data_shift      data_shift       0.71     8.0      9  retrain on recent data
 bad_deploy       bad_deploy      bad_deploy       0.66     8.5      9  roll back
 concept_drift    concept_drift   concept_drift    0.81     9.0      9  retrain with fresh labels
 pipeline_break   pipeline_break  pipeline_break   0.62     5.0      9  fix the upstream pipeline
 flaky_eval       noise           noise            0.62     3.0      9  rerun the evaluation

task success    : 100%
action accuracy : 100%
mean steps      : 9.0

Истина известна по построению, поэтому агент оценивается, а не восхищается. «Агент выдал правдоподобный отчёт об инциденте» — это не результат: правдоподобие — это то, что делают языковые модели, в том числе когда свидетельства ничего не подтверждают.

Сценарий, который является сутью

concept_drift: входные данные статистически идентичны, ничего не было задеплоено, а ранжирование модели инвертировалось (AUC 0.729 → 0.326, хуже случайного).

Не на что указать. Ответ получается из комбинации двух отрицаний и одного положительного — нет дрейфа, нет деплоя, ранжирование рухнуло — именно то, что упустило бы резюме любого отдельного артефакта. Это также слепое пятно, которое model-drift-monitor документирует о себе, диагностированное на уровень выше.


Ключевое проектное решение

Диагноз детерминирован. Языковая модель только повествует.

Очевидная реализация — вставить три JSON-файла в промпт и спросить, что пошло не так. Это каждый раз даёт связный текст — в том числе когда свидетельства ничего не подтверждают — и это нетестируемо, потому что нельзя делать утверждения о прозе или отличить правильный ответ от случайного.

Поэтому рассуждение — это обычный Python:

  • каждый специалист читает один артефакт и выдаёт Finding

  • каждый finding несёт цитату — файл, поле, значение. Finding требует её, поэтому утверждение без источника невозможно сконструировать

  • каждый finding указывает, какие корневые причины он поддерживает, а какие исключает

  • diagnose() суммирует веса — чистая функция, покрытая юнит-тестами против истины

| finding                                                    | source                     |
|------------------------------------------------------------|----------------------------|
| input drift is 'none': the scored population is             | `drift:severity=none`      |
| statistically the same as training                          |                            |
| roc_auc is 0.326 - WORSE THAN RANDOM. The model's ranking   | `evals:metrics.roc_auc     |
| has inverted, which is a changed relationship               |  =0.326`                   |
| 1 change(s) landed but none touch model behaviour           | `changes:changes[].files`  |

test_the_narration_does_not_change_the_diagnosis утверждает, что вердикт идентичен с моделью и без неё. Если он когда-нибудь упадёт, значит, модель начала выполнять рассуждение — и рассуждение перестаёт быть тестируемым в тот момент, когда это происходит.

Отрицательные свидетельства — это источник большей части диагностической силы. «Нет дрейфа» и «ничего не было задеплоено» — это findings с весами. LLM, резюмирующая JSON дрейфа, пропустила бы их, потому что ничего не произошло.


Граф

   SUPERVISOR ──► drift ──┐
        ▲   ├──► evals ───┤   one specialist per artifact, each consulted once
        │   └──► changes ─┤
        │                 ▼
        │             DIAGNOSE          sum the weighted findings
        │                 ▼
        └──────────────  CRITIC         "is this conclusion supported?"
           (bounded)      ▼
                        REPORT

Два свойства, которых нет у линейного конвейера:

Критик может отправить работу обратно. Если вердикт опирается на два артефакта, а третий так и не был прочитан, управление возвращается супервизору, а не публикуется вывод, сделанный на основе половины свидетельств.

Цикл ограничен. MAX_REVISIONS = 2 ограничивает его, а recursion_limit ловит всё, что вырвалось. Агент, который может зациклиться, — это агент, который может зациклиться навсегда, а вышедший из-под контроля on-call агент генерирует страницы вместо того, чтобы отвечать на них.

Он работает без LLM и без API-ключа — супервизор маршрутизирует по простому правилу Python — поэтому маршрутизация, делегирование и обратная связь критика покрыты юнит-тестами офлайн. test_the_graph_and_the_plain_loop_agree утверждает, что прогон LangGraph и простой цикл for достигают идентичных вердиктов на каждом сценарии, что доказывает: граф добавляет оркестрацию, а не рассуждение.


Сколько на самом деле стоит каждый артефакт

python -m oncall.cli ablate

свидетельства

успех задачи

точность действий

все три

100%

100%

без дрейфа

67%

67%

без eval

67%

67%

без изменений

100%

100%

Честный отрицательный результат, и он зафиксирован, чтобы его нельзя было тихо забыть. Удаление журнала изменений ничего не стоит на этом наборе сценариев — bad_deploy уже отделим по свидетельствам дрейфа и eval. Журнал изменений оправдывает своё место тем, что называет коммит — это то, что нужно человеку для действий, а не изменением диагноза.

test_the_change_log_currently_changes_no_verdicts фиксирует это. Если будущий сценарий сделает его критически важным, тест упадёт, и эту таблицу придётся изменить. Именно для этого и существует утверждение.

Абляция — единственный способ обнаружить, что источник, на интеграцию которого вы потратили неделю, не меняет ни одного вердикта.

И когда артефакт пропадает

Джобы падают, бакеты пусты, пути меняются. Интересный вопрос не в том, ответит ли агент — он ответит — а в том, заметит ли он:

  missing drift     -> verdict bad_deploy   flagged=True
  missing evals     -> verdict bad_deploy   flagged=True
  missing changes   -> verdict bad_deploy   flagged=True

Агент, который тихо диагностирует по двум из трёх файлов, хуже того, кто отказывается, потому что никто не знает, что ему нельзя доверять.


MCP-сервер

python -m oncall.mcp_server                 # stdio, for Claude Desktop et al
python -m oncall.mcp_server --http --port 8931

Семь инструментов — list_incidents, get_drift_report, get_eval_run, get_changes, investigate_incident, compare_incidents, list_specialists — плюс ресурс и шаблонный ресурс.

Инструменты возвращают свидетельства, а не прозу. Каждый возвращает артефакт или структурированный диагноз, поэтому модель клиента рассуждает над данными с цитатами, а не над абзацем, который кто-то уже резюмировал. Резюме — это место, где умирают детали.

Написано на mcp 2.0.0, который является ломающим переписыванием

Стоит упомянуть, потому что почти всё опубликованное — это 1.x и не работает. Каждый из этих пунктов был проверен запуском:

1.x

2.0.0

from mcp.server.fastmcp import FastMCP

исчезлоModuleNotFoundError. Используйте MCPServer из mcp.server.mcpserver

@server.list_tools() / @server.call_tool()

исчезло — низкоуровневый Server принимает колбэки в конструкторе

stdio_client + ClientSession вручную

Client(server_or_url_or_transport)

tool.inputSchema

tool.input_schema — snake_case на модели, camelCase на проводе

Ещё два, которые стоили реального времени:

  • @server.tool() должен вызываться. Голый @server.tool вызывает TypeError, который об этом говорит.

  • Голый -> dict вообще не даёт structuredContent. Текстовый контент всё ещё там, поэтому в чат-клиенте выглядит нормально, но молча ломает любой клиент, читающий структурированное поле. Каждый инструмент здесь возвращает параметризованный дженерик (Dict[str, Any]) по этой причине, и есть тест.

Тестирование протокольного сервера без подпроцесса

Client(server) принимает объект сервера напрямую, поэтому весь протокольный цикл выполняется в процессе — без подпроцесса, без порта, без флаки-тестов из-за них. Именно эта возможность — единственная причина, по которой протокольные тесты здесь дёшевы.


Быстрый старт

git clone https://github.com/kanishqtanwar35-hub/ml-oncall-agent
cd ml-oncall-agent
pip install -r requirements.txt
export PYTHONPATH=src

python -m oncall.cli incidents                  # what can be investigated
python -m oncall.cli investigate concept_drift  # the full report
python -m oncall.cli evaluate                   # score it against truth
python -m oncall.cli ablate                     # what each artifact is worth
pytest -q                                       # 65 tests

Укажите на реальные артефакты:

python -m oncall.cli write ./artifacts --scenario bad_deploy
# then: Evidence.load("./artifacts") reads a real pipeline's output unchanged

Всё работает без API-ключа. investigate --narrate добавляет написанный моделью вступительный абзац, если задан GEMINI_API_KEY, и больше ничего не меняет.


Баги, о которых стоит прочитать

Жёстко заданный допуск проглотил случай с флаки-eval. regressions() использовал порог 0.02, поэтому сдвиг на 0.006 никогда не регистрировался, и проверка шума не запускалась — агент говорил healthy, тогда как истина была noise. Это ровно та ошибка фольклорного порога, против которой выступает model-drift-monitor, воспроизведённая на один репозиторий дальше. Обнаружение и значимость — два разных вопроса: нижний порог теперь 0.005 («всё, что показал бы дашборд»), а различение выполняет измеренный самим харнессом noise_std.

Тест восстановления не мог упасть. Он имитировал пропущенного специалиста, заранее заполнив consulted — но критик сравнивает доступные с проконсультированными, поэтому пометка чего-то как проконсультированного скрывала разрыв от самой проверки, которую тестировали. Он сообщал recovered: False и выглядел как сломанный критик. Ошибку нужно внедрять в маршрутизацию, а не в учёт; build_graph(skip_first_pass=...) делает это, и критик теперь демонстративно ловит разрыв и отправляет супервизора обратно.


Ограничения, изложенные прямо

  • Сценарии синтетические. Намеренно — реальные инциденты не приходят с размеченной корневой причиной, именно поэтому их диагностика сложна. Числа характеризуют метод, а не какую-либо продакшн-систему.

  • Шесть сценариев — это маленький набор. 100% на шести — это не 100% в общем случае, и следующий добавленный сценарий скорее сломает его, чем подтвердит.

  • Калибровка здесь неизмерима. Без неправильных ответов не с чем сравнивать уверенность. CLI печатает n/a и объясняет почему, а не выдумывает цифру — test_calibration_is_honestly_unmeasurable_here фиксирует это.

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

  • Точность выбора инструментов тривиально равна 1.0 при трёх всегда присутствующих артефактах. Метрика здесь потому, что она начинает иметь значение на четвёртом инструменте, а добавление её задним числом — это способ обнаружить, что агент месяцами вызывал всё подряд.

  • Нет живых интеграций. Он читает артефакты с диска. Подключение к реальному хранилищу, CI-системе и git-хосту — это работа по деплою, а не по рассуждению.

  • Критик проверяет полноту и поддержку, а не корректность. Он не может сказать вам, что веса неверны, только что свидетельства были скудными.

Дорожная карта

  1. Больший размеченный набор инцидентов и подгонка весов на отложенной выборке.

  2. Больше специалистов — задержка обслуживания, учёт затрат, свежесть feature-store — именно здесь точность выбора инструментов начинает что-то значить.

  3. Подключение MCP-сервера к реальным источникам артефактов вместо генератора сценариев.

  4. Корреляция нескольких инцидентов: одновременная регрессия трёх сервисов — это один инцидент, а не три.

Лицензия

MIT.

-
license - not tested
Not graded
quality - not tested
C
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

  • MCP server providing access to the Scorecard API to evaluate and optimize LLM systems.

  • Monitor MCP servers, API contracts and AI outputs for schema drift. Alerts on breaking changes.

  • MCP server for AI agents to plan, verify, and deploy Cloudflare-native apps.

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/kanishqtanwar35-hub/ml-oncall-agent'

If you have feedback or need assistance with the MCP directory API, please join our Discord server