AI 故障排障 Agent:从人工诊断到多源数据自动推理的工程实践

cover

一、排障效率的天花板:当资深工程师成为瓶颈

一次 P0 故障的排障过程暴露了传统模式的深层问题:交易服务响应超时,值班工程师花了 15 分钟确认现象,20 分钟逐个服务排查日志,10 分钟对比监控指标,最终在数据库侧找到根因——慢查询锁表。整个排障过程 45 分钟,其中 80% 的时间花在"找数据"而非"做判断"。

更深层的问题是知识断层:能快速排障的资深工程师只有 3 个,他们轮岗时排障效率直接腰斩。新人面对故障,不知道该看哪些指标、查哪些日志、按什么顺序排查,只能靠"猜"和"试"。

AI 排障 Agent 的核心目标:将排障经验从"人脑中的隐性知识"转化为"可执行的显性流程",让 Agent 自动完成数据收集、关联分析和推理判断,输出根因假设和处置建议,工程师只需做最终决策。

二、AI 排障 Agent 的架构与推理链路

graph TB
    subgraph 感知层
        A1[告警事件输入]
        A2[人工触发排障]
        A3[定时巡检触发]
    end

    subgraph 规划层
        PL[排障规划器<br/>根据故障类型选择排障流程]
        KB[知识库<br/>排障SOP+历史案例]
    end

    subgraph 执行层
        E1[指标查询工具<br/>Prometheus API]
        E2[日志查询工具<br/>Elasticsearch API]
        E3[链路查询工具<br/>Jaeger API]
        E4[K8s查询工具<br/>kubectl/API Server]
        E5[数据库查询工具<br/>慢查询+锁状态]
    end

    subgraph 推理层
        R1[数据关联分析<br/>时序+拓扑+因果]
        R2[假设生成<br/>基于规则+历史案例]
        R3[假设验证<br/>查询验证性数据]
        R4[置信度评估<br/>多证据交叉验证]
    end

    subgraph 输出层
        O1[根因报告<br/>含推理过程]
        O2[处置建议<br/>含风险评级]
        O3[自动执行<br/>低风险操作]
    end

    A1 --> PL
    A2 --> PL
    A3 --> PL
    PL --> KB
    PL --> E1
    PL --> E2
    PL --> E3
    PL --> E4
    PL --> E5
    E1 --> R1
    E2 --> R1
    E3 --> R1
    E4 --> R1
    E5 --> R1
    R1 --> R2
    R2 --> R3
    R3 --> R4
    R4 --> O1
    R4 --> O2
    R4 --> O3

AI 排障 Agent 采用 ReAct(Reasoning + Acting)模式:先根据故障类型规划排障步骤,然后逐步执行工具调用获取数据,根据数据推理生成根因假设,再调用工具验证假设,循环迭代直到置信度达标。这与传统规则引擎的区别在于:规则引擎是"if-then"的静态匹配,Agent 是"观察-推理-行动"的动态循环。

三、AI 排障 Agent 的生产级代码实现

3.1 排障 Agent 核心框架

#!/usr/bin/env python3
"""AI 排障 Agent:基于 ReAct 模式的自动故障诊断"""

import json
import time
from datetime import datetime, timedelta
from typing import Dict, List, Optional, Any
from dataclasses import dataclass, field
from enum import Enum
import logging

logger = logging.getLogger(__name__)


class DiagnosisStatus(Enum):
    """诊断状态"""
    PLANNING = "planning"       # 规划排障步骤
    EXECUTING = "executing"     # 执行工具调用
    REASONING = "reasoning"     # 推理分析
    VERIFYING = "verifying"     # 验证假设
    COMPLETED = "completed"     # 诊断完成
    FAILED = "failed"           # 诊断失败


@dataclass
class Hypothesis:
    """根因假设"""
    id: str
    description: str           # 假设描述
    confidence: float          # 置信度 0.0-1.0
    evidence: List[str]        # 支持证据
    verification_queries: List[Dict]  # 验证该假设需要的数据查询
    verified: bool = False
    verification_result: Optional[str] = None


@dataclass
class DiagnosisStep:
    """诊断步骤记录"""
    step_id: int
    action: str                # 执行的动作
    tool: str                  # 使用的工具
    query: Dict                # 查询参数
    result: Any                # 查询结果
    observation: str           # 观察结论
    timestamp: datetime = field(default_factory=datetime.utcnow)


