Expedia STAR:用服务遥测与大模型加速生产事故调查

2026-07-23 40 预计阅读时间: 1 分钟
来源: infoq.com AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:11 分钟

生产事故调查最耗时的部分,往往不是查看某一张监控图,而是在日志、指标、链路追踪和部署记录之间反复切换,并把零散信号整理成可验证的故障假设。Expedia Group 推出的内部平台 STAR,尝试用结构化工作流和大模型缩短这一过程,同时保留工程师对结论的审核权。

根据公开摘要,STAR 使用 FastAPI、Datadog、Celery、Redis 和 Langfuse 构建。这个技术组合透露出一个重要设计方向:LLM 并不直接“盯着生产环境猜答案”,而是被放进一个可追踪、可排队、可审查的遥测分析流程中。

关键不是聊天框,而是结构化调查工作流

一次生产事故可能同时包含多类证据:

  • 服务指标:错误率、延迟、吞吐量、资源饱和度;
  • 日志:异常堆栈、超时、重试和依赖错误;
  • 分布式追踪:慢调用、失败跨度和依赖传播路径;
  • 变更信息:部署版本、配置调整和功能开关;
  • 事故上下文:受影响服务、开始时间和用户症状。

如果直接把大量原始数据塞进提示词,通常会遇到上下文超限、噪声过多、成本失控和结论无法复现等问题。更稳妥的工作流是先缩小时间窗口和服务范围,再由确定性代码聚合遥测数据,最后让模型解释已经筛选过的证据。

一个典型流程可以拆成以下阶段:

  1. 接收事故编号、服务名和调查时间窗;
  2. 异步查询指标、日志与追踪数据;
  3. 计算基线偏差,提取异常事件和高频错误;
  4. 将证据整理成固定结构,再调用 LLM;
  5. 输出根因候选、证据引用、置信度与下一步验证动作;
  6. 由值班工程师确认、驳回或补充结论。

这种结构让 AI 承担信息压缩和假设生成,而不是替代事故指挥者作出最终判断。

这套组件分别解决什么问题

FastAPI 适合提供事故分析 API,并通过请求模型约束服务名、时间范围等输入。Celery 把耗时的遥测查询和模型调用移出 HTTP 请求,避免分析任务占满 Web 进程。Redis 可以承担任务队列或短期状态存储,让前端轮询调查进度。

Datadog 提供生产遥测数据。实际实现中,访问层应当限制查询窗口、数据量和允许访问的服务,避免一次调查拉取无边界日志。Langfuse 一类 LLM 可观测工具则可记录提示词版本、模型响应、延迟、费用和人工反馈,帮助团队判断某次根因分析为何产生了特定结论。

把这些组件组合起来后,平台本身也必须具备可观测性。至少应记录:

  • 每个阶段的开始时间、结束时间和失败原因;
  • 查询过的数据源、时间窗口与过滤条件;
  • 提交给模型的证据摘要,而非无法核对的隐式上下文;
  • 模型、提示词版本和采样参数;
  • 工程师最终接受或否决了哪些判断。

可以这样实践:搭建最小调查任务 API

下面不是 STAR 的源码,而是基于摘要中技术栈整理的最小示例。它用 FastAPI 接收事故信息,用 Celery 和 Redis 执行异步分析,并用模拟遥测数据展示结构化输出。运行前需要安装 Docker 和 Python 3.11 以上版本。

创建 requirements.txt

fastapi==0.115.6
uvicorn[standard]==0.34.0
celery[redis]==5.4.0
pydantic==2.10.4

创建 app.py

from datetime import datetime
from typing import Literal

from celery import Celery
from fastapi import FastAPI
from pydantic import BaseModel, Field

celery = Celery(
    "incident_analyzer",
    broker="redis://localhost:6379/0",
    backend="redis://localhost:6379/1",
)
app = FastAPI(title="Telemetry Incident Analyzer")


class InvestigationRequest(BaseModel):
    incident_id: str = Field(min_length=1)
    service: str = Field(min_length=1)
    start: datetime
    end: datetime


class InvestigationStatus(BaseModel):
    task_id: str
    state: str
    result: dict | None = None


