Prometheus可观测性体系与LLM翻译质量监控

Prometheus 可观测性实战:为 LLM 翻译系统构建全链路监控

LLM 应用的可观测性远比传统 Web 服务复杂——除了常规的 QPS 和延迟,还需要追踪 Token 消耗、模型降级、翻译质量评分、Chunk 处理成功率等 LLM 特有指标。本文以"欧小译"为例,详解如何用 Prometheus + Grafana 构建一个 LLM 翻译系统的全链路可观测性体系。


一、LLM 应用的监控"三维模型"

传统 Web 服务的监控聚焦于性能(延迟、吞吐、错误率),但 LLM 应用还需要额外关注两个维度:

          ┌─────────────┐
          │   性能维度    │  延迟、QPS、错误率
          └──────┬──────┘
                 │
    ┌────────────┼────────────┐
    │                         │
┌───▼───────┐          ┌─────▼───────┐
│  成本维度  │          │  质量维度    │
│ Token消耗  │          │ 翻译评分     │
│ 模型调用量  │          │ 术语一致性   │
│ 降级/重试  │          │ 人工反馈     │
└───────────┘          └─────────────┘

我们的指标设计围绕这三个维度展开。


二、指标体系设计

2.1 业务指标

# 翻译请求总量(按语言对、领域、状态四维度切分)
translate_requests_total = Counter(
    'translate_requests_total',
    '翻译请求总数',
    ['source_lang', 'target_lang', 'domain', 'status']
)

# 端到端翻译耗时(Histogram,预定义桶边界)
translate_duration_seconds = Histogram(
    'translate_duration_seconds',
    '端到端翻译耗时',
    ['source_lang', 'target_lang', 'domain'],
    buckets=[1, 5, 10, 15, 30, 60, 120, 300]
)

桶边界的设计考量:翻译任务通常在 5~120 秒之间,因此桶边界覆盖了从 1s(极短文本)到 300s(超长文本或模型降级重试)的范围。桶太多浪费存储,太少丢失精度。

可以回答的核心问题:

  • 过去 1 小时,zh→entech 领域翻译请求量是多少?
  • P95 延迟是多少?→ histogram_quantile(0.95, rate(translate_duration_seconds_bucket[5m]))

2.2 步骤级指标

# 各步骤耗时(按步骤名和模型名切分)
step_duration_seconds = Histogram(
    'step_duration_seconds',
    '各步骤耗时',
    ['step_name', 'model_name'],
    buckets=[0.5, 1, 2, 5, 10, 20, 30, 60]
)

# Chunk 处理总量
chunk_processing_total = Counter(
    'chunk_processing_total',
    'Chunk 处理总数',
    ['status', 'step_name']
)

这两个指标能精确定位性能瓶颈在哪个步骤

# 各步骤平均耗时对比
sum(rate(step_duration_seconds_sum[5m])) by (step_name) 
/ 
sum(rate(step_duration_seconds_count[5m])) by (step_name)

如果发现 polish 步骤平均耗时是 terminology 的 3 倍,就知道优化方向在哪里。

2.3 成本指标

# Token 消耗总量
token_usage_total = Counter(
    'token_usage_total',
    'Token 消耗总量',
    ['model_name', 'token_type']  # prompt | completion
)

为什么区分 prompt 和 completion? 因为多数 LLM API 的定价中,completion token 的价格是 prompt token 的 2~4 倍。分开统计才能精确计算成本。

Grafana 面板示例:

# 过去 24 小时各模型的 Token 消耗(按美元估算)
sum(increase(token_usage_total{token_type="completion"}[24h])) by (model_name) * 0.000002
+
sum(increase(token_usage_total{token_type="prompt"}[24h])) by (model_name) * 0.0000005

2.4 质量指标

# 翻译质量评分
translate_eval_score = Gauge(
    'translate_eval_score',
    '翻译质量评分',
    ['metric_name', 'domain']  # bleu | comet | human
)

