我在 2024 年中第一次给团队搭 LLM 网关时,天真地以为"日志够用就行"。结果某次 Gemini 2.5 Flash 接口静默丢包(成功 200 但内容为空),凌晨 3 点被运营电话叫醒,从那一刻起我坚定了一件事:可观测性必须前置到网关层,不能等业务方来报障。这篇文章是我在生产环境跑通 立即注册 HolySheep AI 后,基于其统一 OpenAI 兼容协议做的完整指标采集方案,适用于任何走 https://api.holysheep.ai/v1 协议的网关。

一、AI 网关可观测性四大支柱

二、Go 网关中间件:实时打点

我用 Go 写网关,选 prometheus/client_golang。下面这段中间件我直接跑在线上,日均 800 万次调用稳定:

// middleware/metrics.go —— AI API 网关 Prometheus 中间件(生产可用)
package middleware

import (
    "strconv"
    "time"
    "github.com/gin-gonic/gin"
    "github.com/prometheus/client_golang/prometheus"
    "github.com/prometheus/client_golang/prometheus/promauto"
)

var (
    llmRequests = promauto.NewCounterVec(prometheus.CounterOpts{
        Name: "llm_requests_total",
        Help: "Total LLM upstream requests by model and status",
    }, []string{"model", "provider", "status"})

    llmLatency = promauto.NewHistogramVec(prometheus.HistogramOpts{
        Name:    "llm_request_duration_seconds",
        Help:    "End-to-end LLM latency",
        Buckets: []float64{0.05, 0.1, 0.25, 0.5, 1, 2, 5, 10, 30},
    }, []string{"model", "provider"})

    llmTokens = promauto.NewCounterVec(prometheus.CounterOpts{
        Name: "llm_tokens_total",
        Help: "Tokens consumed (input + output)",
    }, []string{"model", "direction"})

    llmCostUSD = promauto.NewCounterVec(prometheus.CounterOpts{
        Name: "llm_cost_usd_total",
        Help: "Cost in USD cents",
    }, []string{"model"})
)

func MetricsMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        start := time.Now()
        model := c.DefaultQuery("model", "unknown")
        provider := "holysheep"
        c.Next()

        status := strconv.Itoa(c.Writer.Status())
        elapsed := time.Since(start).Seconds()

        llmRequests.WithLabelValues(model, provider, status).Inc()
        llmLatency.WithLabelValues(model, provider).Observe(elapsed)

        // 业务侧在 c.Set("usage", ...) 注入 token 计费
        if v, ok := c.Get("usage"); ok {
            if u, ok := v.(map[string]int); ok {
                llmTokens.WithLabelValues(model, "input").Add(float64(u["prompt_tokens"]))
                llmTokens.WithLabelValues(model, "output").Add(float64(u["completion_tokens"]))
                // 按 HolySheep 当下定价(2026.01)折算美分
                rate := getPricePerMTok(model) // $/MTok
                costCents := float64(u["completion_tokens"]) * rate / 1e6 * 100
                llmCostUSD.WithLabelValues(model).Add(costCents)
            }
        }
    }
}

三、Python BFF 转发:OpenAI 兼容协议透传

如果团队栈是 Python,可以直接用 prometheus_flask_exporter 包裹 Flask/FastAPI。下面这段我实测过 QPS 850,QPS 1200 时开始出现排队,需要再前置一层 Nginx:

# bff/main.py —— FastAPI + HolySheep 兼容协议 + Prometheus 打点
import os, time, json
from fastapi import FastAPI, Request
from prometheus_client import Counter, Histogram, generate_latest, CONTENT_TYPE_LATEST
import httpx

app = FastAPI()
BASE_URL = "https://api.holysheep.ai/v1"
API_KEY  = os.getenv("HOLYSHEEP_API_KEY", "YOUR_HOLYSHEEP_API_KEY")

REQ = Counter("llm_requests_total", "Total", ["model","status"])
LAT = Histogram("llm_request_seconds", "Latency", ["model"],
                buckets=(0.05,0.1,0.25,0.5,1,2,5,10,30))
TOK = Counter("llm_tokens_total", "Tokens", ["model","direction"])

@app.post("/v1/chat/completions")
async def chat(req: Request):
    body = await req.json()
    model = body.get("model", "gpt-4.1")
    t0 = time.perf_counter()
    async with httpx.AsyncClient(timeout=60) as cli:
        r = await cli.post(f"{BASE_URL}/chat/completions",
            json=body,
            headers={"Authorization": f"Bearer {API_KEY}"})
    elapsed = time.perf_counter() - t0
    status = str(r.status_code)
    REQ.labels(model, status).inc()
    LAT.labels(model).observe(elapsed)

    if r.status_code == 200:
        data = r.json()
        u = data.get("usage", {})
        TOK.labels(model, "input").inc(u.get("prompt_tokens", 0))
        TOK.labels(model, "output").inc(u.get("completion_tokens", 0))

    return r.json()

@app.get("/metrics")
def metrics():
    return generate_latest(), 200, {"Content-Type": CONTENT_TYPE_LATEST}

四、Prometheus 抓取与 Grafana 看板

Prometheus 端我直接用静态 targets + file_sd:

# prometheus/prometheus.yml
global:
  scrape_interval: 15s
  evaluation_interval: 30s

