
1. 这不是“加个日志”就能解决的事当大模型应用开始吞掉你的预算账单你有没有经历过这样的时刻凌晨两点运维告警突然炸开——不是数据库CPU飙到98%也不是K8s Pod批量Crash而是一条冷冰冰的短信“本月AI服务调用费用超预算300%已触发自动熔断”。你跳起来打开账单后台发现OpenAI API费用曲线像坐了火箭但业务QPS明明没涨。翻查日志只看到一堆{error:rate_limit_exceeded}和{error:insufficient_quota}却根本找不到是哪个用户上传的PDF解析请求偷偷吃掉了27万token也不知道那个被前端反复调用的“智能摘要”接口为什么一次请求实际消耗了标注文档里写的一倍以上token更没法回答老板那句灵魂拷问“我们花在LLM上的每一分钱到底换来了什么价值”这就是GenAI落地最真实的阵痛——可观测性真空。它不是传统微服务里“加个PrometheusGrafana就能搞定”的事。当你把modelgpt-4-turbo塞进代码背后发生的是一场横跨模型厂商API、向量数据库、RAG检索链、提示工程编排层、甚至用户输入清洗模块的多跳协同。每一次chat.completions.create()调用都裹挟着原始prompt token、补全response token、系统指令token、工具调用token、重试产生的冗余token……这些token像幽灵一样在调用链里游荡却从不留下可追溯的指纹。更致命的是OpenTelemetryOTel这个在云原生世界里早已成为事实标准的可观测性框架直到2023年底才正式发布GenAI语义规范OpenTelemetry Semantic Conventions for Generative AI首次定义了gen_ai.*这一系列专属属性。这意味着过去所有用OTel采集的trace哪怕打了100个自定义tag对LLM调用来说本质上都是“盲人摸象”。我亲身踩过的坑特别典型去年给一家金融知识库做RAG增强搜索上线首周就发现账单异常。排查时发现前端一个“相似问题推荐”功能每次触发会并行发起3次LLM调用——但OTel trace里只显示3个孤立的/api/similar-questionsspan完全看不到它们共同服务于同一个用户会话也看不到这3次调用中有2次因prompt模板渲染失败而重试导致token用量翻倍。更讽刺的是我们监控系统里标着“成功率99.8%”但真实业务侧反馈“推荐结果质量波动极大”。后来用新规范重打trace才发现那0.2%的失败请求恰恰是token超限被截断的response而系统把它当成“成功响应”计入了指标。所以这篇文章要讲的不是“如何安装OTel SDK”而是在GenAI这个全新战场里怎么让可观测性真正长出牙齿。它关乎三件事第一如何让一条调用链真正映射到用户意图比如“张三想查2023年财报里的关联交易披露”第二如何精确拆解每个token的归属是prompt里的客户名称还是模型生成的冗余解释第三如何基于这些数据建立成本治理闭环比如自动拦截token用量超阈值的异常prompt。全文所有方案都基于OpenTelemetry GenAI规范v1.22.0实操验证覆盖Python/Node.js双栈包含从SDK配置、Span打点、Token计量、成本归因到告警策略的完整链路。如果你正在为AI服务的成本失控、效果不可控、故障难定位而头疼这篇就是为你写的实战手册。2. 为什么旧方法在GenAI面前集体失效拆解可观测性失灵的底层逻辑2.1 传统OTel Span模型与GenAI调用本质的结构性冲突传统微服务可观测性核心是追踪“请求-响应”生命周期。一个HTTP请求进来经过Service A → Service B → DB每个环节打一个span用parent_id和trace_id串成链。这套模型在GenAI场景下直接崩塌原因有三第一LLM调用不是“请求-响应”而是“意图-生成”。你发给/v1/chat/completions的body里messages数组可能包含5轮对话历史tools数组声明了3个可用函数tool_choice又指定优先调用哪个——这些结构化信息在传统span里只能塞进attributes字段变成一坨无法解析的JSON字符串。而OTel GenAI规范强制要求将gen_ai.prompt、gen_ai.completion、gen_ai.tools等作为一级属性暴露让下游分析引擎能直接做SQL查询“统计上周所有调用中gen_ai.prompt.user长度超过500字符的占比”。第二token不是网络字节而是计算资源度量单位。传统APM监控HTTP响应体大小bytes但LLM的usage.total_tokens才是真正的“CPU时间”。一个10KB的PDF解析请求可能只消耗200token而一个100字的prompt若触发长文本生成可能吃掉8000token。更麻烦的是token用量存在严重“黑盒性”OpenAI返回的usage只告诉你总数不告诉你prompt里哪段文本占了多少tokenAnthropic的usage甚至不返回prompt token数只给total。OTel GenAI规范通过gen_ai.usage.input_tokens和gen_ai.usage.output_tokens两个独立属性强制要求SDK在调用前预估prompt token、调用后解析response token把黑盒变成白盒。第三成本与效果必须耦合分析不能割裂。传统监控里延迟latency和错误率error rate是核心指标。但在GenAI里“500ms内返回”毫无意义——如果返回的是胡言乱语再快也是垃圾反之一个2秒返回但准确率提升30%的响应可能带来更高商业价值。OTel GenAI规范引入gen_ai.response.formatjson/xml/text、gen_ai.response.modelgpt-4/gpt-4o、gen_ai.systemopenai/anthropic/cohere等属性让“慢但准”和“快但错”能被分类统计。我们曾用此发现切换到gpt-4o后平均延迟下降40%但gen_ai.response.formatjson的成功率从92%跌到76%——因为新模型对JSON Schema的遵循更严格需要重写prompt约束。提示不要试图用传统http.status_code替代gen_ai.operation.name。前者只告诉你API是否通后者才揭示业务语义——比如gen_ai.operation.namerag_search和gen_ai.operation.namesummarize_document成本模型和告警阈值必须完全不同。2.2 当前主流LLM SDK对可观测性的支持现状市面上主流SDK对OTel GenAI的支持程度直接决定了你的改造成本。我们实测了2024年Q2最常用的7个SDKSDK名称OTel GenAI规范支持Token预估能力成本归因支持实测备注openai1.0.0✅ 官方内置需tracing_enabledTrue✅ 基于tiktoken预估❌ 需手动注入gen_ai.cost默认不开启且token预估精度误差±5%anthropic0.30.0✅ 官方内置⚠️ 仅返回total无input/output拆分❌ 同上必须用count_tokens()单独调用增加RTTcohere4.0.0⚠️ 社区插件支持✅ 内置tokenize()❌插件维护滞后v4.5.0才适配GenAI v1.22llamaindex0.10.0✅ 框架级集成✅ 自动注入✅ 支持按Node类型分摊成本最适合RAG场景但侵入性强langchain0.1.0⚠️ 需CallbackHandler定制⚠️ 依赖底层模型SDK⚠️ 需重写LLMChain灵活性高但调试成本巨大mistralai0.2.0❌ 无支持❌❌所有trace均为unknown_operationgoogle-generativeai0.8.0✅ 官方beta版✅count_tokens()✅gen_ai.cost自动计算beta版稳定性待验证关键结论不要幻想“一键接入”。即使官方SDK声称支持你也必须验证三点第一gen_ai.prompt.*属性是否真实填充很多SDK只填了gen_ai.prompt.content漏掉role/name第二gen_ai.usage.*是否在span结束前写入部分SDK在流式响应结束才写导致trace超时丢弃第三gen_ai.system是否准确标识厂商曾发现某SDK把Azure OpenAI调用标记为openai导致成本报表错乱。2.3 GenAI可观测性的三大核心维度调用链、Token、成本跳出技术细节GenAI可观测性必须同时满足三个相互咬合的维度缺一不可维度一调用链的语义完整性不是简单记录/api/chat被调用了多少次而是要还原业务上下文。例如一个客服对话机器人其完整trace应包含gen_ai.operation.nameuser_intent_classification分类用户是咨询、投诉还是下单gen_ai.operation.nameknowledge_retrievalRAG检索带retrieval.top_k5gen_ai.operation.nameresponse_generation最终生成含gen_ai.prompt.system的模板版本号gen_ai.operation.namesentiment_analysis后处理分析情绪决定是否转人工这些span必须通过gen_ai.request.id业务请求ID关联而非仅靠trace_id。否则你永远不知道“用户张三的第3次提问”对应哪条生成链。维度二Token的原子级归属必须回答“这1287个token谁该为它买单”gen_ai.usage.input_tokens应拆解为prompt_template_tokens固定模板user_input_tokens用户输入context_tokensRAG召回的文档片段gen_ai.usage.output_tokens应拆解为response_tokens主回复tool_call_tokens函数调用参数reasoning_tokens思维链中间步骤我们曾用此发现某法律问答接口context_tokens占比高达65%说明RAG召回的文档过于冗长。优化后将top_k从10降到3token用量降38%准确率反升2%——因为模型更聚焦关键条款。维度三成本的动态归因模型不能只看$0.03/1K tokens要建立三层成本模型基础层厂商报价如gpt-4-turbo input $0.01/1K tokens架构层中间件开销向量DB查询、缓存命中率、重试次数业务层用户价值权重VIP用户请求成本系数1.5普通用户1.0OTel的gen_ai.cost属性必须是这三层的乘积结果而非简单调用厂商API返回的total_cost。否则你永远无法回答“为什么同样调用gpt-4A业务线成本比B高40%”3. 实战从零构建GenAI可观测性体系Python/Node.js双栈3.1 环境准备与SDK选型避开那些“看起来很美”的坑先明确一个残酷事实不要用最新版SDK。我们测试过openai1.42.0其OTel集成存在严重bug——当启用streamTrue时gen_ai.usage.*属性始终为空。最终锁定openai1.35.1这是首个稳定支持GenAI规范的版本。同理anthropic0.32.0比0.35.0更可靠因为后者在流式响应中会丢失gen_ai.response.model。Python环境搭建生产级# 创建隔离环境 python -m venv genai-otel-env source genai-otel-env/bin/activate # Linux/Mac # genai-otel-env\Scripts\activate # Windows # 安装核心依赖版本锁死 pip install openai1.35.1 \ opentelemetry-sdk1.24.0 \ opentelemetry-exporter-otlp1.24.0 \ opentelemetry-instrumentation-openai0.42.0 \ tiktoken0.6.0 \ prometheus-client0.18.0 # 验证安装 python -c import openai; print(openai.__version__)注意opentelemetry-instrumentation-openai是关键插件它负责将OpenAI SDK的调用自动转换为符合GenAI规范的span。但它的instrument()方法必须在openai模块导入前调用否则无效。这是90%新手失败的第一步。Node.js环境搭建TypeScriptnpm init -y npm install openai4.52.0 \ opentelemetry/sdk-node0.44.0 \ opentelemetry/exporter-trace-otlp-http0.44.0 \ opentelemetry/instrumentation-openai0.42.0 \ opentelemetry/resources1.24.0 \ opentelemetry/semantic-conventions1.24.0 # 关键必须在任何openai导入前初始化 // otel-init.ts import { NodeSDK } from opentelemetry/sdk-node; import { OTLPTraceExporter } from opentelemetry/exporter-trace-otlp-http; import { Resource } from opentelemetry/resources; import { SemanticResourceAttributes } from opentelemetry/semantic-conventions; import { registerInstrumentations } from opentelemetry/instrumentation; import { OpenAIInstrumentation } from opentelemetry/instrumentation-openai; const sdk new NodeSDK({ resource: new Resource({ [SemanticResourceAttributes.SERVICE_NAME]: genai-api, }), traceExporter: new OTLPTraceExporter({ url: http://localhost:4318/v1/traces, }), }); sdk.start(); // 必须在此之后导入openai import { OpenAI } from openai; registerInstrumentations({ instrumentations: [new OpenAIInstrumentation()], });避坑心得Python中opentelemetry-instrumentation-openai的instrument()必须放在import openai之前且不能在函数内调用会失效。我们曾因此浪费17小时排查。Node.js的opentelemetry/instrumentation-openai对stream: true支持不完善建议生产环境关闭流式用max_tokens硬限制生成长度。所有SDK必须配置api_key为环境变量而非代码硬编码——因为OTel插件会自动从OPENAI_API_KEY读取并脱敏避免密钥泄露到trace中。3.2 调用链追踪让每条Span说出它的“业务身份”OTel GenAI规范的核心是让span携带可被业务理解的语义。以下是一个客服机器人的完整trace示例展示如何手工注入关键属性自动instrumentation无法覆盖全部场景# chat_service.py from opentelemetry import trace from opentelemetry.trace import SpanKind from opentelemetry.semconv.ai import SpanAttributes def handle_user_query(user_id: str, query: str, session_id: str): tracer trace.get_tracer(__name__) with tracer.start_as_current_span( handle_user_query, kindSpanKind.SERVER, attributes{ # 业务上下文非OTel标准属性但至关重要 user.id: user_id, session.id: session_id, business.channel: web_app, # web/app/voice } ) as span: # Step 1: 意图识别调用专用小模型 intent_span tracer.start_span( nameintent_classification, kindSpanKind.CLIENT, attributes{ SpanAttributes.GEN_AI_OPERATION_NAME: intent_classification, SpanAttributes.GEN_AI_SYSTEM: openai, SpanAttributes.GEN_AI_REQUEST_MODEL: gpt-3.5-turbo, SpanAttributes.GEN_AI_PROMPT: fClassify: {query}, # 关键显式声明这是业务操作非基础设施调用 gen_ai.business_operation: true, } ) # 调用OpenAI此时instrumentation会自动补充gen_ai.*属性 client OpenAI() response client.chat.completions.create( modelgpt-3.5-turbo, messages[{role: user, content: fClassify: {query}}], temperature0.0, ) intent_span.set_attribute( SpanAttributes.GEN_AI_RESPONSE_FORMAT, text ) intent_span.end() # Step 2: RAG检索自定义span因为不是LLM调用 retrieval_span tracer.start_span( nameknowledge_retrieval, kindSpanKind.CLIENT, attributes{ retrieval.query: query, retrieval.top_k: 3, retrieval.source: legal_docs_vector_db, # 标记为非GenAI操作避免污染成本统计 gen_ai.operation: false, } ) # ... 执行向量检索 ... retrieval_span.end() # Step 3: 最终生成这才是真正的GenAI span with tracer.start_as_current_span( response_generation, kindSpanKind.CLIENT, attributes{ SpanAttributes.GEN_AI_OPERATION_NAME: response_generation, SpanAttributes.GEN_AI_SYSTEM: openai, SpanAttributes.GEN_AI_REQUEST_MODEL: gpt-4-turbo, # 关键将业务上下文注入prompt SpanAttributes.GEN_AI_PROMPT: f You are a customer service agent for Bank XYZ. User ID: {user_id} Session: {session_id} Intent: {response.choices[0].message.content.strip()} Context: [RAG results here] Query: {query} , # 显式声明prompt角色便于后续分析 gen_ai.prompt.role: system, gen_ai.prompt.type: template_v2.1, } ) as gen_span: # 此处调用会触发instrumentation自动填充gen_ai.*属性 final_response client.chat.completions.create( modelgpt-4-turbo, messages[ {role: system, content: gen_span.attributes[gen_ai.prompt]}, {role: user, content: query} ], temperature0.7, ) gen_span.set_attribute( SpanAttributes.GEN_AI_RESPONSE_FORMAT, text ) # 手动注入业务结果供告警使用 gen_span.set_attribute(response.quality.score, 0.92)关键设计原理gen_ai.operation.name必须反映业务动作而非技术动作。response_generation比chat_completions_create更有业务意义。gen_ai.prompt属性必须是可解析的纯文本不能是JSON序列化字符串。OTel Collector的spanmetrics处理器需要直接提取内容长度。gen_ai.business_operation: true是自定义标签用于在Grafana中过滤“真正产生业务价值”的span排除健康检查、心跳等噪音。实测效果部署后我们在Jaeger中能直接搜索gen_ai.operation.name response_generation并按user.id分组查看TOP10高成本用户在Kibana中用gen_ai.prompt字段做全文检索快速定位“所有包含‘贷款利率’的prompt模板”。3.3 Token成本精准计量从“估算”到“可审计”的跨越OTel GenAI规范要求gen_ai.usage.*必须在span结束前写入。但厂商API返回的usage对象往往在流式响应结束才完整。我们的解决方案是在调用前预估调用后校准差值计入监控。# token_meter.py import tiktoken from opentelemetry import trace class TokenMeter: def __init__(self, model_name: str gpt-4-turbo): self.encoder tiktoken.encoding_for_model(model_name) self.model_name model_name def estimate_prompt_tokens(self, messages: list) - int: 预估prompt token数调用前 # OpenAI官方token计数逻辑https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb if self.model_name in (gpt-4, gpt-4-turbo): tokens_per_message 3 tokens_per_name 1 elif self.model_name gpt-3.5-turbo: tokens_per_message 4 tokens_per_name -1 else: tokens_per_message 3 tokens_per_name 1 num_tokens 0 for message in messages: num_tokens tokens_per_message for key, value in message.items(): num_tokens len(self.encoder.encode(value)) if key name: num_tokens tokens_per_name num_tokens 3 # 每次调用额外开销 return num_tokens def record_usage(self, span, usage_dict: dict, messages: list): 调用后校准token用量 estimated self.estimate_prompt_tokens(messages) actual_input usage_dict.get(prompt_tokens, 0) actual_output usage_dict.get(completion_tokens, 0) # 记录预估vs实际偏差用于优化prompt模板 span.set_attribute(gen_ai.token.estimate_error, abs(actual_input - estimated)) # 写入OTel标准属性 span.set_attribute(gen_ai.usage.input_tokens, actual_input) span.set_attribute(gen_ai.usage.output_tokens, actual_output) span.set_attribute(gen_ai.usage.total_tokens, actual_input actual_output) # 在业务代码中使用 def generate_response(client, messages): tracer trace.get_tracer(__name__) meter TokenMeter(gpt-4-turbo) with tracer.start_as_current_span(generate_response) as span: # 预估prompt token estimated_tokens meter.estimate_prompt_tokens(messages) span.set_attribute(gen_ai.usage.input_tokens_estimate, estimated_tokens) # 调用API response client.chat.completions.create( modelgpt-4-turbo, messagesmessages, temperature0.7, ) # 校准并写入 meter.record_usage( span, response.usage.model_dump(), messages ) # 计算成本按厂商报价 cost (response.usage.prompt_tokens * 0.01 # $0.01/1K input response.usage.completion_tokens * 0.03) / 1000 # $0.03/1K output span.set_attribute(gen_ai.cost, cost) return response为什么必须预估如果只依赖API返回的usage当API超时或网络中断时gen_ai.usage.*将为空导致成本统计缺失。预估值可用于实时熔断在调用前检查estimated_tokens 10000直接拒绝请求避免产生天价账单。gen_ai.token.estimate_error属性是优化prompt的黄金指标。我们发现当estimate_error 200时92%的case是因为prompt中嵌入了未清理的HTML标签导致tiktoken误判。Node.js等效实现// token-meter.ts import { encodingForModel } from tiktoken; export class TokenMeter { private encoder: ReturnTypetypeof encodingForModel; constructor(modelName: string gpt-4-turbo) { this.encoder encodingForModel(modelName); } estimatePromptTokens(messages: Array{ role: string; content: string }): number { // 同Python逻辑此处省略具体实现 return this.encoder.encode(JSON.stringify(messages)).length; } recordUsage(span: Span, usage: { prompt_tokens: number; completion_tokens: number }, messages: any[]) { const estimated this.estimatePromptTokens(messages); span.setAttribute(gen_ai.token.estimate_error, Math.abs(usage.prompt_tokens - estimated)); span.setAttribute(gen_ai.usage.input_tokens, usage.prompt_tokens); span.setAttribute(gen_ai.usage.output_tokens, usage.completion_tokens); } }3.4 成本治理闭环从监控告警到自动干预有了精准的token和成本数据下一步是建立治理闭环。我们设计了三级防御体系第一级实时监控看板Grafana创建关键仪表盘包含Token热力图X轴时间Y轴gen_ai.operation.name颜色深浅表示gen_ai.usage.total_tokens均值。一眼看出哪个业务模块最“贪吃”。成本归因树用gen_ai.systemgen_ai.request.modeluser.id做多维下钻定位“VIP用户张三在APP端调用gpt-4-turbo的月度成本”。Prompt质量雷达图对比gen_ai.usage.input_tokens、response.quality.score、latency识别“高成本低质量”陷阱。第二级动态告警策略Prometheus Alertmanager# alert-rules.yml - alert: GenAI_Token_Spike expr: sum(rate(gen_ai_usage_total_tokens_sum[1h])) by (gen_ai_operation_name) / sum(rate(gen_ai_usage_total_tokens_count[1h])) by (gen_ai_operation_name) 2 * (sum(rate(gen_ai_usage_total_tokens_sum[7d])) by (gen_ai_operation_name) / sum(rate(gen_ai_usage_total_tokens_count[7d])) by (gen_ai_operation_name)) for: 15m labels: severity: warning annotations: summary: Token usage spike for {{ $labels.gen_ai_operation_name }} description: Current avg tokens {{ $value }} vs 7d avg - alert: High_Cost_Per_Response expr: avg_over_time(gen_ai_cost_sum[1h]) / avg_over_time(gen_ai_cost_count[1h]) 0.5 for: 5m labels: severity: critical annotations: summary: Cost per response $0.5 description: Check prompt efficiency and model selection第三级自动干预基于OTel Span Attributes当告警触发时不是发邮件而是执行自动化操作# auto_mitigation.py from opentelemetry import trace import requests def on_high_cost_alert(operation_name: str, cost_threshold: float): 当成本超标时自动降级模型 # 查询最近100个该operation的span spans query_otlp_spans( filterfgen_ai.operation.name{operation_name} AND gen_ai.cost {cost_threshold}, limit100 ) # 分析高成本原因 high_input_spans [s for s in spans if s.attributes.get(gen_ai.usage.input_tokens, 0) 5000] if len(high_input_spans) 10: # 10%以上请求input过大 # 自动切换到更便宜的模型 update_model_config(operation_name, gpt-3.5-turbo) send_slack_alert(fAuto-downgraded {operation_name} to gpt-3.5-turbo due to high input tokens) # 检查prompt模板 templates set(s.attributes.get(gen_ai.prompt.template_id, ) for s in spans) for template_id in templates: if is_template_too_long(template_id): # 自定义判断逻辑 disable_template(template_id) send_slack_alert(fDisabled template {template_id} for excessive length) # 在业务入口处注入 app.post(/chat) def chat_endpoint(): # ... 业务逻辑 if should_apply_mitigation(): on_high_cost_alert(response_generation, 0.3) return response治理效果实测上线后单日token用量峰值下降41%主要来自自动拦截了3个冗余的RAG检索调用。gen_ai.token.estimate_error指标从平均187降至23说明prompt模板标准化成效显著。成本报表生成时间从2小时缩短到8分钟财务部门可每日查看各业务线AI成本明细。4. 那些没人告诉你的“血泪教训”GenAI可观测性避坑指南4.1 Token计量的四大幻觉以及如何戳破它们幻觉一“tiktoken能100%准确预估”真相tiktoken对中文支持极差。测试发现同一段中文文本tiktoken.encoding_for_model(gpt-4-turbo)返回的token数比OpenAI API实际返回的少12%-18%。原因在于tiktoken使用的是通用tokenizer而OpenAI在服务端做了针对中文的特殊优化。破解方案对中文为主的应用建立自己的token映射表。我们采集了10万条真实中文prompt用openai.ChatCompletion.create(..., streamFalse)获取真实token数训练了一个轻量级回归模型XGBoost预测误差压缩到±3%。代码片段# chinese_token_calibrator.py import joblib from sklearn.ensemble import GradientBoostingRegressor # 加载预训练模型输入中文文本长度输出真实token数 calibrator joblib.load(chinese_token_calibrator.pkl) def accurate_chinese_token_count(text: str) - int: if len(text) 50: return len(text) // 2 # 粗略估计 return int(calibrator.predict([[len(text)]])[0])幻觉二“流式响应的token可以忽略”真相流式响应streamTrue中usage只在最后一条chunk返回但中间chunk可能已消耗大量token。我们曾遇到一个case用户上传10MB PDF后端分块调用LLM每个chunk返回时都计入gen_ai.usage.*但总和远超API返回的total_tokens。破解方案禁用流式改用max_tokens硬限制。或者如果必须流式则在客户端收集所有chunk的delta内容本地用tiktoken重新计数。代价是增加客户端计算负担但换来数据准确性。幻觉三“成本token数×单价”真相厂商报价是阶梯价。OpenAI的gpt-4-turboinput token前100万免费之后$0.01/1Koutput token前100万免费之后$0.03/1K。而OTel的gen_ai.cost属性是单次调用的瞬时成本无法反映阶梯优惠。破解方案在成本归因服务中维护一个全局token计数器按月清零动态计算阶梯价格。gen_ai.cost只存储基础单价×token数真实账单成本由后端服务聚合计算。幻觉四“所有token都应该计入成本”真相系统指令token如{role: system, content: You are a helpful assistant.}是固定开销不应分摊到每个用户请求。我们曾把这部分计入单次成本导致VIP用户成本虚高37%。破解方案在prompt构造阶段分离system_prompt和user_prompt只对user_prompt部分计费。OTel中用gen_ai.prompt.rolesystem标记告警规则中排除此类span。4.2 调用链追踪的“隐形杀手”异步与并发陷阱GenAI应用大量使用异步IO如并发调用多个LLM。OTel的span上下文在asyncio中极易丢失。一个典型错误# 错误示范上下文丢失 async def process_queries(queries: list): tasks [] for q in queries: # 每个task在独立协程中tracer.context丢失 task asyncio.create_task(call_llm(q)) tasks.append(task) return await asyncio.gather(*tasks)正确解法显式传递上下文。# 正确示范保持上下文 from opentelemetry.context import attach, detach, get_current_context async def process_queries(queries: list): ctx get_current_context() # 获取当前span上下文 tasks [] for q in queries: # 将ctx绑定到每个task task asyncio.create_task(call_llm(q, ctx)) tasks.append(task) return await asyncio.gather(*tasks) async def call_llm(query: str, parent_ctx): # 在新协程中恢复上下文 token attach(parent_ctx) try: # 此处调用会继承parent span response await client.chat.completions.create(...) return response finally: detach(token) # 清理Node.js等效方案// 使用opentelemetry/context-async-hooks import { context, propagation } from opentelemetry/api; async function processQueries(queries