支持三种评分来源:

  • BLEU:自动计算的机器翻译经典指标
  • COMET:基于神经网络的翻译质量评估
  • Human:用户在界面上的人工评分反馈

2.5 错误与降级指标

# 翻译错误总量
translation_errors_total = Counter(
    'translation_errors_total',
    '翻译错误总数',
    ['error_type', 'step_name', 'model_name']
)

# 模型重试次数
model_retries_total = Counter(
    'model_retries_total',
    '模型重试次数',
    ['model_name', 'step_name', 'fallback_model']
)

# 模型降级次数
model_fallbacks_total = Counter(
    'model_fallbacks_total',
    '模型降级次数',
    ['original_model', 'fallback_model', 'step_name']
)

这三个 Counter 组合起来可以计算出模型可靠性看板

# DeepSeek 模型过去 1 小时的故障率
sum(increase(translation_errors_total{model_name="deepseek"}[1h]))
/
sum(increase(step_duration_seconds_count{model_name="deepseek"}[1h]))

2.6 资源指标

# 活跃翻译会话数
active_sessions = Gauge('active_sessions', '当前进行中的翻译会话数')

# SSE 连接数
sse_connections = Gauge('sse_connections', '当前活跃的 SSE 连接数')

# SSE 事件推送量
sse_events_total = Counter(
    'sse_events_total',
    'SSE 事件推送总数',
    ['event_type', 'status']
)

active_sessions 是一个 Gauge(可增可减),反映系统实时负载。当它接近系统容量上限时,可以触发限流或扩容。


三、指标埋点位置

指标埋点遵循"在业务逻辑中就近采集"原则:

translate_service.py  ──→ translate_requests_total, translate_duration_seconds, active_sessions
llm_router.py         ──→ step_duration_seconds, model_retries_total, model_fallbacks_total
terminology.py        ──→ chunk_processing_total (terminology)
literal.py            ──→ chunk_processing_total (literal)
redis_client.py       ──→ sse_events_total
translate_service.py  ──→ translation_errors_total (catch block)

关键原则:指标采集不应改变业务逻辑的控制流。所有 .inc() / .observe() 调用都是"旁路"操作,不影响主流程的执行。


四、Prometheus 告警规则设计

基于上述指标,可以设计以下告警规则:

告警1:模型故障率过高

- alert: ModelHighErrorRate
  expr: |
    sum(rate(translation_errors_total[5m])) by (model_name)
    /
    sum(rate(step_duration_seconds_count[5m])) by (model_name)
    > 0.3
  for: 5m
  labels:
    severity: warning
  annotations:
    summary: "模型 {{ $labels.model_name }} 过去 5 分钟故障率超过 30%"

告警2:翻译延迟 P95 过高

- alert: TranslationHighLatency
  expr: |
    histogram_quantile(0.95, 
      sum(rate(translate_duration_seconds_bucket[5m])) by (le)
    ) > 120
  for: 10m
  labels:
    severity: warning
  annotations:
    summary: "翻译 P95 延迟超过 120 秒"

告警3:活跃会话过多(容量告警)

- alert: HighActiveSessions
  expr: active_sessions > 50
  for: 3m
  labels:
    severity: critical
  annotations:
    summary: "活跃翻译会话数超过 50,可能需要扩容"

告警4:降级频率异常

- alert: ExcessiveModelFallbacks
  expr: |
    sum(rate(model_fallbacks_total[10m])) > 5
  for: 10m
  labels:
    severity: warning
  annotations:
    summary: "过去 10 分钟模型降级超过 5 次,请检查模型服务状态"

五、Grafana Dashboard 布局建议

