← 返回文章列表

Dify 高级实验(08):智能告警——如何用 AI 替代固定阈值监控?

1. 业务场景

先讲一个我们实际遇到的场景。

一家 SaaS 公司的运维团队,凌晨两点被告警电话吵醒——监控系统报「数据库连接超时」,值班同学爬起来一看,日志里就那么几条 ERROR,等他想进一步排查时,故障已经自己恢复了。第二天复盘才发现:那只是网络抖动,系统自动重连成功了。可同样的告警,上周真的把数据库搞挂了,那次因为响应慢了十分钟,线上服务中断了半小时。固定阈值监控的尴尬就在这里:同一个告警,有时候是虚惊,有时候是真灾——阈值规则根本分不清。

我们第一次接手监控告警时,第一反应也是「把阈值调准一点不就行了」。后来才发现——阈值规则理解不了上下文,而「数据库连接超时」到底是网络抖动还是数据库挂了,恰恰需要看上下文才能判断。这是规则的死角,却是 LLM 的主场:把日志喂给它,它能说出「这是抖动,不用管」还是「这是故障,立刻升级」。

这不是个例。任何「靠固定阈值做监控」的团队都是这个模式:CPU 超过 90% 就告警、日志出现 ERROR 就告警——阈值定低了,告警风暴淹没真故障;阈值定高了,真故障被漏掉。规则理解不了上下文,而「数据库连接超时」是网络抖动还是数据库挂了,恰恰需要看上下文才能判断。

2. 场景痛点

这个流程的痛点,在运维值班同学身上体现得最直接:

本质上,固定阈值的瓶颈是「规则理解不了上下文」——监控的价值不在「报不报」,而在「报得准不准、报完能不能直接定位根因」。

3. 方案:为什么是这套智能告警范式

Dify 工作流里的「解析 → AI 分析 → 分级路由 → 汇聚报告」链路,把监控从「阈值报警」升级成「AI 判断 + 分级处置」。

选它的理由,我们实际对比过:

这篇文章我们就用它搭一个「智能日志告警系统」:粘贴一段系统日志,AI 判断是否异常、按等级分流处置,输出结构化诊断报告。

4. 整体架构

graph TD start["开始:raw_logs"] --> cpl["Code 解析日志:级别/模块/错误信息 + 统计"] cpl --> la["LLM 异常分析:输出 JSON"] la --> cpa["Code 解析异常等级:json.loads + severity 白名单兜底 + 展平 7 字段"] cpa --> cond{"IF/ELSE 等级分支"} cond -- "critical" --> cc["Code 紧急告警 + 创建工单"] cond -- "warning" --> cw["Code 一般告警通知"] cond -- "info" --> ci["Code 记录日志不通知"] cc --> lr["LLM 生成诊断报告"] cw --> lr ci --> lr lr --> e1["结束"]

链路很清晰:入口收原始日志 → 结构化解析 → AI 分析 → 分级路由 → 汇聚诊断报告。分级路由是这个架构的处置中枢——critical 建工单、warning 发通知、info 只记录,三级分流互不干扰,最后统一汇聚成一份诊断报告。

5. 模块设计

5.1 解析日志(cd_parse_logs)

输出 parsed_entries(array[object],供下游代码消费)与 parsed_json(string,供 LLM 引用——array[object] 在变量选择器不可见):

def main(raw_logs: str) -> dict:

    import json

    lines = (raw_logs or "").strip().split("\n")

    parsed = []

    for line in lines:

        if not line.strip():

            continue

        entry = {"raw": line, "level": "info", "module": "", "message": ""}

        if "ERROR" in line or "FATAL" in line:

            entry["level"] = "error"

        elif "WARN" in line:

            entry["level"] = "warn"

        if "]" in line:

            entry["module"] = line.split("]")[0].strip("[")

            entry["message"] = line.split("]")[-1].strip()

        else:

            entry["message"] = line

        parsed.append(entry)

    return {"parsed_entries": parsed,

            "parsed_json": json.dumps(parsed, ensure_ascii=False),

            "total": len(lines),

            "errors": sum(1 for e in parsed if e["level"] == "error"),

            "warnings": sum(1 for e in parsed if e["level"] == "warn")}

5.2 异常分析 LLM(lm_analyze)

枚举字段必须给判定规则(只写 critical/warning/info 不给规则,LLM 会随意输出,下游分支全走错):

你是一个系统运维工程师。分析以下系统日志,判断是否存在异常。