@celery.task(name="investigate")
def investigate(payload: dict) -> dict:
    # 实际项目应在这里查询 Datadog,并对日志、指标和追踪进行聚合。
    evidence = [
        {
            "type": "metric",
            "signal": "http_5xx_rate",
            "observation": "rose from 0.2% to 8.7%",
        },
        {
            "type": "trace",
            "signal": "payment-api latency",
            "observation": "p95 increased to 4.8 seconds",
        },
        {
            "type": "deployment",
            "signal": "release",
            "observation": "version 2025.03.08 deployed 6 minutes earlier",
        },
    ]

    # 生产版本可将 evidence 传给 LLM,但必须要求模型引用证据并声明不确定性。
    return {
        "incident_id": payload["incident_id"],
        "service": payload["service"],
        "assessment": {
            "status": "needs_human_review",
            "hypothesis": "The recent release may have increased downstream latency.",
            "confidence": "medium",
            "evidence_indexes": [0, 1, 2],
            "next_actions": [
                "Compare traces before and after the deployment",
                "Check timeout configuration changes",
                "Consider a controlled rollback",
            ],
        },
        "evidence": evidence,
    }


@app.post("/investigations", status_code=202)
def create_investigation(request: InvestigationRequest) -> dict:
    if request.end <= request.start:
        return {"error": "end must be later than start"}
    task = investigate.delay(request.model_dump(mode="json"))
    return {"task_id": task.id, "state": "queued"}


@app.get("/investigations/{task_id}", response_model=InvestigationStatus)
def get_investigation(task_id: str) -> InvestigationStatus:
    task = celery.AsyncResult(task_id)
    result = task.result if task.successful() else None
    return InvestigationStatus(task_id=task_id, state=task.state, result=result)

启动 Redis、Celery worker 和 API:

docker run --rm --name telemetry-redis -p 6379:6379 redis:7-alpine

在另外两个终端运行:

python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
celery -A app.celery worker --loglevel=INFO
source .venv/bin/activate
uvicorn app:app --reload --port 8000

提交调查任务:

curl -sS -X POST http://localhost:8000/investigations \
  -H 'Content-Type: application/json' \
  -d '{
    "incident_id": "INC-1042",
    "service": "checkout-api",
    "start": "2025-03-08T10:00:00Z",
    "end": "2025-03-08T10:30:00Z"
  }'

响应中的 task_id 可用于查询结果:

curl -sS http://localhost:8000/investigations/替换为任务ID

接入真实 LLM 时,可以要求模型严格返回 JSON,并明确区分事实与推断。提示词可以这样设计:

You are assisting an on-call engineer with incident investigation.
Use only the supplied telemetry evidence.

Return JSON with:
- hypotheses: ranked root-cause candidates
- evidence_indexes: evidence supporting each candidate
- contradictions: evidence that weakens each candidate
- confidence: low, medium, or high
- next_actions: reversible validation steps

Do not claim causation from timing alone. If evidence is insufficient,
state that explicitly. Never invent logs, metrics, deployments, or traces.

人在环中不是装饰性审批

根因分析很容易把“同时发生”误写成“因果关系”。例如,某个版本上线后错误率升高,并不能单独证明该版本就是根因;流量变化、下游故障或配置漂移也可能发生在同一时间。平台应该主动展示反证,并要求工程师通过回滚、流量切换、配置对比或追踪采样验证假设。

同时需要防范遥测数据中的敏感信息。日志可能包含令牌、个人数据和业务字段,在发送给外部模型之前应执行脱敏、字段白名单、访问控制和保留期管理。对于高风险操作,模型只能建议,不能直接执行重启、扩容或回滚。

落地时从窄场景开始

引入类似 STAR 的能力时,可以先选择遥测质量较高、依赖关系清晰的一组服务,并限定为只读调查助手。上线前重点检查以下事项:

  • 每项结论能否追溯到具体指标、日志或链路证据;
  • 数据查询是否有服务范围、时间窗口和数量上限;
  • 模型失败或超时时,值班人员能否继续使用原有工具;
  • 是否记录提示词版本、成本、延迟和人工反馈;
  • 是否用历史事故回放评估准确率,而不是只看演示效果;
  • 是否明确禁止模型自动执行高风险生产操作。

这类平台的价值不在于给出一个听起来确定的答案,而在于更快组织证据、暴露未知项,并向工程师提供可验证的下一步动作。只有当调查过程能够审计、结论能够引用证据、人工能够推翻模型时,LLM 才真正适合进入生产事故响应链路。


相关推荐