公司动态
基于OpenTelemetry构建GenAI应用性能与成本监控实战
大家好我是专注于可观测性领域的技术博主。在当前的AI浪潮下将生成式AIGenAI能力集成到应用中的场景越来越普遍。随之而来的一个核心挑战是我们如何有效地监控和度量这些AI调用的性能、成本和质量如果仅仅依赖模型提供商的后台数据不仅数据不完整也难以与自身业务系统的链路关联。本文将分享一套基于 OpenTelemetryOtel追踪Traces和原始提示词Raw Prompts来构建有界BoundedGenAI 监控指标的实战方案。这套方案能让你清晰地看到每一次AI调用的耗时、Token消耗、费用以及提示词本身并与你的业务链路无缝整合。我们将使用 Prometheus 收集指标并用 Grafana 进行可视化展示。无论你是刚开始接触 GenAI 应用开发还是正在为已有的AI功能寻找可观测性解决方案本文都将提供一个从原理到部署的完整指南。你将学会如何搭建监控环境、如何从Otel Trace中提取关键数据、如何构建有业务意义的仪表盘并最终掌握一套可复用于生产环境的监控体系。1. 背景与核心概念为什么需要“有界”的GenAI监控在深入技术细节之前我们首先要理解几个核心概念以及当前监控GenAI的痛点。什么是“有界”Bounded的GenAI指标传统的应用监控指标如请求延迟、错误率通常是“无界”的它们描述的是系统行为本身。而GenAI的指标尤其是与提示词Prompt和模型响应相关的需要被“绑定”到具体的业务上下文和输入输出上。例如延迟不仅要知道调用花了3秒还要知道是哪个用户的哪个问题导致了这3秒。成本不仅要知道今天花了100美元还要知道是哪个功能、哪类提示词消耗了主要成本。质量不仅要知道整体成功率还要知道对于“客服问答”和“内容生成”这两种不同场景模型的表现有何差异。“有界”就是指将这些指标与具体的业务属性如用户ID、功能模块、输入属性如提示词长度、主题和模型属性如模型名称、供应商关联起来形成有明确上下文的、可深入下钻的分析维度。OpenTelemetryOtel与追踪Traces的角色OpenTelemetry 是一个云原生计算基金会CNCF下的项目旨在提供一套标准的API、SDK和工具用于收集、生成和导出遥测数据指标、日志、追踪。其中追踪Traces记录了单个请求在分布式系统中流转的完整路径它由多个跨度Spans组成。对于一次GenAI调用我们可以将其建模为一个Span。这个Span不仅记录了起止时间用于计算延迟还可以携带丰富的属性Attributes例如genai.model.name: “gpt-4”genai.provider: “openai”genai.prompt.tokens: 150genai.completion.tokens: 300genai.total.cost: 0.06 (美元)user.id: “user_123”business.feature: “customer_service”通过Otel我们能够自动或手动地将这些属性注入到追踪链路中为后续的指标提取提供丰富的上下文。原始提示词Raw Prompts的价值仅仅有数字指标是不够的。当某个请求延迟异常或成本激增时开发者和运维人员迫切需要知道当时“到底问了AI什么问题”。将原始提示词和模型响应作为Span的事件Events或属性谨慎处理可能包含PII数据记录下来是实现根因分析RCA的关键。这能帮助我们快速识别是否是某些特定的问题模式导致了性能或成本问题。整体架构从Trace到Metric再到Dashboard我们的目标流程是应用侧在代码中集成Otel SDK对每一次GenAI调用创建Span并记录耗时、Token数、成本等属性以及原始提示词可脱敏。收集与导出Otel Collector 接收应用发送的Trace数据。指标提取在Collector中或后端使用处理管道Processors从Trace的Span属性中提取出我们关心的数值如genai.total.cost并将其转换为Prometheus格式的指标Metrics。这就是“从Trace生成Metric”的核心步骤。存储与可视化Prometheus 抓取这些指标并存储。Grafana 从Prometheus查询数据构建出能够按模型、用户、功能等多维度下钻的监控仪表盘。接下来我们将从环境搭建开始一步步实现这个架构。2. 环境准备与版本说明为了完整演示我们需要搭建一个包含以下组件的本地测试环境一个简单的Python演示应用模拟调用GenAIOpenTelemetry Collector负责接收、处理、导出遥测数据Prometheus指标存储与查询Grafana指标可视化版本说明本文示例将使用当前撰写时较为稳定且兼容性好的版本。实际部署时请务必查阅官方文档根据你的生产环境进行调整。操作系统Ubuntu 22.04 LTS 或 macOS命令可能略有不同Python3.9OpenTelemetry Python SDK API:opentelemetry-sdk1.24.0,opentelemetry-api1.24.0OpenTelemetry Collector Contrib:0.102.0(Docker镜像)Prometheus:2.48.0(Docker镜像或二进制)Grafana:10.3.0(Docker镜像或二进制)Docker Docker Compose可选用于快速启动后端组件项目结构预览genai-otel-demo/ ├── app/ │ ├── main.py # 主应用模拟GenAI调用 │ └── requirements.txt # Python依赖 ├── collector/ │ └── otel-collector-config.yaml # Otel Collector配置文件 ├── prometheus/ │ └── prometheus.yml # Prometheus配置文件 ├── docker-compose.yml # 一键启动Otel Collector, Prometheus, Grafana └── README.md我们首先准备后端监控基础设施。3. 核心组件配置与原理拆解3.1 OpenTelemetry Collector 配置从Trace中提取指标Otel Collector 是我们的数据处理中枢。它通过接收器Receivers接收数据经过处理器Processors加工再通过导出器Exporters发送到后端。我们需要配置一个关键处理器metricsgeneration。这个处理器允许我们根据Span的属性Attributes来生成新的指标。创建collector/otel-collector-config.yamlreceivers: otlp: protocols: grpc: endpoint: 0.0.0.0:4317 # 接收应用通过gRPC发送的Trace http: endpoint: 0.0.0.0:4318 # 接收应用通过HTTP发送的Trace processors: batch: # 批量处理提高效率 metrics_generator/genai: # 关键定义指标生成规则 # 这是一个实验性处理器需要启用 metrics 扩展 # 规则匹配所有包含 genai. 前缀属性的Span metrics: genai_latency: description: “GenAI调用延迟” unit: “ms” type: gauge # 瞬时值每次Span结束时记录 value_type: double # 从Span的 duration_ms 属性中取值如果没有则用Span的持续时间计算 value: “duration_ms” attributes: - key: genai.model.name from_attribute: genai.model.name - key: genai.provider from_attribute: genai.provider - key: business.feature from_attribute: business.feature genai_cost_usd: description: “单次GenAI调用成本美元” unit: “USD” type: gauge value_type: double value: “genai.total.cost” # 直接从Span属性中读取成本 attributes: - key: genai.model.name from_attribute: genai.model.name - key: user.tier from_attribute: user.tier # 示例按用户等级分析成本 genai_total_tokens: description: “单次GenAI调用消耗的总Token数” unit: “{token}” type: gauge value_type: int value: “genai.total.tokens” attributes: - key: genai.model.name from_attribute: genai.model.name exporters: debug: verbosity: detailed # 用于调试打印处理后的数据到日志 prometheus: endpoint: “0.0.0.0:8889” # Collector将指标暴露给Prometheus抓取的地址 namespace: “genai” # 指标名称的前缀如 genai_latency const_labels: environment: “demo” service: pipelines: traces: receivers: [otlp] processors: [batch, metrics_generator/genai] # Trace经过处理生成指标 exporters: [debug] # 可以将Trace导出到Jaeger等此处仅调试 metrics: receivers: [otlp] # 也可以直接接收应用发来的指标但本文重点是从Trace生成 processors: [batch] exporters: [prometheus] # 将生成的指标导出到Prometheus端点 extensions: [] telemetry: logs: level: “info”关键配置解释metrics_generator处理器这是实现“从Trace生成指标”的核心。它扫描每一个处理完成的Span根据我们定义的规则如genai_latency从Span的属性中提取数值并打上指定的属性标签生成一个新的Gauge类型指标。prometheus导出器将处理好的指标数据以Prometheus的格式暴露在一个HTTP端点0.0.0.0:8889上等待Prometheus来抓取Scrape。双管道设计traces管道负责处理原始追踪数据并生成指标metrics管道负责收集并导出这些生成的指标以及可能从应用直接发来的指标。3.2 Prometheus 配置抓取与存储Prometheus 需要配置一个抓取任务job来定期从Otel Collector暴露的端点拉取指标。创建prometheus/prometheus.ymlglobal: scrape_interval: 15s # 每15秒抓取一次数据 evaluation_interval: 15s # 每15秒评估一次告警规则 scrape_configs: - job_name: ‘otel-collector’ static_configs: - targets: [‘otel-collector:8889’] # 指向Otel Collector的Prometheus端点 labels: source: ‘otel-genai-metrics’这里otel-collector:8889是服务名在Docker Compose网络中可以通过服务名访问。如果是二进制部署需替换为对应的主机IP和端口。3.3 使用 Docker Compose 一键启动基础设施为了简化部署我们使用 Docker Compose 来启动 Otel Collector、Prometheus 和 Grafana。创建docker-compose.ymlversion: ‘3.8’ services: otel-collector: image: otel/opentelemetry-collector-contrib:0.102.0 command: [“--config/etc/otel-collector-config.yaml”] volumes: - ./collector/otel-collector-config.yaml:/etc/otel-collector-config.yaml ports: - “4317:4317” # OTLP gRPC 接收端口 - “4318:4318” # OTLP HTTP 接收端口 - “8889:8889” # Prometheus 指标暴露端口 networks: - observability-net prometheus: image: prom/prometheus:v2.48.0 volumes: - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml command: - ‘--config.file/etc/prometheus/prometheus.yml’ - ‘--web.enable-lifecycle’ # 允许热重载配置 ports: - “9090:9090” networks: - observability-net grafana: image: grafana/grafana:10.3.0 environment: - GF_SECURITY_ADMIN_PASSWORDadmin # 设置默认管理员密码 volumes: - grafana-storage:/var/lib/grafana ports: - “3000:3000” networks: - observability-net depends_on: - prometheus volumes: grafana-storage: networks: observability-net: driver: bridge在项目根目录下运行docker-compose up -d即可启动所有服务。访问http://localhost:9090进入Prometheushttp://localhost:3000进入Grafana用户名admin密码admin。4. 完整实战编写并监控一个GenAI模拟应用现在我们来编写一个简单的Python应用它模拟调用GenAI服务并使用Otel SDK发送包含丰富属性和原始提示词的Trace数据。4.1 创建应用与安装依赖进入app目录创建requirements.txtopentelemetry-sdk1.24.0 opentelemetry-api1.24.0 opentelemetry-exporter-otlp-proto-http1.24.0 # 使用HTTP协议导出 opentelemetry-instrumentation0.44b0安装依赖pip install -r requirements.txt4.2 编写模拟GenAI调用的应用代码创建app/main.pyimport random import time from typing import Dict, Any from opentelemetry import trace from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import BatchSpanProcessor from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter from opentelemetry.sdk.resources import Resource # 1. 设置TracerProvider和资源标识服务 resource Resource(attributes{ “service.name”: “genai-demo-app”, “service.version”: “1.0.0”, “environment”: “demo” }) trace.set_tracer_provider(TracerProvider(resourceresource)) # 2. 创建OTLP导出器指向本地Otel Collector的HTTP端口 otlp_exporter OTLPSpanExporter( endpoint“http://localhost:4318/v1/traces” # 对应Collector的otlp/http端口 ) span_processor BatchSpanProcessor(otlp_exporter) trace.get_tracer_provider().add_span_processor(span_processor) # 获取一个Tracer tracer trace.get_tracer(__name__) def simulate_genai_call(prompt: str, user_id: str, feature: str) - Dict[str, Any]: 模拟一次GenAI调用并记录详细的Trace信息。 # 模拟不同的模型和供应商 models [“gpt-4”, “gpt-3.5-turbo”, “claude-3-opus”] providers [“openai”, “anthropic”] selected_model random.choice(models) selected_provider “openai” if “gpt” in selected_model else “anthropic” # 模拟根据模型和提示词长度计算Token和成本 prompt_tokens len(prompt) // 4 # 简单模拟 completion_tokens random.randint(50, 500) total_tokens prompt_tokens completion_tokens # 模拟成本美元 cost_per_token 0.00003 if “gpt-4” in selected_model else 0.000001 total_cost total_tokens * cost_per_token # 模拟延迟毫秒 latency_ms random.randint(500, 5000) # 使用Tracer创建一个Span代表这次GenAI调用 with tracer.start_as_current_span(“genai.inference”) as span: # 设置Span的属性Attributes- 这些将被用于生成指标 span.set_attribute(“genai.model.name”, selected_model) span.set_attribute(“genai.provider”, selected_provider) span.set_attribute(“genai.prompt.tokens”, prompt_tokens) span.set_attribute(“genai.completion.tokens”, completion_tokens) span.set_attribute(“genai.total.tokens”, total_tokens) span.set_attribute(“genai.total.cost”, total_cost) span.set_attribute(“user.id”, user_id) span.set_attribute(“business.feature”, feature) span.set_attribute(“user.tier”, random.choice([“free”, “premium”])) # 模拟用户等级 # 记录原始提示词作为一个事件Event注意隐私生产环境需脱敏。 # 这里我们只记录提示词的前100个字符作为示例。 span.add_event(“genai.prompt”, attributes{“text”: prompt[:100] “...” if len(prompt) 100 else prompt}) # 模拟处理时间 time.sleep(latency_ms / 1000.0) # 记录延迟作为属性也可以由Collector从Span持续时间计算 span.set_attribute(“duration_ms”, latency_ms) # 模拟返回结果 return { “model”: selected_model, “tokens_used”: total_tokens, “cost_usd”: total_cost, “latency_ms”: latency_ms, “response”: f“Simulated response for ‘{prompt[:30]}...’” } def main(): print(“Starting GenAI demo application...“) # 模拟一系列用户请求 prompts [ “Explain quantum computing in simple terms.”, “Write a Python function to reverse a linked list.”, “What are the benefits of renewable energy?”, “Generate a marketing slogan for a new coffee brand.”, ] users [“user_001”, “user_002”, “user_003”] features [“customer_service”, “code_generation”, “content_creation”] for i in range(20): # 模拟20次调用 prompt random.choice(prompts) user random.choice(users) feature random.choice(features) result simulate_genai_call(prompt, user, feature) print(f“Call {i1}: User{user}, Feature{feature}, Cost${result[‘cost_usd’]:.4f}, Latency{result[‘latency_ms’]}ms”) time.sleep(random.uniform(0.5, 2.0)) # 模拟随机请求间隔 print(“Demo finished. Waiting for traces to be exported...“) time.sleep(5) # 等待BatchSpanProcessor导出数据 if __name__ “__main__”: main()4.3 运行与验证确保基础设施已运行在项目根目录执行docker-compose up -d。运行模拟应用在app目录下执行python main.py。你将看到控制台输出模拟的调用信息。验证Trace数据Otel Collector配置了debugexporter可以查看其日志确认数据接收。docker-compose logs -f otel-collector你应该能看到类似“Spans traced:”的日志里面包含了我们设置的属性genai.model.name,genai.total.cost等。验证指标生成访问Prometheus UI (http://localhost:9090)在表达式输入框中输入genai_latency或genai_cost_usd点击“Execute”。如果配置正确你应该能看到这些指标已经存在并且有我们设置的标签genai_model_name,business_feature等。4.4 在Grafana中创建监控仪表盘登录Grafana访问http://localhost:3000使用admin/admin登录。添加数据源点击左侧齿轮图标 - “Data Sources”。点击 “Add data source”。选择 “Prometheus”。在URL栏填写http://prometheus:9090Docker Compose网络内地址。点击 “Save Test”应显示 “Data source is working”。创建仪表盘Dashboard点击左侧 “” 号 - “Dashboard”。点击 “Add visualization”。选择我们刚添加的Prometheus数据源。现在我们来创建几个关键面板面板1GenAI调用延迟按模型和功能分组查询Agenai_latency。这会显示所有数据点的瞬时值。转换为了得到有意义的折线图我们通常使用rate()或avg_over_time()等函数。但由于我们的genai_latency是Gauge每次调用记录一个值更适合用avg_over_time()来看趋势。修改查询为avg_over_time(genai_latency[5m])。这显示过去5分钟内延迟的平均值。图例格式点击 “Legend” 输入框填写{{genai_model_name}} - {{business_feature}}让图例显示模型和功能。可视化选择选择 “Time series” 图表。面板2GenAI调用成本分布按用户等级查询genai_cost_usd。同样是一个Gauge。转换为了看到总成本趋势我们可以使用sum_over_time()。但更常见的是在面板设置“Stat”或“Bar gauge”来显示最新值。我们先创建一个“Bar gauge”选择可视化类型 “Bar gauge”。查询保持为genai_cost_usd。在 “Standard options” - “Unit” 中选择 “Currency - USD”。分组在 “Transform” 标签页添加 “Group by” 转换按user_tier字段分组可以清晰看到免费用户和付费用户的成本对比。面板3总Token消耗趋势查询sum(rate(genai_total_tokens[5m]))。注意genai_total_tokens也是Gaugerate()对Gauge可能不直观。更好的方式是直接显示原始值或使用max_over_time()看峰值。我们可以创建一个“Stat”面板显示最近一次调用的Token数或者用“Time series”显示genai_total_tokens本身每个点代表一次调用的Token数。这里我们创建一个“Time series”查询为genai_total_tokens图例格式为{{genai_model_name}}。面板4原始提示词查看表需要日志关联Grafana 直接展示Trace中的事件如我们的genai.prompt比较复杂通常需要将Trace数据导出到如Jaeger、Tempo等专门的Trace后端并在Grafana中配置关联数据源。 一个简化的替代方案是将重要的提示词摘要如哈希值或分类作为一个Span属性如prompt.hash或prompt.intent记录下来然后就可以像其他属性一样在Grafana的Prometheus图表中作为标签进行筛选和分组。这实现了“有界”监控——将指标与具体的输入特征关联。保存仪表盘命名为 “GenAI Metrics from OTel Traces”。现在每当你运行模拟应用仪表盘上的数据就会更新。5. 常见问题与排查思路在实施过程中你可能会遇到以下问题问题现象常见原因解决思路Prometheus 中查询不到genai_开头的指标。1. Otel Collector 配置中metrics_generator处理器规则未生效或写错。2. Collector 的prometheusexporter 端口未暴露或配置错误。3. Prometheus 抓取配置中的targets地址不正确。4. 应用没有成功发送Trace数据到Collector。1. 检查Collector日志确认metrics_generator处理器已加载且无配置错误。2. 访问http://collector-host:8889/metrics查看是否能看到genai_指标。3. 检查Prometheus的Targets页面 (http://localhost:9090/targets)确认otel-collectorjob的状态是UP。4. 检查应用日志和Collector的debugexporter日志确认Trace数据已送达。Grafana 中图表显示 “No data”。1. Grafana 数据源配置错误无法连接Prometheus。2. Prometheus 中确实没有数据。3. 查询的时间范围不对如选择了未来时间。4. 查询语句语法错误。1. 在Grafana数据源配置页面点击 “Save Test” 验证连接。2. 回到Prometheus UI用相同查询验证是否有数据。3. 调整Grafana面板右上角的时间范围。4. 在Grafana查询面板使用 “Explorer” 模式逐步构建查询。应用报错无法连接Otel Collector。1. Collector 服务未启动。2. 应用代码中的OTLP端点地址或端口错误。3. 防火墙或网络策略阻止了连接。1. 运行docker-compose ps确认otel-collector服务状态。2. 确认应用中的endpoint(http://localhost:4318) 与docker-compose.yml中映射的端口一致。3. 尝试从应用所在主机使用curl或telnet测试Collector端口连通性。metrics_generator处理器不工作日志提示未知处理器。Otel Collector Contrib 镜像版本可能过旧或者metrics_generator是实验性功能需要显式启用扩展。1. 确保使用足够新的otel/opentelemetry-collector-contrib镜像如本文使用的0.102.0。2. 在Collector配置文件的service-extensions部分添加experimental_metricsgeneration如果版本要求并在service-pipelines-metrics-processors中引用。具体请查阅对应版本Collector的文档。生成的指标数值异常如成本为0。应用代码中设置的Span属性名称与Collectormetrics_generator规则中value字段引用的属性名不匹配。仔细核对应用span.set_attribute(“genai.total.cost”, ...)中的属性名必须与Collector配置value: “genai.total.cost”完全一致包括大小写。使用Collector的debugexporter 查看收到的Span属性是否正确。6. 最佳实践与工程建议将这套方案应用于生产环境时需要考虑更多工程细节1. 提示词Prompt的隐私与安全处理绝不记录完整明文原始提示词和模型响应可能包含用户隐私、商业机密或敏感信息。脱敏策略在记录到Span事件或属性前必须进行脱敏。例如移除个人信息邮箱、电话、身份证号。使用哈希函数如SHA256对提示词生成唯一指纹只记录哈希值用于关联分析。对提示词进行意图分类如“编程问题”、“翻译请求”、“总结任务”记录分类标签而非原文。访问控制确保存储Trace数据的后端如Jaeger、Tempo有严格的访问权限控制。2. 指标设计与命名规范遵循OpenTelemetry语义约定尽量使用已有的语义约定属性名如genai.*命名空间正在社区讨论中。这有利于不同系统间的互操作性。指标类型选择Gauge适用于瞬时值如单次调用的延迟、成本、Token数。本文示例即采用此类型。Counter适用于累计值如总调用次数、总Token消耗、总成本。可以在应用侧直接递增Counter也可以通过metrics_generator的counter类型从Span计数生成。Histogram适用于分析延迟等指标的分布情况P50, P90, P99。Otel SDK支持直接记录Histogram指标。标签维度设计标签是下钻分析的关键。除了genai.model.name、business.feature还可以考虑status.code成功/失败、error.type错误类型、user.region用户地域等。但需注意高基数的标签如user.id会导致Prometheus序列爆炸应谨慎使用。3. 性能与可扩展性采样Sampling在高流量场景下记录每一次调用的完整Trace和提示词可能开销过大。需要配置采样策略例如头部采样只记录1%的请求。尾部采样只记录慢请求或错误请求。这能有效控制数据量同时保留对问题排查最重要的数据。批处理与异步导出确保使用Otel SDK的BatchSpanProcessor并合理配置max_queue_size和schedule_delay_millis以平衡内存使用和导出效率。Collector资源规划metrics_generator处理器是CPU密集型操作。在生产环境中需要根据Trace流量监控Collector的CPU和内存使用情况并进行水平扩展。4. 告警与自动化基于生成的指标在Prometheus或Grafana中设置告警规则。例如高延迟告警avg_over_time(genai_latency[5m]) 10000平均延迟超过10秒成本异常告警sum(genai_cost_usd) without (instance, job) 100总成本突然超过100美元错误率升高告警需要定义一个错误指标如genai_call_errorCounter然后计算错误率。将告警与现有的运维平台如PagerDuty、Slack、钉钉集成。5. 与现有监控体系融合本文方案生成的指标是Prometheus格式的可以无缝融入你现有的Prometheus Grafana监控栈。如果你已使用其他APM工具如Datadog、New Relic可以考虑使用Otel Collector的相应导出器如datadogexporter将指标和Trace同时导出到现有平台实现统一监控。通过以上步骤我们构建了一个从代码埋点、数据收集、指标提取到可视化分析的完整GenAI可观测性闭环。这套方案的核心优势在于它利用成熟的OpenTelemetry标准将GenAI的专项监控无缝嵌入到了整个应用的可观测性体系中使得AI不再是黑盒其性能、成本和质量都变得可度量、可分析、可优化。