ayl-duke-dev-28/incident-response
GitHub: ayl-duke-dev-28/incident-response
一个基于 Claude 和 FastAPI 的自主事件响应系统,在告警触发后几秒内完成 AI 分诊、自动修复和 Slack 简报生成,并在事件解决时自动输出复盘报告。
Stars: 0 | Forks: 0
# 自主事件响应
一个在告警触发的瞬间响应生产环境宕机的 AI 系统。告警输入 → 几秒内输出 Slack 简报和自动化修复,并在事件被标记为已解决的那一刻生成复盘报告。
## 告警时会发生什么
1. **Webhook 触发 `POST /alerts`**,携带 Datadog/PagerDuty/通用样式的 JSON payload。
2. **认证**:共享 token(`X-Webhook-Token`)*或* HMAC 签名(`X-Datadog-Signature` / `X-PagerDuty-Signature` / `X-Webhook-Signature`) — 任何一种凭证即可。
3. **速率限制**:内存中的滑动窗口限制器,以 `(client_ip, service)` 为键。
4. **去重**:告警通过 `(service, metric, severity, 15分钟时间桶)` 进行指纹识别。同一时间桶内的重复项会作为时间线条目附加到现有事件中,而不是开启新的事件。
5. `/alerts` 在个位数毫秒内返回 `202 {"status": "accepted", "incident_id": "inc-..."}`,并将告警放入后台 worker 的队列中。
6. **Worker 运行编排器**,通过 `asyncio.gather` 并行执行三项操作:
- **分诊 agent** — 提取过去 90 分钟的 commits,让 Claude 根据时间、文件、消息和 diff 大小对最多 3 个可疑项进行排名,并返回每个可疑项的置信度及推理。
- **Runbook agent** — 让 Claude 从 `./runbooks/*.md` 中挑选最佳匹配的 runbook(包含标题 + 标签 + 正文第一行,不发送完整内容以节省 token);在置信度低于 0.3 时返回 `None`,以避免“过度自信但错误”的匹配。
- **影响评估 agent** — 提取错误率 + 请求率序列 + 活跃用户数,让 Claude 估算出受影响的整数用户估计值 + 峰值错误率。
- 在分诊触发之前,`history.py` 会根据 service + 关键词 + 近期程度对过往复盘报告进行评分,并将排名前 K 的结果作为 few-shot 上下文传递给分诊 prompt,**并且**作为“以往类似事件”部分传入简报。
7. **Slack 简报**组合完成(确定性模板,无 LLM)并发布到 incidents 频道。每当检索器找到命中结果时,会包含一个“以往类似事件”区块,其中包含日期、复盘报告链接、相似度得分和根因片段 —— 这样on-call 工程师无需打开 wiki 就能看到“我们以前修过这个问题,方法如下”。
8. **Runbook 修复** — 如果匹配的 runbook 声明了一个 `## Automated actions` JSON 区块,白名单内 `auto: true` 的步骤将通过 shell 执行器运行,并作为 threaded reply 发布结果。默认的 `REMEDIATION_MODE=mock` 永远不会触碰系统。
9. **事件状态**在每一步之后都会持久化到 SQLite,因此在中途重启不会丢失任何内容。
当有人触发 `POST /alerts/{id}/resolve` 时:
- Claude 会生成一份**无指责的复盘报告**(摘要 → 影响 → 时间线 → 根因 → 检测 → 缓解 → 带有 P0/P1/P2 的行动项 → 经验教训)并保存至 `./postmortems/YYYY-MM-DD-inc-*.md`。
- 一条 Slack threaded reply 会将复盘报告链接回原始简报。
外部调用(Anthropic、GitHub REST、Slack webhook、Datadog)均通过带有 jitter 的指数退避重试进行封装。每个集成在相同的接口背后都有一个 `Mock*` 和一个真实客户端 —— 整个流程可离线运行并进行测试。
## 端点
| 方法 | 路径 | 用途 |
|---|---|---|
| `POST` | `/alerts` | 将事件加入队列。返回 `202 {status, incident_id}`。 |
| `POST` | `/alerts/{id}/resolve` | 标记为已解决;生成复盘报告;thread reply。返回完整事件。 |
| `GET` | `/incidents/{id}` | 当前状态(状态、分诊报告、时间线、复盘报告路径)。 |
| `GET` | `/healthz` | 存活检测。 |
| `GET` | `/readyz` | 存活检测 + 当前 worker 队列深度。 |
## 快速开始
```
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]" # or ".[dev,otel]" for OpenTelemetry export
cp .env.example .env # set ANTHROPIC_API_KEY at minimum
uvicorn incident_response.main:create_app --factory --reload --port 8080
```
触发一个测试告警:
```
curl -X POST http://localhost:8080/alerts \
-H "x-webhook-token: change-me" \
-H "content-type: application/json" \
-d '{
"id": "ddg-9273",
"title": "Checkout 5xx > 5%",
"service": "checkout",
"severity": "sev2",
"triggered_at": "2026-07-02T21:05:00+00:00",
"metric": "http.error_rate",
"value": 0.184
}'
# → 202 {"status":"accepted","incident_id":"inc-ddg-9273"}
```
分诊完成后(通常需要 2–5 秒)获取事件:
```
curl http://localhost:8080/incidents/inc-ddg-9273
```
解决并自动生成复盘报告:
```
curl -X POST http://localhost:8080/alerts/inc-ddg-9273/resolve \
-H "x-webhook-token: change-me" \
-H "content-type: application/json" \
-d '{"resolution_note": "rolled back a1b2c3d"}'
```
复盘报告会生成在 `./postmortems/YYYY-MM-DD-inc-ddg-9273.md`。
## 对接真实系统
在 `.env` 中切换模式环境变量:
| 环境变量 | 模式 | 说明 |
|---|---|---|
| `GITHUB_MODE` | `mock`, `rest` | `rest` 需要 `GITHUB_TOKEN` + `GITHUB_REPO` |
| `SLACK_MODE` | `mock`, `webhook`, `bot` | `webhook` 需要 `SLACK_WEBHOOK_URL`;`bot` 需要 `SLACK_BOT_TOKEN` 并允许通过 `chat.update` 实现流式简报更新 |
| `METRICS_MODE` | `mock`, `datadog` | `datadog` 需要 `DATADOG_API_KEY` + `DATADOG_APP_KEY` |
| `REMEDIATION_MODE` | `mock`, `shell` | `shell` 仅运行在 `REMEDIATION_ALLOWED_COMMANDS` 中且带有 `auto: true` 的步骤 |
添加其他提供商(GitLab、Prometheus、ServiceNow)只需在 `integrations/` 中增加一个新类,并在对应的 `build_*_client` 工厂中加入一行代码。
## Runbook 库
位于 `./runbooks/` 中的 Markdown 文件。Frontmatter 驱动搜索;一个可选的 JSON 区块用于声明修复步骤。
```
---
title: Checkout service elevated error rate
tags: [checkout, http_5xx]
---
## 首要操作
1. Confirm the alert in Datadog.
2. Check the last 3 deploys...
## 自动化操作
```json
[
{"name": "flip pricing cache off", "command": "feature-flag set checkout.pricing_cache off", "auto": true},
{"name": "rollback last deploy", "command": "deploy rollback checkout --confirm"}
]
```
```
- 只有带有 `"auto": true` **且** 第一个 token 存在于 `REMEDIATION_ALLOWED_COMMANDS` 中的步骤才会执行。
- 其他所有内容都会被报告为 `skipped_not_auto` / `skipped_not_allowed` —— 完整的审计跟踪,杜绝静默跳过。
- 默认的 `REMEDIATION_MODE=mock` 总是返回 `dry_run` —— 可以安全地指向 runbook 库而无需先开启执行权限。
- 结果(已执行、dry_run、已跳过、已失败)会作为 threaded Slack reply 发布在事件简报中。
## 生产环境加固
| 关注点 | 所在位置 |
|---|---|
| 重试/退避(外部调用) | `retry.py` — Anthropic、REST GitHub、Slack webhook、Datadog 上的装饰器 |
| 包含 `incident_id`/`trace_id` 关联的结构化 JSON 日志 | `logging_config.py` — 基于 ContextVar,formatter 自动附加 |
| OpenTelemetry(可选) | `telemetry.py` — 如果设置了 `OTEL_EXPORTER_OTLP_ENDPOINT`,则自动为 FastAPI + httpx 植入监控,否则无操作 |
| HMAC webhook 认证 | `security.py` — Datadog (base64)、PagerDuty (multi-key `v1=`)、通用 (hex) |
| 速率限制 | `rate_limit.py` — 基于 `(ip, service)` 的滑动窗口,可注入时钟 |
| 异步 worker(快速 202) | `queue.py` — `asyncio.Queue` + 通过 FastAPI lifespan 启动/停止的 worker |
| 告警去重 | `dedup.py` — 带有 TTL 的有界 LRU,每个 `(service, metric, severity, bucket)` 对应一个指纹 |
| Runbook 修复 | `executor.py` — 默认为 dry-run,`ShellExecutor` 白名单可选开启 |
| 用于分诊 + 简报的历史复盘 RAG | `history.py` — service + 关键词 + 近期程度评分;命中结果会反馈给分诊 prompt,**并且**作为带有复盘链接的“以往类似事件”区块出现在 Slack 简报中 |
| 流式 Slack 简报更新 | `agents/brief.py::compose_streaming_brief` + `_stream_triage` — 初始为占位符,然后在每个 agent 完成时执行 `chat.update`(仅限 bot-token 模式) |
| 修复后验证循环 | `verification.py` — 在自动执行后轮询错误率,将已恢复 / 正在改善 / 仍然偏高的情况发布到事件 thread 中 |
## 测试
```
pytest
```
**80 个测试,无需网络。**全程使用 `FakeLLM` 和 mock 适配器。
## 布局
```
src/incident_response/
main.py FastAPI app, lifespan-managed worker, auth + rate limit + dedup
orchestrator.py Alert → dedup → triage → brief → remediate → resolve → post-mortem
models.py Immutable pydantic domain models (frozen where safe)
db.py SQLite persistence (one JSON blob per incident)
config.py Env-driven pydantic-settings
runbooks_loader.py Markdown frontmatter parser
retry.py Exponential-backoff-with-jitter decorator
logging_config.py JSON logs with ContextVar correlation
telemetry.py Optional OpenTelemetry setup (guarded imports)
security.py Datadog / PagerDuty / generic HMAC verification
rate_limit.py Sliding-window in-memory limiter
dedup.py Fingerprinting + bounded TTL LRU
queue.py In-process async worker
executor.py Runbook remediation (mock + allow-listed shell)
history.py Post-mortem RAG (keyword + service + recency scoring); feeds triage prompt + brief
verification.py Post-remediation recovery loop
agents/
llm.py Anthropic wrapper (retried) + FakeLLM for tests
triage.py Suspect-commit ranking
runbook.py Runbook selection
impact.py User-impact estimate
brief.py Deterministic Slack brief composer
postmortem.py LLM post-mortem generator with deterministic fallback
integrations/
github.py Recent commits (Mock + REST, retried)
slack.py Post + thread (Mock + Webhook, retried)
metrics.py Error rate / rps / active users (Mock + Datadog, retried)
tests/ pytest suite (80 tests, no network)
runbooks/ Example markdown runbooks (checkout, redis, auth)
postmortems/ Written at resolve time
```
## 运维人员注意事项
- **重启安全性**:SQLite 会在每一步之后写入,但内存队列*并不*具备持久性 —— 在分诊过程中强行终止会丢失排队中的告警。可将 `queue.py` 替换为 arq/Celery/Dramatiq(相同的接口)以实现至少一次的持久性。
- **多实例**:速率限制器和去重索引都在内存中。在水平扩展时,请使用 Redis 为其提供后端支持。
- **成本控制**:每个事件三次 Claude 调用(分诊、runbook、影响评估),加上解决时的一次调用(复盘报告)。按照 Sonnet 4.6 的定价,根据 runbook 库的大小,每个事件的成本约为 ~$0.05–0.15。
- **复盘报告生成器的故障模式**:如果 Claude 返回格式错误的 JSON,`agents/postmortem.py` 会回退到确定性模板,而不是完全丢弃复盘报告。
- **尚未实现的功能**:不支持跨服务的事件合并、无 on-call 轮换查询、无 Jira/Linear 工单创建、无针对可疑 commit 的 PR 批注。以上每一项都可以作为现有适配器模式背后独立的集成来实现。
标签:AIOps, 事故管理, 告警响应, 安全规则引擎, 用户代理, 自动化运维, 计算机取证, 逆向工具