@dataclass
class DiagnosisReport:
    """诊断报告"""
    incident_id: str
    start_time: datetime
    end_time: Optional[datetime]
    status: DiagnosisStatus
    steps: List[DiagnosisStep]
    hypotheses: List[Hypothesis]
    root_cause: Optional[Hypothesis]
    remediation: List[Dict]    # 处置建议
    summary: str


class DiagnosisAgent:
    """排障诊断 Agent"""

    def __init__(self, tools: Dict[str, Any],
                 knowledge_base: Any,
                 max_iterations: int = 10,
                 confidence_threshold: float = 0.8):
        """
        Args:
            tools: 可用工具集合 {tool_name: tool_instance}
            knowledge_base: 排障知识库
            max_iterations: 最大推理迭代次数
            confidence_threshold: 根因确认的置信度阈值
        """
        self.tools = tools
        self.kb = knowledge_base
        self.max_iterations = max_iterations
        self.confidence_threshold = confidence_threshold

    def diagnose(self, incident_id: str,
                 alert_info: Dict) -> DiagnosisReport:
        """
        执行故障诊断

        Args:
            incident_id: 故障ID
            alert_info: 告警信息 {alert_name, service, severity, labels}
        """
        report = DiagnosisReport(
            incident_id=incident_id,
            start_time=datetime.utcnow(),
            end_time=None,
            status=DiagnosisStatus.PLANNING,
            steps=[],
            hypotheses=[],
            root_cause=None,
            remediation=[],
            summary=""
        )

        try:
            # 阶段1: 规划排障步骤
            plan = self._plan_diagnosis(alert_info)

            # 阶段2: 执行排障循环(ReAct模式)
            for iteration in range(self.max_iterations):
                report.status = DiagnosisStatus.EXECUTING

                # 选择下一步动作
                next_action = self._select_next_action(
                    plan, report.steps, report.hypotheses
                )

                if not next_action:
                    break

                # 执行工具调用
                step = self._execute_action(
                    iteration + 1, next_action, alert_info
                )
                report.steps.append(step)

                # 推理分析
                report.status = DiagnosisStatus.REASONING
                new_hypotheses = self._reason(
                    step, report.hypotheses, alert_info
                )
                report.hypotheses.extend(new_hypotheses)

                # 验证假设
                report.status = DiagnosisStatus.VERIFYING
                self._verify_hypotheses(report.hypotheses)

                # 检查是否找到高置信度根因
                best = self._get_best_hypothesis(report.hypotheses)
                if best and best.confidence >= self.confidence_threshold:
                    report.root_cause = best
                    break

            # 生成诊断报告
            report.status = DiagnosisStatus.COMPLETED
            report.end_time = datetime.utcnow()
            report.remediation = self._generate_remediation(
                report.root_cause
            )
            report.summary = self._generate_summary(report)

        except Exception as e:
            logger.error("诊断过程异常: %s", e, exc_info=True)
            report.status = DiagnosisStatus.FAILED
            report.end_time = datetime.utcnow()
            report.summary = f"诊断失败: {str(e)}"

        return report

    def _plan_diagnosis(self, alert_info: Dict) -> List[Dict]:
        """根据告警类型规划排障步骤"""
        alert_name = alert_info.get("alert_name", "")
        service = alert_info.get("service", "")

        # 从知识库获取排障SOP
        sop = self.kb.get_sop(alert_name)

        if sop:
            return sop.steps

        # 通用排障流程:从外到内逐层排查
        return [
            {"tool": "prometheus", "action": "check_service_metrics",
             "params": {"service": service, "metrics": [
                 "error_rate", "latency_p99", "qps"
             ]}},
            {"tool": "k8s", "action": "check_pod_status",
             "params": {"service": service}},
            {"tool": "elasticsearch", "action": "search_error_logs",
             "params": {"service": service, "level": "ERROR",
                        "window": "30m"}},
            {"tool": "jaeger", "action": "check_trace_errors",
             "params": {"service": service, "window": "30m"}},
            {"tool": "prometheus", "action": "check_infra_metrics",
             "params": {"service": service, "metrics": [
                 "cpu", "memory", "disk", "network"
             ]}},
            {"tool": "database", "action": "check_slow_queries",
             "params": {"service": service, "window": "30m"}},
        ]

    def _select_next_action(self, plan: List[Dict],
                            completed_steps: List[DiagnosisStep],
                            hypotheses: List[Hypothesis]) -> Optional[Dict]:
        """选择下一步排障动作"""
        completed_actions = {
            (s.tool, s.action) for s in completed_steps
        }

        # 优先执行验证假设的查询
        for hypothesis in hypotheses:
            if not hypothesis.verified and hypothesis.verification_queries:
                query = hypothesis.verification_queries[0]
                action_key = (query.get("tool", ""), query.get("action", ""))
                if action_key not in completed_actions:
                    return query

        # 否则按计划顺序执行
        for step in plan:
            action_key = (step.get("tool", ""), step.get("action", ""))
            if action_key not in completed_actions:
                return step

        return None

    def _execute_action(self, step_id: int, action: Dict,
                        alert_info: Dict) -> DiagnosisStep:
        """执行工具调用"""
        tool_name = action.get("tool", "")
        action_name = action.get("action", "")
        params = action.get("params", {})

        # 合并告警信息到查询参数
        params.update({
            "namespace": alert_info.get("namespace", "default"),
            "cluster": alert_info.get("cluster", "")
        })

        tool = self.tools.get(tool_name)
        if not tool:
            return DiagnosisStep(
                step_id=step_id,
                action=action_name,
                tool=tool_name,
                query=params,
                result=None,
                observation=f"工具 {tool_name} 不可用"
            )

        # 执行工具调用,捕获异常
        try:
            result = tool.execute(action_name, params)
            observation = self._interpret_result(
                tool_name, action_name, result
            )
        except Exception as e:
            result = None
            observation = f"工具调用失败: {str(e)}"

        return DiagnosisStep(
            step_id=step_id,
            action=action_name,
            tool=tool_name,
            query=params,
            result=result,
            observation=observation
        )

    def _reason(self, step: DiagnosisStep,
                existing: List[Hypothesis],
                alert_info: Dict) -> List[Hypothesis]:
        """根据新数据推理生成根因假设"""
        new_hypotheses = []
        service = alert_info.get("service", "")

        # 基于工具调用结果生成假设
        if step.tool == "prometheus" and step.action == "check_service_metrics":
            result = step.result or {}
            error_rate = result.get("error_rate", 0)
            latency = result.get("latency_p99", 0)

            if error_rate > 0.05:
                new_hypotheses.append(Hypothesis(
                    id=f"hyp-{len(existing)+len(new_hypotheses)+1}",
                    description=f"服务 {service} 错误率异常: {error_rate:.2%}",
                    confidence=0.5,
                    evidence=[f"错误率 {error_rate:.2%} 超过阈值5%"],
                    verification_queries=[
                        {"tool": "elasticsearch",
                         "action": "search_error_logs",
                         "params": {"service": service, "level": "ERROR"}}
                    ]
                ))

            if latency > 1.0:
                new_hypotheses.append(Hypothesis(
                    id=f"hyp-{len(existing)+len(new_hypotheses)+2}",
                    description=f"服务 {service} 延迟异常: P99={latency:.2f}s",
                    confidence=0.4,
                    evidence=[f"P99延迟 {latency:.2f}s 超过阈值1s"],
                    verification_queries=[
                        {"tool": "jaeger",
                         "action": "check_trace_errors",
                         "params": {"service": service}},
                        {"tool": "database",
                         "action": "check_slow_queries",
                         "params": {"service": service}}
                    ]
                ))

        elif step.tool == "database" and step.action == "check_slow_queries":
            result = step.result or {}
            slow_queries = result.get("slow_queries", [])

            if slow_queries:
                top_query = slow_queries[0]
                new_hypotheses.append(Hypothesis(
                    id=f"hyp-{len(existing)+len(new_hypotheses)+1}",
                    description=f"数据库慢查询: {top_query.get('query', '')[:100]}",
                    confidence=0.7,
                    evidence=[
                        f"慢查询耗时 {top_query.get('duration_ms', 0)}ms",
                        f"影响行数 {top_query.get('rows_examined', 0)}"
                    ],
                    verification_queries=[
                        {"tool": "database",
                         "action": "check_lock_status",
                         "params": {"service": service}}
                    ]
                ))

        # 更新已有假设的置信度
        for hyp in existing:
            if step.observation and any(
                e in step.observation for e in hyp.evidence
            ):
                hyp.confidence = min(hyp.confidence + 0.1, 1.0)

        return new_hypotheses

    def _verify_hypotheses(self, hypotheses: List[Hypothesis]):
        """验证假设:检查验证查询的结果是否支持假设"""
        for hyp in hypotheses:
            if hyp.verified:
                continue
            # 如果验证查询已执行且有结果,更新置信度
            if hyp.verification_result:
                hyp.verified = True
                # 验证通过增加置信度,验证不通过降低置信度
                if "confirmed" in hyp.verification_result:
                    hyp.confidence = min(hyp.confidence + 0.2, 1.0)
                else:
                    hyp.confidence = max(hyp.confidence - 0.3, 0.0)

    def _get_best_hypothesis(self,
                             hypotheses: List[Hypothesis]) -> Optional[Hypothesis]:
        """获取最高置信度的假设"""
        if not hypotheses:
            return None
        return max(hypotheses, key=lambda h: h.confidence)

    def _generate_remediation(self,
                              root_cause: Optional[Hypothesis]) -> List[Dict]:
        """生成处置建议"""
        if not root_cause:
            return [{"action": "manual_investigation",
                     "description": "未找到高置信度根因,需人工排查",
                     "risk": "high"}]

        # 从知识库获取处置方案
        remediation = self.kb.get_remediation(root_cause.description)

        if remediation:
            return remediation

        # 通用处置建议
        return [
            {"action": "investigate",
             "description": f"根因: {root_cause.description}",
             "risk": "medium"},
            {"action": "escalate",
             "description": "建议升级到相关团队处理",
             "risk": "low"}
        ]

    def _generate_summary(self, report: DiagnosisReport) -> str:
        """生成诊断摘要"""
        duration = (report.end_time - report.start_time).total_seconds()
        steps_count = len(report.steps)

        summary = (
            f"故障诊断完成,耗时 {duration:.1f}s,"
            f"执行 {steps_count} 步排查。"
        )

        if report.root_cause:
            summary += (
                f"根因: {report.root_cause.description},"
                f"置信度 {report.root_cause.confidence:.0%}。"
            )
        else:
            summary += "未找到高置信度根因,需人工介入。"

        return summary

    def _interpret_result(self, tool_name: str, action_name: str,
                          result: Any) -> str:
        """解释工具调用结果"""
        if result is None:
            return "查询无结果"
        if isinstance(result, dict):
            # 提取关键信息
            keys = list(result.keys())[:5]
            return f"查询返回 {len(result)} 个字段: {', '.join(keys)}"
        return str(result)[:200]

