AI 服务可观测性:从黑盒调用到全链路监控的体系建设
AI 服务可观测性从黑盒调用到全链路监控的体系建设一、AI 服务的盲飞困境当延迟飙升却无从定位某智能写作平台上线后用户反馈AI 生成内容越来越慢。运维团队查看传统 APM 指标CPU、内存、网络均正常。问题在于传统监控只覆盖了应用层而 AI 服务的瓶颈在模型推理层。一次完整的 AI 调用链路包括请求接收、Prompt 组装、缓存查询、模型路由、API 调用、响应解析、结果缓存任一环节都可能成为延迟热点。更关键的是成本盲区。大模型按 Token 计费但传统监控无法追踪 Token 消耗。月底账单比预算高出 200%却无法定位是哪个业务、哪个用户、哪类请求导致了超额消耗。AI 服务的可观测性建设需要从传统的应用层监控升级为AI 调用链路监控覆盖延迟、成本、质量三个维度。二、AI 服务可观测性的三层指标体系AI 服务的可观测性需要建立三层指标基础设施指标、应用层指标、AI 业务指标。传统监控只覆盖前两层AI 业务指标是缺失的关键环节。graph TB subgraph 第一层基础设施指标 CPU[CPU / 内存 / 网络] GPU[GPU 利用率 / 显存br/本地推理场景] end subgraph 第二层应用层指标 QPS[请求 QPS / 错误率] Latency[P50 / P95 / P99 延迟] Thread[线程池 / 连接池状态] end subgraph 第三层AI 业务指标 TokenIO[Token 用量br/input_tokens / output_tokensbr/按模型 / 租户 / 接口维度] ModelRoute[模型路由分布br/各模型调用占比] CacheHit[语义缓存命中率] Quality[回答质量评估br/用户反馈 / 幻觉检测] Cost[成本追踪br/Token 单价 × 用量br/按租户汇总] end CPU -- QPS GPU -- QPS QPS -- TokenIO Latency -- ModelRoute Thread -- CacheHit TokenIO -- Cost ModelRoute -- QualityToken 用量追踪成本控制的核心Token 用量是 AI 服务独有的核心指标。每次模型调用后响应中包含usage.prompt_tokens和usage.completion_tokens。将这些数据按模型、租户、接口维度聚合就能实现精细化的成本追踪。语义缓存命中率效率的晴雨表语义缓存命中率直接反映缓存策略的有效性。命中率低于 30% 时需要检查相似度阈值是否过高、缓存 TTL 是否过短、业务场景是否适合缓存。回答质量评估效果的可量化大模型的回答质量难以用传统指标衡量。实践中可采用以下方式用户反馈点赞/点踩、自动幻觉检测对比回答与参考资料的一致性、LLM-as-Judge用另一个模型评估回答质量。三、基于 Micrometer OpenTelemetry 的全链路监控实现Token 用量指标采集Component public class LlmMetricsInterceptor implements ClientHttpRequestInterceptor { private final MeterRegistry meterRegistry; private final Tracer tracer; public LlmMetricsInterceptor(MeterRegistry meterRegistry, Tracer tracer) { this.meterRegistry meterRegistry; this.tracer tracer; } /** * 拦截所有大模型 API 调用采集 Token 用量和延迟指标 */ Override public ClientHttpResponse intercept( HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException { String model extractModel(request); String tenant extractTenant(request); String operation extractOperation(request); Span span tracer.nextSpan() .name(llm.call) .tag(model, model) .tag(tenant, tenant) .tag(operation, operation) .start(); long startTime System.nanoTime(); try (Tracer.SpanInScope ws tracer.withSpan(span)) { ClientHttpResponse response execution.execute(request, body); // 从响应体中提取 Token 用量 TokenUsage usage extractTokenUsage(response); if (usage ! null) { // 记录 Token 用量指标 meterRegistry.counter(llm.tokens.input, model, model, tenant, tenant).increment(usage.getInputTokens()); meterRegistry.counter(llm.tokens.output, model, model, tenant, tenant).increment(usage.getOutputTokens()); // 计算并记录成本 double cost calculateCost(model, usage); meterRegistry.counter(llm.cost, model, model, tenant, tenant).increment(cost); span.tag(input_tokens, String.valueOf(usage.getInputTokens())); span.tag(output_tokens, String.valueOf(usage.getOutputTokens())); } // 记录延迟 long durationMs (System.nanoTime() - startTime) / 1_000_000; meterRegistry.timer(llm.call.duration, model, model, tenant, tenant) .record(durationMs, TimeUnit.MILLISECONDS); return response; } catch (IOException e) { meterRegistry.counter(llm.call.error, model, model, tenant, tenant).increment(); span.error(e); throw e; } finally { span.end(); } } /** * 根据模型和 Token 用量计算成本 */ private double calculateCost(String model, TokenUsage usage) { // 价格表每千 Token 的美元价格 MapString, Double inputPricePerK Map.of( gpt-4, 0.03, gpt-4o, 0.005, gpt-4o-mini, 0.00015 ); MapString, Double outputPricePerK Map.of( gpt-4, 0.06, gpt-4o, 0.015, gpt-4o-mini, 0.0006 ); double inputCost usage.getInputTokens() / 1000.0 * inputPricePerK.getOrDefault(model, 0.01); double outputCost usage.getOutputTokens() / 1000.0 * outputPricePerK.getOrDefault(model, 0.01); return inputCost outputCost; } }语义缓存命中率监控Component public class SemanticCacheMonitor { private final MeterRegistry meterRegistry; private final Counter cacheHitCounter; private final Counter cacheMissCounter; public SemanticCacheMonitor(MeterRegistry meterRegistry) { this.meterRegistry meterRegistry; this.cacheHitCounter meterRegistry.counter(llm.cache.hit); this.cacheMissCounter meterRegistry.counter(llm.cache.miss); } public void recordHit(String tenant, double similarity) { cacheHitCounter.increment(); meterRegistry.summary(llm.cache.similarity, tenant, tenant).record(similarity); } public void recordMiss(String tenant) { cacheMissCounter.increment(); } /** * 获取当前缓存命中率 * 用于健康检查接口和告警规则 */ public double getHitRate() { double hits cacheHitCounter.count(); double total hits cacheMissCounter.count(); return total 0 ? 0 : hits / total; } }告警规则配置# Prometheus 告警规则 groups: - name: llm_service_alerts rules: # Token 消耗速率异常单租户每分钟消耗超过 100K Token - alert: HighTokenConsumption expr: rate(llm_tokens_input_total{tenant!}[1m]) * 60 100000 for: 5m labels: severity: warning annotations: summary: 租户 {{ $labels.tenant }} Token 消耗异常 # 语义缓存命中率过低 - alert: LowCacheHitRate expr: rate(llm_cache_hit_total[10m]) / (rate(llm_cache_hit_total[10m]) rate(llm_cache_miss_total[10m])) 0.2 for: 30m labels: severity: info annotations: summary: 语义缓存命中率低于 20% # 模型调用 P99 延迟超过 10 秒 - alert: HighModelLatency expr: histogram_quantile(0.99, rate(llm_call_duration_bucket[5m])) 10 for: 5m labels: severity: critical annotations: summary: 模型 {{ $labels.model }} P99 延迟超过 10 秒四、AI 可观测性建设的边界与取舍采样率与成本的矛盾全量采集每次 AI 调用的 Trace 数据存储成本极高。一次 AI 调用的 Span 包含完整的 Prompt 和 Response数据量可能达到数 KB。在高 QPS 场景下Trace 存储成本可能接近模型调用成本。解决方案是错误请求全量采集正常请求按 1%-5% 采样关键业务如交易、医疗全量采集。质量评估的主观性回答质量的自动化评估仍不成熟。LLM-as-Judge 方法存在自我偏好问题——评估模型倾向于给同系列模型更高的分数。用户反馈数据稀疏且有偏差——满意的用户不反馈不满的用户才反馈。质量指标应作为参考而非唯一决策依据。实时性与一致性的取舍Token 用量和成本数据需要实时展示但精确的成本计算依赖模型供应商的账单数据通常有 24-48 小时延迟。实时指标基于本地计算的估算值与实际账单可能存在 5%-10% 的偏差。需要定期用供应商账单校准本地估算。隐私与可观测性的冲突AI 调用的 Trace 数据包含用户输入的 Prompt 和模型返回的 Response其中可能包含敏感信息如用户对话内容、企业内部数据。全量采集 Trace 会带来数据安全风险。解决方案是对 Trace 中的 Prompt 和 Response 做脱敏处理或仅采集元数据Token 数量、延迟、模型名称不采集内容。五、总结AI 服务的可观测性建设需要从传统的应用层监控扩展到AI 业务指标监控。Token 用量追踪是成本控制的基础语义缓存命中率是效率优化的依据回答质量评估是效果保障的手段。三者结合才能让 AI 服务从黑盒调用变为透明运营。但可观测性本身也有成本。全量 Trace 采集的存储开销、质量评估的计算开销、实时指标的延迟偏差都是需要权衡的因素。建设路径应该是渐进式的先建立 Token 用量和延迟的基础监控再引入缓存命中率和成本追踪最后在关键业务上试点质量评估。落地路线建议第一阶段在 AI 网关层集成 Micrometer 指标采集覆盖 Token 用量、延迟、错误率第二阶段接入 OpenTelemetry Trace实现从用户请求到模型调用的全链路追踪第三阶段搭建 Grafana 监控面板配置告警规则建立成本日报和周报机制第四阶段在关键业务上试点回答质量评估形成效果-成本-延迟的三角监控闭环。