上周四晚上十一点半,数据库连接池使用率突然飙到93%,告警消息在五分钟内通过钉钉群、飞书群和企业微信同时炸开。之所以能做到这种效果,是因为我最近把项目里的告警链路统一换成了一个叫 a2a-alert-agent 的Python包。这个包不搞复杂的机器学习,也不是什么重量级框架,它做的一件事非常聚焦:把"发告警"封装成一个符合A2A协议标准的Agent服务,让你用几行代码就能注册告警规则、接入各类通知渠道,并且让其他AI Agent也能通过标准协议来调用你的告警能力。这篇文章会从语法、参数和应用案例三个角度聊聊它的实际用法,希望对正在做监控告警集成、或者想在A2A生态里挂一个告警服务的Python开发者有帮助。
1. a2a-alert-agent到底解决了什么问题
1.1 传统告警脚本的三大痛点
做运维或后端开发的兄弟应该都经历过这种阶段:告警逻辑散落在各个服务里,发邮件的写一个函数,发钉钉的再写一个函数,Prometheus回调里套一层HTTP处理,脚本里又定时跑一遍检测逻辑。这些代码单看都不难,但维护起来非常痛苦。换一个告警渠道要改十几个文件,新增一个监控指标要复制一坨判断逻辑,时间一长,告警代码就成了整个系统里最不敢碰的部分。
除了维护成本,还有一个更本质的问题:告警这件事是单向的。传统脚本只能被动地往渠道里丢消息,外部系统、其他AI Agent根本没办法通过统一的方式发现你的告警服务、调用你的告警能力。哪怕你写了一个发告警的HTTP接口,接口的鉴权方式、输入格式、字段含义也全是自己定义的,别人要对接得先读你的文档,而文档大概率已经过时了。
a2a-alert-agent 做的最重要的一件事,就是把告警能力"Agent化"。
1.2 A2A协议与"告警Agent"的定位
A2A,全称是Agent2Agent,是近年来AI Agent生态里推动互操作的一套协议。它不像消息队列那么底层,也不像REST API那么随意,它定义了Agent如何发布自己的Agent Card(描述自己能力的元数据)、如何接收其他Agent发来的任务消息、如何通过JSON-RPC或SSE进行流式交互。你可以把Agent Card理解成这个Agent能力的"菜单",其他Agent拿到菜单就知道能请你做什么事。
a2a-alert-agent 的设计思路就是:本身不绑定任何具体监控系统,而是把告警能力发布成A2A协议里的一个Skill。比如它默认会暴露一个 send_alert 的能力,其他Agent或者内部系统只要按照协议发送消息,就能触发告警。与此同时,它保留了传统告警Agent的灵活性,你还可以通过规则引擎把内部产生的指标事件转成告警。
这种设计最大的好处是:告警服务不再是孤岛。监控系统、自动化脚本、甚至一个会思考的主Agent,都可以通过同一种协议来使用它。
2. 安装与最小可运行示例:先跑起来再说
2.1 环境要求与安装
先看环境要求。a2a-alert-agent 目前要求 Python 3.9以上版本,核心依赖是 aiohttp、pydantic 和 jinja2。安装很简单:
pip install a2a-alert-agent如果你用的是poetry或者pdm,注意要把这个包放到主依赖里,因为它是运行时必要的,不是在dev依赖里。我遇到过有人把它装进dev组,然后生产环境跑了半天发现包不存在,告警全都静默了,这个错误很低级但很真实。
2.2 一个最小示例:注册CPU告警规则
安装完,先跑一个最小示例,感受一下整体流程:
import time from a2a_alert_agent import create_agent agent = create_agent( name="demo-alert-agent", channel={ "type": "webhook", "url": "https://example.com/webhook/alert", "headers": {"X-Token": "your-secret"}, }, rules=[ { "name": "cpu_high", "event": "metric.cpu.usage", "condition": "value > 85", "level": "warning", "cooldown": 60, } ], ) agent.start() # 模拟一个监控采集端,每隔10秒上报一次CPU使用率 while True: agent.publish("metric.cpu.usage", {"value": 92, "host": "web-01"}) time.sleep(10)这段代码做了三件事:第一步,用 create_agent 创建一个告警Agent,指定了渠道为Webhook,并声明了一条规则:当 metric.cpu.usage 事件里的 value 大于85时就触发告警。第二步,调用 agent.start() 启动Agent,它会开启HTTP服务(默认端口8899),注册Agent Card,同时启动规则引擎。第三步,在while循环里周期性向事件总线塞入CPU使用率事件,规则引擎会对事件求值,触发后调用渠道发送告警。
2.3 三步判断是不是真跑通了
跑完不能光看它没报错就完事,我建议用三步验证:
第一步,看启动日志。正常情况会输出类似[a2a-alert-agent] Agent Card registered, name=demo-alert-agent, capability=send_alert这样的日志,说明A2A协议层已经起来了。
第二步,调用agent.test()方法,它会直接向配置的渠道发送一条测试告警。渠道端能收到就说明网络和鉴权都没问题。
第三步,手动触发一次规则:把上报的 value 改成 92,观察日志里是否出现rule=cpu_high matched, dispatching alert,再确认渠道端收到了消息。
这三步全部通过,基本说明你的Agent本身跑通了。这个最小示例虽然简单,但已经覆盖了a2a-alert-agent最核心的语法骨架:create_agent创建、rules声明、publish上报、channel分发。
3. 核心语法拆解:面向事件与规则的设计模式
3.1 create_agent工厂函数与Agent对象
a2a-alert-agent 的入口是 create_agent 工厂函数。我用的版本是0.2.x,核心签名大致如下:
create_agent( name: str, channel: dict | ChannelConfig, rules: list[dict] | None = None, endpoint: str = "0.0.0.0", port: int = 8899, heartbeat: int = 30, debug: bool = False, timezone: str = "UTC", escalation: dict | None = None, )它会返回一个 AlertAgent 实例。实例上主要的公共方法就是 publish、on_event、test、start 和 stop。你可以把它理解成一个独立的告警服务:方法调用是同步的,但内部是基于异步事件循环的。
3.2 规则注册的两种姿势
规则注册有两种姿势,一种是上面示例里的字典式规则,另一种是装饰器式。如果你更喜欢显式配置、希望规则和代码解耦,用工厂函数传入的 rules 列表就行;如果你是脚本型选手,想在代码里直接写判断逻辑,装饰器更好用:
from a2a_alert_agent import create_agent agent = create_agent( name="decorator-agent", channel={"type": "stdout"}, ) @agent.on_event("metric.cpu.usage") def handle_cpu(ctx): if ctx.value > 85: ctx.alert( title="CPU告警", content=f"{ctx.labels.get('host', 'unknown')} CPU使用率 {ctx.value}%", level="warning", )注意,装饰器函数接收一个 ctx 上下文对象,里面包含 value、labels、timestamp、event_name 等属性。ctx.alert() 会绕过规则引擎的复杂条件判断,直接发送告警。两种方式可以混用,但建议一个事件只使用一种注册方式,否则规则引擎会出现重复匹配。
3.3 condition表达式的求值逻辑
规则里的 condition 字符串是a2a-alert-agent的一个亮点,它内置了一个安全的表达式解析器,不需要你写lambda或eval。支持的运算符包括>、<、>=、<=、==、!=、and、or、not,也支持in和基本的len()函数。表达式的作用对象是事件负载里除了 event 字段之外的所有字段。
举个例子:
"value > 85": 数值必须大于85才触发。"value > 80 and 'db' in labels": 80以上且labels里包含键db。"len(items) == 0": 事件里items列表为空时触发。
它不会执行任意Python代码,遇到不支持的语法会直接报规则解析错误,启动时就丢日志,而不是运行到一半才崩。这个设计在告警场景里很重要,毕竟你是拿它保命的,不是拿它表演的。
3.4 A2A协议通信层
光有规则引擎还不够,a2a-alert-agent 的另一半是A2A协议通信层。启动后,它会自动暴露两个关键入口:一个是GET /.well-known/agent-card,返回Agent的元数据和能力列表;另一个是POST /message/send,用于接收标准A2A消息。
如果你配置了 escalation 参数,Agent收到critical级告警时会先向指定分析Agent发送一条A2A消息,等对方返回处理建议,再把建议一并发送到告警渠道。这个机制让"告警后的第一轮排查"也能自动化,不再是人肉去翻日志。
很多朋友误以为A2A协议层必须搭配大模型才能用,其实不是。a2a-alert-agent只是实现了协议规范,消息内容既可以来自规则引擎自动生成,也可以来自其他Agent的主动请求。告警Agent本身不需要是"聪明"的AI Agent,它只需要是一个"听话且可靠"的执行者。
4. 参数体系详解:从配置项到典型参数组合
4.1 全局参数
参数是a2a-alert-agent最值得花时间研究的部分。先看全局参数,我用到的核心项整理成了表格:
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| name | str | 必填 | Agent名称,会出现在日志和Agent Card里 |
| channel | dict | 必填 | 告警渠道配置,见4.2 |
| rules | list[dict] | 空 | 规则列表 |
| endpoint | str | 0.0.0.0 | HTTP服务绑定地址 |
| port | int | 8899 | HTTP服务端口 |
| heartbeat | int | 30 | Agent心跳间隔,单位秒 |
| timezone | str | UTC | 告警消息时间格式使用的时区 |
| debug | bool | False | 开启后输出规则求值详细日志 |
| escalation | dict | None | 升级联动配置 |
| max_retries | int | 3 | 告警发送失败重试次数 |
heartbeat这个参数很多人忽略。它在A2A协议里表示Agent的存活探测周期,如果其他Agent或者你的监控系统依赖Agent Card做健康检查,心跳太大会导致故障发现慢,太小又会在网络上产生无谓请求。我生产环境配的是30秒,够用。
4.2 渠道参数
渠道参数是重头戏。目前内置的渠道类型有 webhook、email、dingtalk、slack、stdout,不同渠道参数差异很大。我用过的主要是 webhook 和 dingtalk。
Webhook渠道参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| type | str | 是 | webhook |
| url | str | 是 | 回调地址 |
| method | str | 否 | 默认POST |
| headers | dict | 否 | 自定义请求头 |
| timeout | float | 否 | 默认5秒 |
钉钉渠道参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| type | str | 是 | dingtalk |
| access_token | str | 是 | 钉钉机器人的access_token |
| secret | str | 否 | 加签模式需要填 |
| keyword | str | 否 | 自定义关键词 |
| at_mobiles | list[str] | 否 | 需要@的手机号列表 |
邮件渠道参数:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| type | str | 是 | |
| smtp_host | str | 是 | SMTP服务器地址 |
| smtp_port | int | 是 | 25或465 |
| use_ssl | bool | 否 | 默认True |
| username | str | 是 | 登录账号 |
| password | str | 是 | 登录密码 |
| from_addr | str | 是 | 发件人 |
| to_addrs | list | 是 | 收件人列表 |
stdout渠道适合本地调试,配置type: stdout就行,所有告警会打到控制台。我建议任何项目在接入真实渠道之前,先用stdout跑一遍完整逻辑,确认规则命中、消息排版都正常,再切正式渠道。这个习惯能帮你省掉很多对着渠道文档发呆的时间。
4.3 规则参数
规则参数决定了"什么情况下告警、告警发几遍"。一张表说清楚:
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| name | str | 必填 | 规则名称,日志中用于定位 |
| event | str | 必填 | 监听的事件名 |
| condition | str | 空 | 表达式,为空时收到事件就触发 |
| level | str | info | 告警级别:info/warning/critical |
| cooldown | int | 0 | 冷却时间,单位秒,防止重复告警 |
| template | str | 空 | 自定义告警文案模板 |
| retry | bool | False | 是否启用发送失败重试 |
| max_retries | int | 3 | 最大重试次数 |
cooldown 是我最想强调的参数。默认是0,意味着只要条件满足,每次事件都会触发告警。如果你的监控系统每10秒上报一次,而故障持续10分钟,那你会收到60条相同告警。这不仅是打扰,还可能把你的告警渠道打爆。我在生产环境里给warning级别设了300秒冷却,critical设了60秒。冷却时间内重复命中的事件会被直接丢弃并记录一条 ignore 日志,你可以通过 debug 模式看到。
4.4 参数优先级与覆盖规则
实际用下来,很多人会被参数覆盖问题绕晕。a2a-alert-agent 的参数优先级从高到低是:代码里显式传入的构造参数 > 环境变量(前缀 A2A_) > 配置文件 > 内置默认值。
举个例子,你可以在启动文件里写死端口8899,但这会让多环境部署很困难。我采用的是环境变量配置法:
export A2A_PORT=8900 export A2A_DEBUG=true a2a-alert serve --config config.yaml注意,环境变量只覆盖全局参数,不覆盖规则和渠道里的子参数。渠道子参数目前只能靠配置文件或者构造函数传入。配置文件是YAML格式:
name: prod-alert-agent channel: type: dingtalk access_token: ${DINGTALK_TOKEN} secret: ${DINGTALK_SECRET} rules: - name: cpu_high event: metric.cpu.usage condition: "value > 85" level: warning cooldown: 300这里有个非常实用的细节:配置文件支持${ENV_VAR}占位符,意思是敏感信息不用直接写进仓库,部署时通过环境变量注入。我踩过最大的坑就是有人把钉钉机器人的access_token直接提交到了Git仓库,第二天就收到了恶意刷屏告警。所以哪怕只是个人项目,也请把token放到环境变量里。
5. 实际应用案例:生产环境里的三种玩法
5.1 案例一:Prometheus告警接入并稳定转发
第一个案例是最常规的玩法:把已有的Prometheus告警接到告警Agent上。
Prometheus侧不需要改任何采集配置,只需要在alertmanager.yml里增加一个webhook接收器,指向agent暴露的/webhook/prometheus接口:
route: group_by: ["alertname"] receiver: "a2a-alert" receivers: - name: "a2a-alert" webhook_configs: - url: "http://127.0.0.1:8899/webhook/prometheus" send_resolved: true这里有个经验:send_resolved一定要设为 true。原因很简单,如果只发告警不发恢复通知,值班同学会一直以为故障还在,甚至产生告警疲劳。a2a-alert-agent会把你配置的template同时用于触发和恢复场景,恢复消息里自动带上一句"已恢复"。
Agent侧配置一条规则,监听 Prometheus 的 alert 事件:
channel = {"type": "webhook", "url": "https://hooks.example.com/...", "timeout": 5} agent = create_agent( name="prometheus-alert-agent", channel=channel, rules=[ { "name": "prom_alert", "event": "prometheus.alert", "condition": "status == 'firing'", "level": "warning", "cooldown": 180, "template": "告警: {labels.alertname}, 主机: {labels.instance}, 详情: {annotations.summary}", } ], )Prometheus的告警事件会被格式化成{status, labels, annotations, startsAt}这样的结构体。我在这个案例里的最大体会是:模板里一定要包含 labels.alertname 和 labels.instance,否则大家收到告警也不知道是哪个服务出的问题。
5.2 案例二:自定义脚本监控数据库连接池
第二个案例针对没有接入Prometheus的自建监控脚本。假设你有一个Python脚本,每30秒查一次数据库连接池使用率,超过90%要告警。用a2a-alert-agent的客户端模式非常方便:
import time from a2a_alert_agent import AgentClient from db_utils import get_pool_stats client = AgentClient("http://127.0.0.1:8899") while True: stats = get_pool_stats() client.publish( "metric.db.conn_pool", { "value": stats["usage_percent"], "labels": {"db": "order", "host": "db-01"}, }, ) time.sleep(30)Agent侧规则配置:
rules = [ { "name": "db_pool_full", "event": "metric.db.conn_pool", "condition": "value > 90", "level": "critical", "cooldown": 60, "template": "数据库连接池告警: {labels.db} 使用率 {value}%, 已持续超过阈值", } ]这里的关键是 AgentClient 通过HTTP和Agent通信,所以监控采集脚本和Agent可以部署在不同的机器上。不用把自己的监控逻辑强行塞进Agent进程里,这是它比很多"一体化Agent框架"更实用的地方。
另外,脚本本身要有异常捕获。如果数据库都挂了,get_pool_stats() 大概率也会抛异常。正确的做法是把异常捕获后在except块里也发一条事件:
try: stats = get_pool_stats() except Exception as e: client.publish("metric.db.conn_pool", {"value": 100, "labels": {"error": str(e), "db": "order", "host": "db-01"}})5.3 案例三:A2A多Agent联动,让另一个Agent参与告警处理
第三个案例是我个人最喜欢的场景,也真正发挥了A2A协议的价值。传统告警只是通知人,但如果你的环境里还有一个做根因分析的分析Agent,完全可以让告警Agent在发出critical告警时,先问一下分析Agent应该怎么办。
配置 escalation 参数即可:
agent = create_agent( name="smart-alert-agent", channel={"type": "dingtalk", "access_token": "...", "secret": "..."}, escalation={ "on_level": "critical", "agent_url": "http://analysis-agent:9000", "message_template": "请分析 {labels.host} 的CPU告警,当前值 {value},给出可能的根因和处置建议", }, )触发critical告警时,执行流程是这样的:规则引擎命中 -> Agent向 analysis-agent 发送一条A2A消息询问建议 -> analysis-agent返回处理建议文本 -> Agent把原始告警和建议一起通过钉钉发出来。
我在一次线上CPU毛刺演练中实际跑了这个流程,分析Agent给出的"先查看同时间段慢查询SQL"建议,正好命中问题根因。虽然这不是a2a-alert-agent自带的能力,但协议层的标准化让这种联动像拼积木一样自然。你不必再为两个内部系统写一套私有的互相调用协议。
6. 排错经验:生产环境里踩到的坑
6.1 事件循环与多线程的冲突
第一个坑是事件循环冲突。a2a-alert-agent内部使用asyncio,如果你在Flask或Django这种同步框架里直接调用 agent.publish(),很容易遇到RuntimeError: There is no current event loop in thread。原因很简单:同步线程里没有运行中的asyncio事件循环。
解决办法有两个。第一个是加参数thread_safe_mode=True,让publish方法内部把事件丢到一个线程安全队列里,由Agent主循环异步消费。第二个方法更彻底:把agent.publish()封装成一个HTTP接口,采集端通过AgentClient远程调用,彻底隔离进程。
我建议优先用AgentClient远程调用。不仅是线程安全问题,还能让监控采集和告警分发各自独立部署、独立扩容。不要为了省一个进程把两种职责捆在一起,告警链路最忌讳单点。
6.2 告警重试风暴
第二个坑是重试风暴。有一次我配置了一个webhook渠道,目标服务临时故障,返回500。我的规则里 retry=True 且 max_retries=3,看起来没问题吧?问题出在冷却时间上:如果 cooldown=0,而新的告警事件还在源源不断进来,每一条都会触发新的3次重试。目标服务本来只是处理慢,被我这边的重试流量直接打挂了。
后来我养成了一个习惯:任何真实渠道的重试都遵循指数退避,并且保证 cooldown > retry 的最长时长。比如 max_retries=3、单次超时5秒,那冷却时间至少设60秒,宁可漏掉一次重复告警,也不要让告警系统变成DDoS攻击源。
6.3 时区与时间格式不一致
第三个坑很不起眼:时间时区。a2a-alert-agent默认用UTC时间,如果你的channel是钉钉,群里的告警消息显示的是UTC时间,而你的团队在本地时区,看到的时间总是比真实时间晚8小时。排查问题时对着时间线,经常对不上。
解决办法是在全局参数里设置timezone: "Asia/Shanghai"。它会影响所有告警消息里的时间戳格式化。注意,这只影响消息展示,不影响规则判断的时间,所以放心改。
6.4 一条完整的排查链路
最后分享一个我实际的排查过程,症状是Prometheus明明在报警,但钉钉群里什么都没收到。我的排查链路是:
第一步,看Agent日志。启动时加了 debug=True,日志里能看到/webhook/prometheus收到请求、规则引擎开始求值。如果这里连请求都没收到,问题大概率在Alertmanager到Agent的网络链路上。
第二步,用curl手动发一个模拟告警:
curl -X POST http://127.0.0.1:8899/webhook/prometheus \ -H "Content-Type: application/json" \ -d '{"status":"firing","labels":{"alertname":"TestAlert"},"annotations":{"summary":"test"},"startsAt":"2025-01-01T00:00:00Z"}'如果curl能触发,说明接口和规则都没问题,那就要对比真实告警payload和curl payload是不是有字段差异。
第三步,检查渠道调用。日志里如果有channel sent failed,就去看渠道返回的错误主体。我遇到过一次钉钉加签模式里 secret 配错了,控制台只有一条很模糊的错误提示,最后还是靠打开 debug 日志看到了HTTP响应体里的具体错误码。
这套排查链路的核心原则是:先确认入口通不通,再确认规则匹配不匹配,最后才怀疑渠道。很多人一上来就怀疑渠道配置,结果折腾半天发现是Alertmanager那边根本没发出请求。
7. 目前版本的限制与后续扩展建议
最后说点真实评价。a2a-alert-agent目前还谈不上成熟,我用的0.2.x版本有几个明显的限制。第一,没有内置的告警历史持久化,Agent重启后历史状态就丢了,无法查询上一次告警是什么时候触发的。第二,规则引擎目前只支持单事件条件,像"5分钟内连续3次超过阈值"这类复合判断还不能用纯规则表达,需要自己在采集端做状态缓存。第三,渠道插件生态还小,内置的几类渠道能满足大部分需求,但像飞书、企业微信这类常用渠道得靠定制。
如果你有自定义渠道需求,包内预留了插件接口,继承 BaseChannel 实现 send 方法注册进去就行:
from a2a_alert_agent.channels import BaseChannel class FeishuChannel(BaseChannel): def send(self, alert): # 把alert转成飞书消息并发送 ...我在项目里也做了两件扩展:一是用Redis做状态持久化,把告警的cooldown状态存到Redis里,Agent重启也不会立刻重新告警;二是写了一个简单的Web界面查看历史告警,其实就是把send前后的事件都记到MongoDB里。这些都是常规工程手段,包本身不给,但也没有阻碍我加上去。
整体用下来的感受是:a2a-alert-agent在"告警Agent化"这个垂直场景里足够轻、足够聚焦。如果你的监控体系已经比较庞杂,或者你正准备在A2A协议生态里挂一个告警服务,它值得你花一个下午试跑一遍。至少对我来说,换了它之后,告警链路的维护成本真的是肉眼可见地降下来了。