3.2 工具集实现示例

#!/usr/bin/env python3
"""排障工具集:封装各数据源的查询接口"""

import requests
from typing import Any, Dict, Optional
import logging

logger = logging.getLogger(__name__)


class PrometheusTool:
    """Prometheus 指标查询工具"""

    def __init__(self, base_url: str = "http://prometheus:9090"):
        self.base_url = base_url

    def execute(self, action: str, params: Dict) -> Dict:
        """执行 Prometheus 查询"""
        if action == "check_service_metrics":
            return self._check_service_metrics(params)
        elif action == "check_infra_metrics":
            return self._check_infra_metrics(params)
        else:
            return {"error": f"未知动作: {action}"}

    def _check_service_metrics(self, params: Dict) -> Dict:
        """查询服务级指标"""
        service = params.get("service", "")
        result = {}

        # 错误率
        error_rate_query = (
            f'sum(rate(http_requests_total{{service="{service}",'
            f'status=~"5.."}}[5m]))'
            f' / sum(rate(http_requests_total{{service="{service}"}}[5m]))'
        )
        result["error_rate"] = self._query_scalar(error_rate_query)

        # P99延迟
        latency_query = (
            f'histogram_quantile(0.99,'
            f' sum(rate(http_request_duration_seconds_bucket'
            f'{{service="{service}"}}[5m])) by (le))'
        )
        result["latency_p99"] = self._query_scalar(latency_query)

        # QPS
        qps_query = (
            f'sum(rate(http_requests_total{{service="{service}"}}[5m]))'
        )
        result["qps"] = self._query_scalar(qps_query)

        return result

    def _check_infra_metrics(self, params: Dict) -> Dict:
        """查询基础设施指标"""
        service = params.get("service", "")
        result = {}

        cpu_query = (
            f'sum(rate(container_cpu_usage_seconds_total'
            f'{{pod=~"{service}-.*"}}[5m]))'
        )
        result["cpu"] = self._query_scalar(cpu_query)

        memory_query = (
            f'sum(container_memory_working_set_bytes'
            f'{{pod=~"{service}-.*"}})'
        )
        result["memory"] = self._query_scalar(memory_query)

        return result

    def _query_scalar(self, query: str) -> float:
        """执行 PromQL 查询并返回标量值"""
        try:
            resp = requests.get(
                f"{self.base_url}/api/v1/query",
                params={"query": query},
                timeout=10
            )
            data = resp.json()
            if data["status"] == "success" and data["data"]["result"]:
                return float(data["data"]["result"][0]["value"][1])
        except Exception as e:
            logger.warning("Prometheus查询失败: %s", e)
        return 0.0


