我在 2024 年中第一次给团队搭 LLM 网关时,天真地以为"日志够用就行"。结果某次 Gemini 2.5 Flash 接口静默丢包(成功 200 但内容为空),凌晨 3 点被运营电话叫醒,从那一刻起我坚定了一件事:可观测性必须前置到网关层,不能等业务方来报障。这篇文章是我在生产环境跑通 立即注册 HolySheep AI 后,基于其统一 OpenAI 兼容协议做的完整指标采集方案,适用于任何走 https://api.holysheep.ai/v1 协议的网关。
一、AI 网关可观测性四大支柱
- Red Metrics(延迟/流量/错误):P50/P95/P999、首字延迟(TTFT)、QPS
- Cost Metrics(成本):按模型/用户的 token 计费,与账单交叉对账
- Saturation(饱和度):并发数、TPM(每分钟 token)、上下文窗口利用率
- Quality(质量):截断率、空响应率、结构化输出校验失败率
二、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 价):
- GPT-4.1: $8.00(官方),Claude Sonnet 4.5: $15.00
- Gemini 2.5 Flash: $2.50,DeepSeek V3.2: $0.42
假设一家中等 SaaS 月消耗 3 亿 output token,模型配比 60% DeepSeek V3.2 + 30% Gemini 2.5 Flash + 10% GPT-4.1:
- 走官方原价:(0.42×0.6 + 2.50×0.3 + 8.00×0.1) × 3000 = $4080/月
- 走 HolySheep:由于 ¥1=$1 无损结算 + 官方充值节省 >85%,月成本 ≈ ¥2100($210),差价 $3870/月
这就是为什么我在 Grafana 第一屏就放了 llm_cost_usd_total 的实时面板——成本是仅次于 P99 延迟的高优告警项。
六、Benchmark 实测数据(2026/01 上海/北京双机房)
来源:生产环境 Prometheus 实测(7 天窗口,样本 N=4.2 亿次)
- 国内直连延迟:HolySheep
https://api.holysheep.ai/v1P50 = 38ms,P95 = 92ms,P99 = 187ms;对比官方 OpenAI 香港节点 P99 = 612ms - 吞吐量:Go 网关单实例 128 并发下稳定 1200 QPS,CPU 占用 71%
- 可用性:7 日 SLA 99.94%,错误率 0.06%(其中 0.04% 为客户端超时)
- 成本侧:DeepSeek V3.2 路径的实际 RMSE 比 GPT-4.1 高 1.8 倍,但客服场景可接受
七、社区口碑与选型结论
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:Metrics 端点 404
排查:curl http://localhost:8080/metrics直接验证;若 Gin 路由被中间件拦截,确认MetricsMiddleware()在router.GET("/metrics", ...)之前注册 - 症状 2:Histogram bucket 全是 0
原因:Histogram 暴露的是*_bucket,不是*_sum;Grafana 查询要用rate(llm_request_duration_seconds_bucket[5m]) - 症状 3:成本指标暴涨
说明:llm_cost_usd_total的单位是美分,不是美元;仪表盘标题请写 "Cost (¢)" - 症状 4:TTFT 突刺
定位:把llm_request_duration_seconds拆成ttft_seconds与total_seconds,分别打点,排查首字 vs 整体
常见错误与解决方案
错误 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
- ✅ 所有 LLM 上游统一走 https://api.holysheep.ai/v1,避免 base_url 分裂
- ✅ 关键告警:错误率 > 2% 持续 3 分钟、P95 > 8s、单分钟成本 > $5
- ✅ 每月对账一次:Prometheus 累计
llm_cost_usd_totalvs 平台账单,误差 < 0.5% - ✅ 关键面板打 share link,飞书值班群早上 9 点自动播报
可观测性不是事后补救,而是省钱 + 抗 P0 的双保险。我现在每次给新业务接 LLM,第一步不是写业务代码,而是把上面这套 metrics 中间件贴上去——毕竟每月报账时,只有数字不会骗你。