解析后的日志(共 {{#cd_parse_logs.total#}} 条,{{#cd_parse_logs.errors#}} 个错误,{{#cd_parse_logs.warnings#}} 个警告):

{{#cd_parse_logs.parsed_json#}}

输出 JSON(只输出 JSON,不要输出其他任何内容):

{"has_anomaly": true/false, "severity": "critical/warning/info",

 "root_cause": "根因分析", "affected_modules": ["模块1", "模块2"],

 "suggested_action": "建议措施", "auto_resolvable": true/false}

判定规则:只有 critical 或 warning 才算异常;正常日志 severity 为 info。

5.3 解析异常等级(cd_parse_analysis)

对 LLM 的 textre.search(r"\{.*\}") + json.loads——这要求 lm_analyze 必须 reasoning_format: separated,否则思考混入 text 解析必失败。severity 白名单兜底,boolean 展平为字符串:

def main(llm_text: str) -> dict:

    import json, re

    text = (llm_text or "").strip()

    analysis = {}

    m = re.search(r"\{.*\}", text, re.DOTALL)

    if m:

        try:

            parsed = json.loads(m.group(0))

            if isinstance(parsed, dict):

                analysis = parsed

        except Exception:

            analysis = {}

    severity = str(analysis.get("severity", "info")).lower()

    if severity not in ("critical", "warning", "info"):

        severity = "info"          # 白名单兜底

    affected = analysis.get("affected_modules", []) or []

    affected_text = "、".join(str(x) for x in affected) if isinstance(affected, list) else str(affected)

    return {"severity": severity,

            "has_anomaly": "true" if analysis.get("has_anomaly") else "false",

            "root_cause": str(analysis.get("root_cause", "")) or "无明显异常",

            "suggested_action": str(analysis.get("suggested_action", "")) or "无需处理",

            "affected_modules": affected_text,

            "auto_resolvable": "true" if analysis.get("auto_resolvable") else "false",

            "analysis_json": json.dumps(analysis, ensure_ascii=False)}

5.4 等级分支(cond_severity)

三个 case 全部 is 字符串比较;每个分支一个代码节点(cd_critical/cd_warning/cd_info),输出同名字段(alert_message/alert_type/notify_result/ticket_id)。

5.5 汇聚诊断报告(lm_report)

三分支汇聚共享一个 LLM——prompt 只引用分支前的字段(cd_parse_logs / cd_parse_analysis),不引用分支输出,避免三分支同名字段歧义;长报告节点同样要求「直接输出报告正文」:

你是一个系统运维专家。基于以下日志解析结果和异常分析,生成一份完整的诊断报告。

日志概况:共 {{#cd_parse_logs.total#}} 条日志,{{#cd_parse_logs.errors#}} 个错误,{{#cd_parse_logs.warnings#}} 个警告。

异常分析结论:

- 是否异常:{{#cd_parse_analysis.has_anomaly#}}

- 异常等级:{{#cd_parse_analysis.severity#}}

- 根因分析:{{#cd_parse_analysis.root_cause#}}

- 影响模块:{{#cd_parse_analysis.affected_modules#}}

- 建议措施:{{#cd_parse_analysis.suggested_action#}}

输出一份结构化的诊断报告,包含:1. 摘要 2. 异常详情 3. 根因分析 4. 建议措施 5. 后续监控建议。直接输出报告正文。

6. 运行验证

输入(raw_logs) 期望行为 实测
正常日志(INFO 为主) severity=info,走「记录日志」分支,诊断报告标注无异常 与预期一致
含 ERROR 的日志 severity=warning,一般告警通知 + 根因分析 与预期一致
含多次 CRITICAL/FATAL 的日志 severity=critical,紧急告警 + 创建工单(ticket_id 形如 TK-xxxxxx) 与预期一致
LLM 输出非法 JSON / 无 severity 字段 白名单兜底 severity=info,流程不中断 与预期一致(cd_parse_analysis 防御)

7. 实战坑

现象 修复
LLM 输出 JSON 被代码节点 json.loads 解析失败 未启用推理标签分离时,思考过程混入 textre.search 取到含 <think> 的片段、json.loads 抛异常,流程卡住 lm_analyze 显式 reasoning_format: separated(dify103_02 实测:解析失败流程卡在步骤 0)
枚举字段 severity 不给判定规则 LLM 随意输出 critical/warning/info,告警分支全走错(该紧急告警的只发了普通通知) prompt 给显式规则(「只有 critical 或 warning 才算异常」)+ 代码白名单兜底 default info(dify102_08_01 实测缺陷同源)
has_anomaly/auto_resolvable 用 boolean 输出 boolean 在变量选择器不可见,下游引用取不到;旧写法曾把拦截信息转述成「无法获取数据」 展平为 "true"/"false" 字符串,if-else 用 is 比较(2026-07-31 dify-103 实测)
诊断报告长输出 text 为空 思考与输出共享 max_tokens 预算(默认 2000)被挤空,或内容写进思考部分 lm_report max_tokens 提到 4000-8000 + prompt「直接输出报告正文,不要输出任何解释性文字」(dify103_09 lm_format 实测)
多分支汇聚共享下游 LLM 三个分支代码输出字段同名(alert_message 等),汇聚 prompt 引用分支输出会歧义、取不到 汇聚 LLM 只引用分支前的字段(cd_parse_logs/cd_parse_analysis),分支输出不进 prompt(dify103_08 交付实证)

8. 实验文档及源码获取

文章聚焦核心配置与采坑点;实验的完整分步操作(节点搭建/参数表/调试指引)见实验文档原文。

联系我

15088711270

手机端点击号码可直接拨打 · 桌面端可复制

微信二维码

扫码加微信 · 备注「门户」更快通过