vlm.md
← 所有食谱 · 图表/表格 · 进阶

仪表盘截图数据读取

让 agent 通过截图从 Grafana、DataDog 等仪表盘提取 KPI 数值、告警状态和指标读数,以触发下游动作。

2026/4/30 · vlm.md · 推荐模型: GPT-4oGemini 1.5 Pro

场景

你的 agent 监控 Grafana、DataDog 或业务分析仪表盘,通过定期截图提取 KPI 数值、告警状态和指标读数,从而触发下游动作(告警通知、自动工单、报告生成)——无需访问仪表盘的底层 API。

典型用例:

  • 监控生产环境服务 SLA 指标,超阈值时发送 Slack 告警
  • 每日截图提取业务核心指标并写入报表
  • 将红/黄/绿告警状态同步到值班系统

推荐模型

模型适用场景
GPT-4o对颜色编码告警和多面板布局识别最准确
Gemini 1.5 Pro大分辨率截图(4K 仪表盘)处理更高效;成本更低

Claude 3.5 Sonnet 在此场景下表现不如前两者稳定,尤其对深色主题仪表盘(如 Grafana 默认深色主题)的识别率偏低。

Prompt 模板

你是一个仪表盘数据提取专家。请从这张仪表盘截图中提取所有可见的指标数据,严格按照以下 JSON 格式返回,不要有任何多余文字。

提取规则:
1. 告警颜色:对每个指标,报告其背景色或状态指示器颜色(red/yellow/green/unknown)。
2. 趋势图:对于迷你折线图(sparkline)或小型趋势图,不要猜测具体数值,只报告趋势方向(up/down/flat)。
3. 可读性:如果某个面板因分辨率或动画模糊无法读取,在 value 字段填 null,并在 notes 字段说明原因。

返回格式:
{
  "dashboard_title": "仪表盘标题(如有)",
  "captured_at": "截图时刻描述(如图中有时间戳)",
  "panels": [
    {
      "panel_title": "面板名称",
      "value": "数值或状态文字,如 99.9% 或 CRITICAL",
      "unit": "单位,如 % / ms / rps",
      "status_color": "red | yellow | green | unknown",
      "trend": "up | down | flat | null(无趋势图则为 null)",
      "notes": "备注,如数值模糊、单位不确定等"
    }
  ]
}

代码示例

import base64
import json
import time
from pathlib import Path
from openai import OpenAI

client = OpenAI()

PROMPT = """你是一个仪表盘数据提取专家。请从这张仪表盘截图中提取所有可见的指标数据,严格按照以下 JSON 格式返回,不要有任何多余文字。

提取规则:
1. 告警颜色:对每个指标,报告其背景色或状态指示器颜色(red/yellow/green/unknown)。
2. 趋势图:对于迷你折线图(sparkline)或小型趋势图,不要猜测具体数值,只报告趋势方向(up/down/flat)。
3. 可读性:如果某个面板因分辨率或动画模糊无法读取,在 value 字段填 null,并在 notes 字段说明原因。

返回格式:
{
  "dashboard_title": "仪表盘标题(如有)",
  "captured_at": "截图时刻描述(如图中有时间戳)",
  "panels": [
    {
      "panel_title": "面板名称",
      "value": "数值或状态文字,如 99.9% 或 CRITICAL",
      "unit": "单位,如 % / ms / rps",
      "status_color": "red | yellow | green | unknown",
      "trend": "up | down | flat | null",
      "notes": "备注"
    }
  ]
}"""


def extract_dashboard(image_path: str) -> dict:
    image_data = base64.b64encode(Path(image_path).read_bytes()).decode()
    suffix = Path(image_path).suffix.lower().lstrip(".")
    mime_type = {"jpg": "image/jpeg", "jpeg": "image/jpeg", "png": "image/png"}.get(
        suffix, "image/jpeg"
    )

    response = client.chat.completions.create(
        model="gpt-4o",
        response_format={"type": "json_object"},
        messages=[
            {
                "role": "system",
                "content": "你是仪表盘数据提取助手,只输出符合要求的 JSON,不输出任何其他内容。",
            },
            {
                "role": "user",
                "content": [
                    {
                        "type": "image_url",
                        "image_url": {"url": f"data:{mime_type};base64,{image_data}"},
                    },
                    {"type": "text", "text": PROMPT},
                ],
            },
        ],
        max_tokens=1024,
    )

    return json.loads(response.choices[0].message.content)


