Skip to main content
Glama

ML 值班代理

"昨晚的运行为什么回退了?" —— 一个多代理系统,通过关联漂移报告、评估运行和部署日志来回答这个问题,并且为每一个论断注明出处

通过 MCP 暴露,因此这些工具可以从任何 MCP 客户端使用。

状态: 65 项测试。诊断结果由加权证据确定性计算得出,因此下面的每一个数字都是断言,而不是演示。


真正的问题

ML 系统会悄然退化。当有人最终注意到时,证据分散在三个互不通信的地方:

  • 漂移报告 —— 输入分布是否发生了变化?

  • 评估运行 —— 离线分数是否回退了,在哪些切片上?

  • 部署日志 —— 是否有人部署了什么?

将它们关联起来是一项工作,而且是一项人们在凌晨 3 点做不好的工作,因为它意味着要在脑子里同时记住三个 JSON 文件。

这三类产物并非为本仓库凭空虚构。它们正是 model-drift-monitorllm-eval-pipelineai-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 断言:有无模型参与,结论完全一致。如果这个测试有一天失败了,说明模型已经开始替你做推理了——而一旦如此,推理就不再可测试了。

负面证据才是诊断力最集中的地方。 "没有漂移"和"没有部署"都是 finding,都带有权重。一个 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 捕获任何逃逸的情况。一个能无限循环的代理就会无限循环下去,而一个失控的值班代理会不断发告警而不是回答问题。

不需要 LLM,也不需要 API 密钥——监督者通过一条普通 Python 规则进行路由——因此路由、委派和批评者的回环全部可以在离线状态下进行单元测试。test_the_graph_and_the_plain_loop_agree 断言:LangGraph 运行和普通 for 循环在每一个场景下都得到相同的结论,这证明了图添加的是编排,而不是推理。


每个产物到底值多少

python -m oncall.cli ablate

证据

任务成功率

操作准确率

三者齐全

100%

100%

去掉漂移

67%

67%

去掉评估

67%

67%

去掉变更日志

100%

100%

一个诚实的负面结果,并且被断言固定下来,因此不会被悄悄遗忘。 移除变更日志在这组场景上毫无损失——bad_deploy 仅凭漂移和评估证据就已经可以区分了。变更日志的价值在于指明提交,这才是人类采取行动所需要的,而不是靠改变诊断结论。

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_incidentsget_drift_reportget_eval_runget_changesinvestigate_incidentcompare_incidentslist_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 时添加一段由模型撰写的开头段落,除此之外不改变任何行为。


值得一读的 Bug

一个硬编码的容差吞掉了不稳定评估的案例。 regressions() 使用了 0.02 的阈值,因此 0.006 的变化永远不会被记录,噪声检查也永远不会运行——代理在真值为 noise 的情况下报告了 healthy。这正是 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. 更多专家——服务延迟、成本账本、特征存储新鲜度——这时工具选择准确率才开始有意义。

  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