class ElasticsearchTool:
    """Elasticsearch 日志查询工具"""

    def __init__(self, base_url: str = "http://elasticsearch:9200"):
        self.base_url = base_url

    def execute(self, action: str, params: Dict) -> Dict:
        if action == "search_error_logs":
            return self._search_error_logs(params)
        return {"error": f"未知动作: {action}"}

    def _search_error_logs(self, params: Dict) -> Dict:
        """搜索错误日志"""
        service = params.get("service", "")
        level = params.get("level", "ERROR")
        window = params.get("window", "30m")

        query = {
            "query": {
                "bool": {
                    "must": [
                        {"term": {"service": service}},
                        {"term": {"level": level}},
                        {"range": {"@timestamp": {"gte": f"now-{window}"}}}
                    ]
                }
            },
            "size": 20,
            "sort": [{"@timestamp": {"order": "desc"}}]
        }

        try:
            resp = requests.post(
                f"{self.base_url}/app-logs-*/_search",
                json=query,
                timeout=15
            )
            data = resp.json()
            hits = data.get("hits", {}).get("hits", [])
            return {
                "total": data.get("hits", {}).get("total", {}).get("value", 0),
                "logs": [
                    hit["_source"] for hit in hits[:10]
                ]
            }
        except Exception as e:
            logger.warning("ES查询失败: %s", e)
            return {"total": 0, "logs": []}