def check_alerts(dashboard_data: dict) -> list[dict]:
    """返回所有红色告警面板。"""
    alerts = []
    for panel in dashboard_data.get("panels", []):
        if panel.get("status_color") == "red":
            alerts.append({
                "panel": panel["panel_title"],
                "value": panel.get("value"),
                "notes": panel.get("notes"),
            })
    return alerts


def capture_and_compare(screenshot_paths: list[str]) -> dict:
    """
    对多张截图分别提取,比较数值是否一致(用于动画捕获场景)。
    返回各面板在多次截图中的数值列表。
    """
    results = [extract_dashboard(p) for p in screenshot_paths]
    comparison: dict[str, list] = {}
    for result in results:
        for panel in result.get("panels", []):
            title = panel["panel_title"]
            comparison.setdefault(title, []).append(panel.get("value"))
    return comparison


if __name__ == "__main__":
    # 单张截图提取
    result = extract_dashboard("dashboard.png")
    print(json.dumps(result, ensure_ascii=False, indent=2))

    # 检查告警
    alerts = check_alerts(result)
    if alerts:
        print(f"\n🔴 发现 {len(alerts)} 个红色告警:")
        for a in alerts:
            print(f"  - {a['panel']}: {a['value']}")
    else:
        print("\n✅ 无红色告警")

运行:

pip install openai
python extract_dashboard.py

预期输出:

{
  "dashboard_title": "生产环境监控",
  "captured_at": "2026-04-30 14:23:00",
  "panels": [
    {
      "panel_title": "API 成功率",
      "value": "99.2%",
      "unit": "%",
      "status_color": "green",
      "trend": "flat",
      "notes": null
    },
    {
      "panel_title": "P99 延迟",
      "value": "847",
      "unit": "ms",
      "status_color": "red",
      "trend": "up",
      "notes": "数值接近告警阈值 800ms,已变红"
    },
    {
      "panel_title": "数据库连接数",
      "value": null,
      "unit": null,
      "status_color": "unknown",
      "trend": null,
      "notes": "面板图像模糊,可能正在刷新动画中"
    }
  ]
}

踩坑记录

坑 1:颜色编码告警判断不稳定

如果不明确要求报告颜色,模型有时只描述数值而不说明告警状态,导致无法自动判断是否触发下游告警。始终在 prompt 中要求 status_color 字段,并只接受固定枚举值(red/yellow/green/unknown),不接受自然语言描述(如「偏红」「橙色」)。

对深色主题仪表盘(Grafana 默认主题),建议截图前切换为浅色主题,或对图片做亮度增强:

from PIL import Image, ImageEnhance

def brighten(path: str, factor: float = 1.4) -> str:
    img = Image.open(path)
    brightened = ImageEnhance.Brightness(img).enhance(factor)
    out = path.replace(".", "_bright.")
    brightened.save(out)
    return out

坑 2:迷你图(Sparkline)不适合精确读数

仪表盘中的 Sparkline 面积通常很小(宽 100-150px),模型很难从中读出精确数值。强行要求精确数值会导致模型产生幻觉。正确做法:在 prompt 中明确要求只报告趋势方向(up/down/flat),不要求具体数值。如需精确数值,应调用仪表盘的原生 API。

坑 3:动态仪表盘截图时机问题

Grafana 等仪表盘有数据刷新动画,截图时如果恰好在动画过渡期,某些面板会显示加载状态或数值模糊。解决方案:

import time

def capture_stable_screenshot(capture_fn, screenshot_paths_out: list, n: int = 3, interval: float = 2.0):
    """
    连续截取 n 张截图,间隔 interval 秒。
    返回读数最一致的结果(取出现次数最多的数值)。
    """
    snapshots = []
    for i in range(n):
        path = f"/tmp/dashboard_snap_{i}.png"
        capture_fn(path)  # 你的截图函数
        screenshot_paths_out.append(path)
        if i < n - 1:
            time.sleep(interval)
    return screenshot_paths_out

对提取结果中 valuenull 的面板,重新截图后再次提取。