凌晨两点,手机弹出一条告警——服务 A 的 P99 延迟飙升到 3 秒。你爬起来打开日志平台搜关键词,切到 APM 看监控曲线,再跳到链路追踪系统查 trace 详情。三套系统来回切换,眼睛在五个标签页之间跳了十几轮,最终定位到:上游服务 B 在 01:58 发生了一次 GC 停顿,导致下游调用超时。一分钟后流量恢复正常,告警自愈。
整个过程花了 25 分钟,真正分析的时间不到 5 分钟,剩下的 20 分钟全耗在登录、搜索、拼凑分散在各平台的数据上。更扎心的是——同样的告警下个月还会再来,换个人排查可能要 40 分钟。
告警排查的真正瓶颈
传统排查流程的时间分布大致是这样的:
| 步骤 | 耗时 | 瓶颈 |
|---|---|---|
| 打开日志平台、搜索关键词 | 3-8 min | 平台登录、索引延迟 |
| 切到 APM 查指标曲线 | 2-5 min | 时间窗口对齐、指标筛选 |
| 进链路追踪看 trace | 3-10 min | trace ID 关联、详情展开 |
| 综合判断根因 | 2-5 min | 依赖经验,容易遗漏 |
核心矛盾不是"分析难",而是数据散落在三个系统里,人成了数据搬运工。经验丰富的工程师能快速跳过无效信息,新手则容易在日志海洋里迷失——同一个告警,排查效率可以差 3 倍以上。
Agent 能做什么:把"搬运"交给机器
LLM Agent 在这个场景的价值很明确:代替人完成跨平台的数据采集和初步关联,把散落的信息拼成一份结构化的排查报告。
具体来说,一个告警排查 Agent 可以:
- 接收告警事件,自动提取关键字段(服务名、时间窗口、异常指标)。
- 并行调用多个工具——日志搜索、指标查询、trace 检索——把原本需要人手动切换的步骤变成并行请求。
- 交叉关联——用 trace ID 把日志和链路追踪串起来,用时间窗口把指标和日志对齐。
- 生成初步结论——"上游 GC 停顿导致下游超时,持续时间 1 分钟,已自愈"。
人只需要看最终报告,确认结论或补充细节。10-30 分钟的排查压缩到 2-3 分钟。
搭建一个最小可用的告警排查 Agent
下面用一个简化的 Python 示例演示核心逻辑。基于 OpenAI function calling + 自定义工具,不依赖任何 Agent 框架,方便你直接改造接入自己的平台。
定义工具:日志、指标、链路追踪
import json
import openai
# --- 工具定义 ---
tools = [
{
"type": "function",
"function": {
"name": "search_logs",
"description": "在日志平台搜索指定服务在指定时间窗口的关键词日志",
"parameters": {
"type": "object",
"properties": {
"service": {"type": "string", "description": "服务名,如 order-service"},
"keyword": {"type": "string", "description": "搜索关键词,如 timeout、error"},
"start_time": {"type": "string", "description": "开始时间,ISO 8601 格式"},
"end_time": {"type": "string", "description": "结束时间,ISO 8601 格式"},
},
"required": ["service", "keyword", "start_time", "end_time"],
},
},
},
{
"type": "function",
"function": {
"name": "query_metrics",
"description": "查询 APM 平台指定服务的指标曲线",
"parameters": {
"type": "object",
"properties": {
"service": {"type": "string", "description": "服务名"},
"metric": {"type": "string", "description": "指标名,如 p99_latency、error_rate"},
"start_time": {"type": "string", "description": "开始时间"},
"end_time": {"type": "string", "description": "结束时间"},
},
"required": ["service", "metric", "start_time", "end_time"],
},
},
},
{
"type": "function",
"function": {
"name": "get_trace_detail",
"description": "根据 trace ID 获取链路追踪详情",
"parameters": {
"type": "object",
"properties": {
"trace_id": {"type": "string", "description": "链路追踪 ID"},
},
"required": ["trace_id"],
},
},
},
]
工具执行层:对接你的真实平台
这里用模拟数据演示,实际部署时替换成你公司的 API 调用:
# --- 工具执行(模拟,实际替换为真实 API 调用) ---
def execute_tool(name: str, args: dict) -> str:
if name == "search_logs":
# 实际: 调用 Elasticsearch / Loki API
return json.dumps({
"total": 12,
"hits": [
{"timestamp": "2024-01-15T01:58:03Z", "level": "ERROR",
"message": "upstream timeout: service-B /api/data took 3200ms"},
{"timestamp": "2024-01-15T01:58:04Z", "level": "WARN",
"message": "retrying call to service-B, attempt 2/3"},
],
"trace_ids": ["trace-abc123", "trace-def456"],
})
if name == "query_metrics":
# 实际: 调用 Prometheus / Grafana API
return json.dumps({
"service": args["service"],
"metric": args["metric"],
"datapoints": [
{"time": "01:57", "value": 120},
{"time": "01:58", "value": 3200}, # 延迟飙升
{"time": "01:59", "value": 150},
{"time": "02:00", "value": 110}, # 恢复
],
})
if name == "get_trace_detail":
# 实际: 调用 Jaeger / SkyWalking API
return json.dumps({
"trace_id": args["trace_id"],
"root_span": {"service": "service-B", "operation": "/api/data",
"duration_ms": 3200, "status": "ERROR"},
"spans": [
{"service": "service-B", "operation": "GC pause",
"duration_ms": 2800, "tags": {"gc.type": "Full"}},
],
})
return json.dumps({"error": f"unknown tool: {name}"})
Agent 主循环:让 LLM 自主编排工具调用
# --- Agent 主循环 ---
client = openai.OpenAI() # 需设置 OPENAI_API_KEY
def run_alert_agent(alert_event: str, max_rounds: int = 5) -> str:
"""接收告警描述,自动排查并返回结论"""
messages = [
{
"role": "system",
"content": (
"你是一个告警排查助手。根据告警信息,依次调用日志搜索、指标查询、"
"链路追踪工具来收集数据,然后综合分析给出根因结论。"
"结论格式:根因、影响范围、持续时间、是否自愈、建议动作。"
),
},
{"role": "user", "content": alert_event},
]
for _ in range(max_rounds):
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=tools,
tool_choice="auto",
)
msg = response.choices[0].message
# 如果 LLM 不再调用工具,说明分析完成
if not msg.tool_calls:
return msg.content or "排查完成,但未生成结论。"
# 执行所有工具调用(可并行,这里简化为顺序执行)
messages.append(msg)
for tc in msg.tool_calls:
result = execute_tool(tc.function.name, json.loads(tc.function.arguments))
messages.append({
"role": "tool",
"tool_call_id": tc.id,
"content": result,
})
return "排查轮次超限,请人工介入。"
运行示例
# --- 运行 ---
alert = """
告警事件:
- 服务: order-service
- 时间: 2024-01-15 01:57 ~ 02:00
- 异常: P99 延迟从 120ms 飙升至 3200ms,错误率同步上升
- trace 样本: trace-abc123
请排查根因。
"""
report = run_alert_agent(alert)
print(report)
预期输出类似:
根因:上游服务 service-B 在 01:58 发生 Full GC 停顿(耗时 2800ms),
导致下游 order-service 调用 /api/data 超时。
影响范围:order-service 的 /api/data 接口,P99 延迟峰值 3200ms。
持续时间:约 1 分钟(01:58 ~ 01:59)。
是否自愈:已自愈,02:00 指标恢复正常。
建议动作:无需紧急干预;建议优化 service-B 的 GC 配置,
考虑增大堆内存或切换到 G1/ZGC 收集器以减少 Full GC 频率。
把 execute_tool 里的模拟数据换成你公司的真实 API——Elasticsearch 的 _search、Prometheus 的 /api/v1/query_range、Jaeger 的 /api/traces/{traceID}——Agent 就能跑在生产环境了。
落地时需要留意的几个坑
工具返回的数据量要控制。 日志搜索返回 500 条原始日志,LLM 上下文窗口会被撑爆,token 费用也会飙升。在工具层做预聚合:只返回统计摘要(错误数、Top5 错误消息、关联 trace ID 列表),而不是原始日志全文。
Agent 不适合做最终决策。 它擅长数据采集和初步关联,但"是否需要扩容""是否要回滚"这类决策应该由人确认。把 Agent 的输出定位为"排查报告 + 初步结论",而不是"自动修复指令"。
敏感信息要脱敏。 日志里经常有用户 ID、手机号、内部 IP。在 execute_tool 层做字段过滤,不要把原始数据直接喂给 LLM——尤其是用第三方 API 的时候。
冷启动问题。 Agent 第一次面对某种新告警类型时,可能不知道该查什么关键词、看什么指标。解决方案:维护一份告警类型 → 排查策略的映射表,作为 system prompt 的一部分注入,让 Agent 有"经验"可参考。
上线 Checklist
- [ ] 三个平台的 API 已打通,工具层能稳定返回结构化数据
- [ ] 工具层做了数据量裁剪和敏感字段脱敏
- [ ] Agent 输出只作为排查报告,不触发自动修复动作
- [ ] 常见告警类型的排查策略已整理成 prompt 知识库
- [ ] 设置了 max_rounds 上限,防止 Agent 无限循环调用工具
- [ ] 每次排查结果存入历史库,用于后续评估 Agent 准确率
- [ ] 先在低优先级告警上试跑两周,对比人工排查耗时和结论准确率
告警排查不是最难的工程问题,但它是最耗日常精力的重复劳动之一。Agent 不替代判断,但它能把 20 分钟的数据搬运压缩到 30 秒——剩下的时间留给真正需要人做的决策。