┌──────────────────────────────────────────────────────────────┐
│  📊 翻译系统总览                                              │
├───────────────┬───────────────┬──────────────┬───────────────┤
│ 活跃会话 (G)  │ 请求量/min    │ P95 延迟     │ 错误率        │
│   [数字面板]  │  [时序图]     │  [时序图]    │  [时序图]      │
├───────────────┴───────────────┴──────────────┴───────────────┤
│  📈 各步骤耗时对比(Stacked Bar Chart)                       │
│  terminology | literal | review | polish | aggregate         │
├──────────────────────────────────────────────────────────────┤
│  💰 Token 消耗趋势(按模型分组的 Area Chart)                  │
│  DeepSeek | Bailian | OpenAI | Claude                        │
├────────────────────────┬─────────────────────────────────────┤
│  🔄 模型降级事件       │  📊 翻译质量评分趋势                  │
│  (Event Timeline)     │  BLEU / COMET / Human (时序折线)     │
├────────────────────────┴─────────────────────────────────────┤
│  📦 Chunk 处理成功率(按步骤分组)                             │
│  success / failed 饼图                                       │
└──────────────────────────────────────────────────────────────┘

六、从指标到决策:三个真实场景

场景1:该不该换模型?

问题:DeepSeek 在 literature 领域的意译评分一直偏低,是否值得切到 Claude?

数据

  • translate_eval_score{metric_name="human", domain="literature"} → DeepSeek: 6.2, Claude: 8.1
  • step_duration_seconds{step_name="polish", model_name="claude"} → P95 = 15s(可接受)
  • token_usage_total{model_name="claude"} × 单价 → 成本增加约 40%

决策:在 literature 领域的 polish 步骤切换到 Claude,其他步骤保持 DeepSeek。这正是路由表的设计初衷——按步骤粒度选模型。

场景2:Chunk 切分策略是否合理?

问题:用户反馈长文本翻译质量不稳定。

数据

  • chunk_processing_total{status="failed", step_name="literal"} → 失败率 12%
  • 关联日志发现失败的 Chunk 平均 token 数为 780(接近 800 上限)

决策:将 MAX_CHUNK_TOKENS 从 800 降到 600,留出更多余量给系统 Prompt。

场景3:Redis 连接池是否需要扩容?

问题:高峰期 SSE 推送偶尔延迟。

数据

  • active_sessions 峰值 45
  • sse_events_total{status="failed"} 偶发增长

决策:Redis max_connections=100 对于 45 个并发会话足够(每个会话在轮询时占用 1 个连接),问题可能在 Redis 服务端,进一步排查发现是 EXPIRE 命令堆积。优化方案:将 TTL 设置从每次 LPUSH 后执行改为批量设置。


七、可观测性的"冰山模型"

          ╱╲
         ╱  ╲     ← 用户可见:SSE 进度推送
        ╱────╲
       ╱      ╲   ← 运维可见:Prometheus + Grafana
      ╱────────╲
     ╱          ╲  ← 开发可见:结构化日志 (logger)
    ╱────────────╲
   ╱              ╲ ← 调试可见:LLM Prompt/Response 日志
  ╱────────────────╲

Prometheus 指标是冰山的中层——它比日志更适合做聚合分析,比 SSE 推送更适合做历史回溯。但三层不能互相替代:

  • SSE 给用户提供实时反馈(秒级)
  • Prometheus 给运维提供告警和趋势分析(分钟级)
  • 日志 给开发提供调试信息(请求级)

在"欧小译"中,这三层是并行存在的,同一个事件(如模型降级)会同时触发:

  1. SSE 推送 → 用户看到"已切换到备用模型"
  2. Prometheus Counter +1 → 运维看到降级频率
  3. logger.warning → 开发看到详细堆栈

八、总结

LLM 应用的可观测性需要超越传统的"三大金信号"(延迟、流量、错误),深入到模型行为(降级、重试、Token 消耗)和输出质量(评分、一致性)层面。

本文的指标体系覆盖了五个维度:业务量、性能、成本、质量、可靠性——这五个维度的交叉分析,才能真正驱动 LLM 应用的持续优化。

记住:你无法优化你无法度量的东西。在 LLM 翻译系统中,这句话的分量比任何工程领域都重。

posted @ 2026-06-11 07:56  黄忠  阅读(9)  评论(0)    收藏  举报