四、AI 排障 Agent 的局限性与工程妥协

4.1 推理链路的可靠性

Agent 的推理质量取决于每一步工具调用的结果。如果 Prometheus 查询超时、ES 返回不完整数据,推理链路就会断裂或产生错误假设。解决方案:每个工具调用设置超时和重试,对异常结果做降级处理(如查询失败时跳过该步骤,继续其他推理),同时限制最大迭代次数防止死循环。

4.2 知识库的维护成本

排障 SOP 和历史案例需要持续维护。新服务上线时没有历史案例,Agent 只能走通用流程,效率较低。建议:每次人工排障后,将排障过程和结论自动入库,形成案例积累。同时定期由资深工程师审核知识库,淘汰过时的 SOP。

4.3 自动执行的风险

Agent 输出处置建议后,是否允许自动执行?低风险操作(如扩容、重启非核心 Pod)可以自动执行,但高风险操作(如数据库切换、配置变更)必须人工确认。建议设置操作风险评级,高风险操作即使 Agent 有高置信度,也需要人工审批后才能执行。

4.4 禁用场景

以下场景不适合 AI 排障 Agent:第一,安全事件排查(如入侵检测),Agent 的工具调用可能破坏取证现场;第二,涉及敏感数据的故障(如用户隐私泄露),Agent 的日志记录可能违反合规要求;第三,基础设施级故障(如机房断电),Agent 本身可能无法运行。

五、总结

AI 排障 Agent 将故障诊断从"人工逐项排查"升级为"自动推理循环"。通过 ReAct 模式,Agent 自动规划排障步骤、调用工具获取数据、推理生成根因假设、验证假设直到找到高置信度根因。但推理链路的可靠性依赖工具调用的稳定性,知识库需要持续维护,自动执行必须限制在低风险操作。务实的落地路径:先从高频故障类型(如服务超时、Pod 重启)入手,积累排障案例,逐步扩展覆盖范围。让排障从"靠经验"变成"靠流程",让开发少背锅。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