大模型应用的可观测性——从 Token 监控到链路追踪的完整方案

📅 2026/7/9 8:30:11
大模型应用的可观测性——从 Token 监控到链路追踪的完整方案
大模型应用的可观测性——从 Token 监控到链路追踪的完整方案一、背景与问题大模型应用的可观测性与传统微服务有本质差异。传统微服务的可观测性关注请求延迟、错误率、资源占用——这些指标在 LLM 应用中同样重要但新增了两个维度Token 消耗直接关联成本与延迟和生成质量回答是否准确、完整、无幻觉。这两个维度没有现成的监控方案需要从零构建。我们在企业知识库助手项目中经历了上线后才发现成本超预期 3 倍、用户反馈回答错误但无法定位是检索问题还是生成问题的典型困境。根因是可观测性缺失没有 Token 粒度的成本追踪没有检索-生成的链路追踪没有答案质量的自动化评估。本文记录从 Token 监控到链路追踪的完整可观测性方案构建过程。二、方案设计大模型应用的可观测性体系分为四个层级指标层MetricsToken 消耗、调用延迟、成本、缓存命中率——量化资源使用。链路层Traces检索→增强→生成的全链路追踪——定位问题环节。日志层LogsPrompt/Response 的结构化记录——支撑质量分析与审计。质量层Quality答案准确率、相关性、完整性——自动化评估生成效果。flowchart TD A[用户请求] -- B[链路追踪起始点] B -- C[语义缓存检查 Span] C --|命中| D[缓存返回 Span] C --|未命中| E[上下文裁剪 Span] E -- F[模型路由 Span] F -- G[LLM 调用 Span] G -- H[Token 计量记录] H -- I[结果质量评估 Span] I -- J[链路追踪结束] J -- K[指标聚合写入 Prometheus] J -- L[日志写入 ElasticSearch] H -- M[Token Dashboard] K -- M L -- N[链路分析 Dashboard] I -- N三、实战演示3.1 Token 指标采集与 Prometheus 导出/** * Token 指标采集器——基于 Micrometer 的 Prometheus 指标导出 * 采集维度模型、应用、调用结果 */ Service Slf4j public class TokenMetricsCollector { private final MeterRegistry meterRegistry; private final Counter inputTokenCounter; private final Counter outputTokenCounter; private final Counter totalTokenCounter; private final Counter costCounter; private final Timer llmCallTimer; private final Counter cacheHitCounter; private final Counter cacheMissCounter; public TokenMetricsCollector(MeterRegistry meterRegistry) { this.meterRegistry meterRegistry; // Token 计数器——按模型和应用分组 this.inputTokenCounter Counter.builder(llm.tokens.input) .description(LLM 输入 Token 数) .tag(model, unknown) .register(meterRegistry); this.outputTokenCounter Counter.builder(llm.tokens.output) .description(LLM 输出 Token 数) .tag(model, unknown) .register(meterRegistry); this.totalTokenCounter Counter.builder(llm.tokens.total) .description(LLM 总 Token 数) .register(meterRegistry); // 成本计数器美元 this.costCounter Counter.builder(llm.cost.usd) .description(LLM 调用成本美元) .register(meterRegistry); // 调用延迟计时器 this.llmCallTimer Timer.builder(llm.call.duration) .description(LLM 调用延迟) .publishPercentiles(0.5, 0.9, 0.95, 0.99) .register(meterRegistry); // 缓存命中率 this.cacheHitCounter Counter.builder(llm.cache.hit) .description(语义缓存命中次数) .register(meterRegistry); this.cacheMissCounter Counter.builder(llm.cache.miss) .description(语义缓存未命中次数) .register(meterRegistry); } /** * 记录一次 LLM 调用的指标 * param model 模型名称 * param inputTokens 输入 Token 数 * param outputTokens 输出 Token 数 * param costUsd 调用成本美元 * param durationMs 调用延迟毫秒 * param cacheHit 是否命中语义缓存 */ public void record(String model, int inputTokens, int outputTokens, double costUsd, long durationMs, boolean cacheHit) { try { inputTokenCounter.increment(inputTokens); outputTokenCounter.increment(outputTokens); totalTokenCounter.increment(inputTokens outputTokens); costCounter.increment(costUsd); llmCallTimer.record(durationMs, TimeUnit.MILLISECONDS); if (cacheHit) { cacheHitCounter.increment(); } else { cacheMissCounter.increment(); } log.debug(指标记录: model{}, input{}, output{}, cost${:.4f}, latency{}ms, model, inputTokens, outputTokens, costUsd, durationMs); } catch (Exception e) { // 指标采集失败不应阻断主流程 log.warn(指标记录异常, e); } } }3.2 链路追踪——OpenTelemetry 集成/** * LLM 链路追踪服务——基于 OpenTelemetry 的全链路 Span 记录 * 将 RAG 链路中的每个环节记录为独立 Span */ Service Slf4j public class LlmTracingService { private final Tracer tracer; public LlmTracingService(Tracer tracer) { this.tracer tracer; } /** * 创建 RAG 查询链路 * param question 用户问题 * return 链路 Span 上下文 */ public SpanContext startRagTrace(String question) { Span span tracer.spanBuilder(rag.query) .setAttribute(question, question) .setAttribute(question.length, question.length()) .startSpan(); return new SpanContext(span, span.makeCurrent()); } /** * 记录检索环节 Span * param parentSpan 父 Span * param topK 检索参数 * param resultCount 检索结果数 * param durationMs 检索延迟 */ public void recordRetrievalSpan(Span parentSpan, int topK, int resultCount, long durationMs) { try { Span retrievalSpan tracer.spanBuilder(rag.retrieval) .setParent(parentSpan) .setAttribute(retrieval.topK, topK) .setAttribute(retrieval.resultCount, resultCount) .setAttribute(retrieval.durationMs, durationMs) .startSpan(); retrievalSpan.end(); } catch (Exception e) { log.warn(检索 Span 记录异常, e); } } /** * 记录 LLM 调用环节 Span * param parentSpan 父 Span * param model 调用模型 * param inputTokens 输入 Token * param outputTokens 输出 Token * param durationMs 调用延迟 */ public void recordLlmSpan(Span parentSpan, String model, int inputTokens, int outputTokens, long durationMs) { try { Span llmSpan tracer.spanBuilder(rag.llm_call) .setParent(parentSpan) .setAttribute(llm.model, model) .setAttribute(llm.inputTokens, inputTokens) .setAttribute(llm.outputTokens, outputTokens) .setAttribute(llm.durationMs, durationMs) .setAttribute(llm.costUsd, calculateCost(model, inputTokens, outputTokens)) .startSpan(); llmSpan.end(); } catch (Exception e) { log.warn(LLM Span 记录异常, e); } } /** * 结束 RAG 链路 * param spanContext 链路 Span 上下文 * param finalAnswer 最终回答 * param totalDurationMs 总延迟 */ public void endRagTrace(SpanContext spanContext, String finalAnswer, long totalDurationMs) { try { Span span spanContext.getSpan(); span.setAttribute(answer.length, finalAnswer.length()); span.setAttribute(total.durationMs, totalDurationMs); span.end(); } catch (Exception e) { log.warn(链路 Span 结束异常, e); } } }3.3 质量评估自动化/** * 答案质量评估服务——基于参考答案与 LLM 辅助的自动化评估 * 评估维度准确性、相关性、完整性、无幻觉 */ Service Slf4j public class QualityEvaluationService { private final ChatClient evalModel; public QualityEvaluationService(ChatClient evalModel) { this.evalModel evalModel; } /** * 评估回答质量 * param question 原始问题 * param answer LLM 生成的回答 * param referenceAnswer 参考答案可选 * param retrievedContext 检索到的上下文 * return 质量评估结果 */ public QualityResult evaluate(String question, String answer, String referenceAnswer, String retrievedContext) { try { String evalPrompt 你是质量评估专家。对以下 LLM 回答进行评分0-10 分评估维度 1. 准确性回答内容是否正确 2. 相关性回答是否针对问题 3. 完整性回答是否覆盖了问题的全部要点 4. 无幻觉回答是否仅基于检索上下文没有编造信息 问题%s 检索上下文%s LLM 回答%s 参考答案如有%s 输出 JSON{accuracy: N, relevance: N, completeness: N, hallucination_free: N, overall: N, issues: ...} .formatted(question, truncate(retrievedContext, 500), truncate(answer, 500), referenceAnswer ! null ? truncate(referenceAnswer, 500) : 无); String evalResult evalModel.call(evalPrompt); QualityResult result parseQualityResult(evalResult); log.info(质量评估完成: overall{}, question{}, result.getOverall(), truncate(question, 30)); return result; } catch (Exception e) { log.error(质量评估异常, e); // 降级返回默认评分 return QualityResult.builder() .accuracy(5) .relevance(5) .completeness(5) .hallucinationFree(5) .overall(5) .issues(评估异常: e.getMessage()) .build(); } } private String truncate(String text, int maxLen) { if (text null) return ; return text.length() maxLen ? text.substring(0, maxLen) … : text; } }3.4 Grafana Dashboard 配置要点# Prometheus 自定义指标示例 # llm_tokens_input_total{modelgpt-4o,appknowledge-assistant} # llm_tokens_output_total{modelgpt-4o,appknowledge-assistant} # llm_cost_usd_total{modelgpt-4o,appknowledge-assistant} # llm_call_duration_seconds{modelgpt-4o,quantile0.95} # llm_cache_hit_total / llm_cache_miss_total # Grafana Dashboard 面板设计 # 1. Token 消耗趋势图按模型分组24小时滚动窗口 # 2. 成本累计仪表盘今日/本周/本月 # 3. 延迟 P50/P95/P99 折线图 # 4. 缓存命中率饼图 # 5. 质量评分分布图按 overall 评分分桶四、深度解析4.1 可观测性四层的数据关联四层之间不是孤立存在而是通过 Trace ID 串联层级数据载体关联方式查询场景MetricsPrometheusTrace ID 作为标签哪条链路的 Token 异常高TracesOpenTelemetry自身就是关联轴检索环节是否是瓶颈LogsElasticSearchTrace ID 字段这条链路的具体 Prompt 和 Response 是什么Quality自建存储Trace ID 字段这条链路的回答质量如何关键设计每条 RAG 查询链路生成唯一的 Trace ID贯穿四个层级。在 Grafana 中从 Metrics Dashboard 的异常点点击跳转到 Jaeger Trace 页面查看链路详情再跳转到 ElasticSearch 查看完整 Prompt/Response最后查看质量评分——这是可观测性的端到端可导航体验。4.2 Token 消耗的异常检测Token 消耗异常的三个典型模式异常模式特征检测方法典型根因突增单小时 Token 消耗 3倍均值Prometheus rate 规则检索结果过多、Prompt 膨胀缓增日均消耗逐日增长 10%7 天趋势对比知识库膨胀、缓存失效周期异常特定时段消耗骤降/骤增按时段分桶对比定时任务调用、缓存 TTL 刷新4.3 质量评估的抽样策略全量评估每条回答的成本约 $0.01/条调用评估模型日均 10 万次调用则月评估成本 $3000。我们采用分层抽样全量自动评估基于简单规则回答长度异常、包含特定幻觉关键词的轻量检查。10% LLM 评估对全量自动评估标记为可疑的回答 10% 随机抽样回答调用 LLM 做深度评估。人工评估LLM 评估中 overall 6 的回答推送至人工审核队列。三层评估的总成本控制在月 $300覆盖 100% 的基础检查 10% 的深度评估 重点人工审核。五、总结与展望大模型应用的可观测性方案核心构建原则Trace ID 是四层的串联轴Metrics/Traces/Logs/Quality 通过 Trace ID 关联实现端到端可导航。Token 是大模型特有的核心指标成本、延迟、质量都受 Token 消耗影响必须以模型和应用维度细分追踪。质量评估需要分层抽样全量评估成本过高三层抽样规则检查 → LLM 评估 → 人工审核是务实的平衡。后续方向基于 OpenTelemetry 的 LLM Semantic Convention 标准化正在起草中的社区规范、质量评估的离线批量分析夜间低成本评估当日全量回答、以及成本异常的自动熔断Token 消耗超阈值时自动切换到小模型或缓存模式。大模型应用的可观测性不仅是运维工具更是成本控制与质量保障的工程基础——没有可观测性大模型应用就是黑箱。