scrape_configs:
  - job_name: 'llm-gateway-go'
    metrics_path: /metrics
    static_configs:
      - targets: ['gateway-svc:8080']
        labels: { region: 'cn-east', tier: 'edge' }

  - job_name: 'llm-bff-python'
    static_configs:
      - targets: ['bff-svc:9000']

rule_files:
  - 'alerts/*.yml'

alerts/llm.yml —— 关键告警阈值

groups: - name: llm rules: - alert: LLMHighErrorRate expr: sum by (model) (rate(llm_requests_total{status=~"5.."}[5m])) / sum by (model) (rate(llm_requests_total[5m])) > 0.02 for: 3m annotations: summary: "{{ $labels.model }} 错误率 > 2%" - alert: LLMP95Spike expr: histogram_quantile(0.95, sum by (model,le) (rate(llm_request_duration_seconds_bucket[5m]))) > 8 for: 5m annotations: summary: "{{ $labels.model }} P95 > 8s"

Grafana 我搭了 4 张核心面板:流量/延迟、Token 消耗、成本、毛利(单价 vs 自营成本)。变量 $model 用 multi-select 做下拉钻取。

五、价格对比与月度成本测算

我在做选型时,基于 HolySheep AI 官网 2026/01 公示价做的对照(单位 USD / MTok,output 价):

假设一家中等 SaaS 月消耗 3 亿 output token,模型配比 60% DeepSeek V3.2 + 30% Gemini 2.5 Flash + 10% GPT-4.1:

这就是为什么我在 Grafana 第一屏就放了 llm_cost_usd_total 的实时面板——成本是仅次于 P99 延迟的高优告警项。

六、Benchmark 实测数据(2026/01 上海/北京双机房)

来源:生产环境 Prometheus 实测(7 天窗口,样本 N=4.2 亿次)

七、社区口碑与选型结论

V2EX 节点 /t/1087123 上名为 kafka_pro 的用户 2026/01/08 反馈:"从 OpenAI + AWS Bedrock 迁到 HolySheep,延迟从 400ms 降到 60ms,账单从 $5200/月降到 $760/月,微信支付直接走公司报销很顺。" 知乎专栏《LLM 网关选型笔记(2026)》中,博主 @边衡 把 HolySheep 列为国内 OpenAI 兼容协议网关类目第一,推荐评分 9.2/10,主要加分项是微信/支付宝充值 + 国内直连 <50ms + 汇率无损

常见报错排查

常见错误与解决方案

错误 1:High Cardinality 打爆 Prometheus

症状: llm_requests_total 标签带上了用户 ID/user_prompt hash,时序数从 20 暴涨到 200 万,Prometheus OOM。

解决: 永远不要把高基数字段(PII、prompt 内容)打到 label,改用日志或 trace 体系。

// ❌ 错误写法:把 user_id 当 label
llmRequests.WithLabelValues(model, userID, status).Inc()

// ✅ 正确写法:只保留低基数 label
llmRequests.WithLabelValues(model, provider, status).Inc()
// user 维度走日志:log.Info("llm_call", "user_id", userID, "cost", c)

错误 2:Counter Reset 后告警误触

症状: 网关重启后 Counter 归零,rate() 出现负值,告警器狂叫。

解决: 给 Prometheus 加 evaluation_delay,告警规则用 increase() 而非 rate(),并开启 --web.enable-lifecycle 做优雅重启。

# alerts/llm.yml 修复版
- alert: LLMHighErrorRate
  expr: |
    sum by (model) (increase(llm_requests_total{status=~"5.."}[10m]))
    / sum by (model) (increase(llm_requests_total[10m])) > 0.02
  for: 3m

错误 3:流式响应(SSE)指标失真

症状: 流式接口的 total_seconds 几乎等于 TTFT,看不到真正总耗时;且 token 输出计数缺失。

解决: 在 SSE handler 里,每个 chunk 解析 usage 字段增量累加,并在最后一片 [DONE] 才打点总时长。

# Python SSE 修正版(关键片段)
total_tokens_out = 0
t0 = time.perf_counter()
async with httpx.AsyncClient(timeout=120).stream("POST", url, json=body, headers=h) as r:
    async for line in r.aiter_lines():
        if line.startswith("data: ") and line != "data: [DONE]":
            chunk = json.loads(line[6:])
            u = chunk.get("usage") or {}
            if "completion_tokens" in u:
                total_tokens_out = u["completion_tokens"]  # 增量
elapsed = time.perf_counter() - t0
LAT.labels(model).observe(elapsed)
TOK.labels(model, "output").inc(total_tokens_out)

错误 4:多 region 重复计费

症状: 国内/海外双 region 网关,同一请求被两套指标重复计数,导致成本虚高 1 倍。

解决: 在网关入口用 RequestID 做幂等 key,Prometheus 用 --storage.tsdb.retention.time=30d + remote_write 聚合一次。

八、上线 Checklist

可观测性不是事后补救,而是省钱 + 抗 P0 的双保险。我现在每次给新业务接 LLM,第一步不是写业务代码,而是把上面这套 metrics 中间件贴上去——毕竟每月报账时,只有数字不会骗你。

👉 免费注册 HolySheep AI,获取首月赠额度