多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

OpenTelemetry GenAI规范实战:大模型调用链追踪与Token成本治理

OpenTelemetry GenAI规范实战:大模型调用链追踪与Token成本治理 1. 从一次线上事故说起为什么GenAI应用需要专门的追踪体系去年底我接手了一个基于大模型API构建的智能客服系统上线第一周就遇到了一个非常典型的问题用户投诉响应慢但翻遍传统APM面板接口P99延迟只有800毫秒看起来完全正常。可用户端实际感受到的等待时间经常超过15秒。这个矛盾让我意识到传统可观测性工具在面对GenAI应用时存在严重的观测盲区。传统APM擅长追踪HTTP请求的进出时间但大模型调用是一个黑盒——请求发出去之后模型在服务端做了多少推理、生成了多少Token、是否触发了重试、流式响应中每个chunk的间隔是多少这些信息在标准HTTP指标里完全看不到。更麻烦的是成本问题一次对话可能消耗几千个Token如果某个环节出现Prompt膨胀或者死循环重试账单会在你毫无察觉的情况下飙升。这就是OpenTelemetry GenAI规范要解决的核心问题。它为大模型调用定义了一套语义约定Semantic Conventions让Span能够携带模型名称、Token用量、请求参数、响应状态等关键属性。说白了就是给大模型调用装上一个行车记录仪不仅记录什么时候出发、什么时候到达还记录一路上烧了多少油、走了哪条路、有没有绕远。这篇文章适合三类人正在构建AI应用的工程师、负责AI产品成本控制的团队负责人、以及希望把可观测性体系延伸到GenAI场景的运维同学。我会从规范本身讲起拆解调用链追踪的落地细节然后重点聊Token成本治理这个最容易被忽视但最烧钱的环节。所有内容都基于我在实际项目中的踩坑经验不是照搬文档。2. OpenTelemetry GenAI规范的语义约定到底定义了什么2.1 为什么不能直接用HTTP Span来追踪大模型调用很多人第一反应是大模型调用不就是个HTTP请求吗用标准的HTTP Span追踪不就行了我一开始也是这么想的直到发现几个致命问题。标准HTTP Span记录的是http.method、http.url、http.status_code这些属性。但当你面对一个/v1/chat/completions的POST请求时你完全不知道这次调用用的是GPT-4还是GPT-3.5不知道输入了多少Token不知道模型返回了多少Token更不知道这次调用花了多少钱。这些信息全部藏在请求体和响应体里而标准HTTP Span默认不记录body内容。另一个问题是流式响应。大模型普遍采用Server-Sent EventsSSE流式返回一个请求可能持续几十秒期间不断推送chunk。标准HTTP Span只能记录整个请求的总时长无法反映首Token延迟Time to First TokenTTFT这个对用户体验影响最大的指标。用户感知的快慢往往取决于第一个字什么时候出现而不是整个回答什么时候结束。GenAI规范正是为了解决这些盲区而设计的。它在标准Span的基础上增加了一组专门针对生成式AI的语义属性让追踪数据能够真实反映大模型调用的全貌。2.2 GenAI规范的核心属性字段拆解OpenTelemetry GenAI规范目前还在演进中但核心属性已经相对稳定。我把最关键的字段整理成了一张表方便你对照理解属性名类型说明实际用途gen_ai.systemstring模型提供方如openai、anthropic区分不同供应商的调用gen_ai.request.modelstring请求的模型名称追踪模型版本切换影响gen_ai.response.modelstring实际响应的模型发现模型被静默降级gen_ai.usage.input_tokensint输入Token数成本核算的核心依据gen_ai.usage.output_tokensint输出Token数成本核算的核心依据gen_ai.request.temperaturedouble温度参数排查输出不稳定问题gen_ai.request.max_tokensint最大输出Token限制分析截断问题gen_ai.response.finish_reasonsstring[]结束原因识别内容过滤或长度截断gen_ai.operation.namestring操作类型如chat、embeddings区分不同调用场景这些字段里我认为最有价值的是gen_ai.usage.input_tokens和gen_ai.usage.output_tokens。没有这两个字段成本治理就是一笔糊涂账。而gen_ai.response.finish_reasons则经常被忽视但它能帮你快速定位为什么回答被截断了这类问题——是触发了length限制还是被content_filter拦截了。2.3 Span的层级结构设计一次对话应该拆成几层规范定义了属性但怎么组织Span的层级结构是落地时第一个要做的设计决策。我的经验是采用三层结构最外层是会话级SpanSession Span代表一次完整的用户会话。这个Span的生命周期可能跨越多次模型调用携带用户ID、会话ID等业务属性。它的作用是让你能从业务视角回溯整个对话过程。中间层是模型调用SpanLLM Call Span每次调用大模型API生成一个Span。这一层携带GenAI规范定义的所有属性是追踪的核心。如果一次对话涉及多轮调用比如Agent场景下的工具调用循环就会有多个模型调用Span。最内层是流式响应SpanStream Span如果采用流式返回可以为每个chunk或者首Token单独记录事件。首Token延迟TTFT就记录在这一层。这种三层结构的好处是既能从宏观上看整个会话的耗时和成本又能下钻到单次调用的细节还能精确测量流式响应的用户体验指标。我试过把三层压缩成一层结果就是Span属性爆炸查询和分析都变得极其困难。3. 调用链追踪的落地从埋点到可视化的完整链路3.1 埋点位置的选择SDK层还是网关层埋点位置决定了你能采集到什么数据也决定了改造成本。常见的方案有两种在应用代码里通过SDK埋点或者在API网关层统一埋点。SDK层埋点的优势是信息最全。你能拿到完整的请求参数、响应内容、业务上下文比如用户ID、会话ID。缺点是需要在每个调用大模型的地方都加代码改造成本高而且不同语言、不同框架的SDK实现不一致。网关层埋点的优势是统一、无侵入。所有大模型调用都经过网关在网关统一注入追踪逻辑应用代码完全不用改。缺点是拿不到业务上下文而且如果请求体经过加密或压缩解析Token用量会比较麻烦。我的实际选择是混合方案网关层做基础埋点保证所有调用都有追踪数据关键业务路径在SDK层补充业务属性。这样既保证了覆盖率又保证了关键路径的数据丰富度。具体来说网关层负责记录gen_ai.system、gen_ai.request.model、Token用量这些通用属性SDK层负责补充user.id、session.id、conversation.turn这些业务属性。3.2 Token用量的采集响应头、响应体还是本地计算Token用量的采集是成本治理的基础但采集方式有讲究。主流大模型API的Token用量信息通常出现在两个地方响应体的usage字段或者响应头部分供应商会放在header里。响应体采集是最准确的方式因为供应商返回的usage字段就是官方计费依据。但问题是流式响应下usage字段通常在最后一个chunk才返回如果你在流式过程中就结束了Span就会丢失这个数据。我的做法是在流式响应结束后从最后一个chunk里提取usage信息然后更新Span属性。有些场景下你拿不到官方的usage字段比如自部署的模型或者某些兼容接口。这时候就需要本地计算Token用量。本地计算通常用tiktoken这类分词库但要注意不同模型的分词方式不同计算结果和官方计费可能有偏差。我的经验是本地计算只用于实时监控和告警最终成本核算还是以官方账单为准。注意流式响应下如果客户端提前断开连接部分供应商仍然会按完整输出计费。这种情况下usage字段可能不准确需要在Span里额外记录client.disconnected事件用于后续对账。3.3 首Token延迟的精确测量首Token延迟TTFT是GenAI应用最核心的用户体验指标但它的测量比想象中复杂。在流式响应下TTFT是指从请求发出到第一个chunk到达的时间。这个时间包含了网络传输、模型排队、Prefill阶段计算等多个环节。测量TTFT的关键是在收到第一个chunk时记录一个事件。在OpenTelemetry里可以用span.add_event()方法记录一个名为first_token的事件事件属性里带上时间戳。这样在追踪系统里就能看到TTFT的具体数值。但这里有个坑如果你的应用在网关和模型之间还有一层代理或负载均衡TTFT的测量点会受到影响。我遇到过一种情况网关记录的TTFT是200毫秒但用户端实际感受到的是2秒。排查后发现是网关到模型之间的连接池配置有问题导致请求在网关内部排队了1.8秒。所以TTFT的测量点应该尽量靠近用户侧或者至少在多个位置都记录时间戳方便对比分析。3.4 追踪数据的可视化与下钻分析埋点采集的数据最终要能可视化才有价值。我用的方案是OpenTelemetry Collector Jaeger/Tempo Grafana的组合。Collector负责接收和加工追踪数据Jaeger或Tempo负责存储和查询Grafana负责展示和告警。在Grafana里我通常会配置几个核心面板调用量趋势图按模型、按业务线拆分、Token消耗趋势图、TTFT分布图、错误率趋势图。这些面板能让你一眼看出系统是否健康。下钻分析是排查问题的关键。当发现某个时间段TTFT飙升时我会在追踪系统里按时间范围过滤然后按gen_ai.request.model分组看是某个特定模型的问题还是普遍问题。如果是个别请求的问题就进一步查看该请求的完整Span链路看时间消耗在哪个环节。这里分享一个实用技巧在Span里记录gen_ai.request.prompt_hashPrompt内容的哈希值。这样当发现某类请求特别慢或特别贵时可以通过哈希值快速定位到具体的Prompt模板而不需要把完整的Prompt内容存进追踪系统既节省存储又避免敏感信息泄露。4. Token成本治理从月底看账单到实时可管控4.1 Token成本失控的三种典型模式在聊治理方案之前先看看Token成本是怎么失控的。根据我的观察最常见的三种模式是Prompt膨胀系统Prompt随着功能迭代不断加长从最初的几百Token膨胀到几千Token。每次调用都要带上完整的系统Prompt成本成倍增加。更隐蔽的是Few-shot示例为了提升效果不断添加示例每个示例都是几百Token。重试风暴当模型返回错误或超时时应用层自动重试。如果重试逻辑没有退避机制或者错误原因是Prompt本身有问题比如触发了内容过滤重试只会不断烧钱而不会成功。上下文累积多轮对话场景下每轮都把完整的历史对话传给模型。对话轮次越多输入Token越多成本呈线性增长。我见过一个客服系统第10轮对话的输入Token是第1轮的8倍。这三种模式的共同点是在传统监控体系下完全不可见。你只能看到API调用次数看不到每次调用的Token用量更看不到成本趋势。等月底账单出来已经来不及了。4.2 基于Span属性的实时成本计算成本治理的第一步是让成本可见。基于GenAI规范采集的Token用量数据可以在追踪系统里实时计算成本。计算逻辑很简单成本 输入Token数 × 输入单价 输出Token数 × 输出单价。不同模型的单价不同所以需要在配置里维护一张模型单价表。这张表需要定期更新因为供应商会调价。在OpenTelemetry Collector里可以用Processor对Span进行加工根据gen_ai.request.model和Token用量计算出成本然后作为一个新的Span属性写入。这样在Grafana里就能直接展示成本趋势而不需要额外做数据关联。我通常会在Grafana里配置几个成本相关的面板按业务线拆分的成本趋势、按模型拆分的成本占比、单次调用平均成本、成本异常告警比如单次调用成本超过阈值。这些面板能让成本问题在发生的当天就被发现而不是等到月底。4.3 用Span数据定位成本热点成本可见之后下一步是定位成本热点。所谓成本热点就是那些消耗了大量Token但价值不高的调用。定位方法是在追踪系统里按Token用量排序找出消耗最高的那些Span然后分析它们的特征。常见的成本热点包括某个特定的Prompt模板、某个业务场景、某个用户或租户。我遇到过一个典型案例某个租户的调用量只占5%但Token消耗占了30%。深入分析后发现这个租户的对话轮次特别多而且每轮都触发了工具调用导致上下文不断累积。定位到问题后我们针对这个租户做了上下文窗口优化成本直接降了60%。另一个常见热点是失败重试。在Span里记录retry.count属性就能快速找出重试率高的调用。如果重试原因是content_filter说明Prompt需要调整如果是超时说明需要优化超时配置或增加并发。4.4 成本管控的工程手段限流、缓存与Prompt优化定位到成本热点后就需要工程手段来管控。我常用的手段有三种Token级限流传统的QPS限流对大模型调用不够精细因为每次调用的Token用量差异很大。更好的做法是基于Token用量的限流比如每个租户每分钟最多消耗10万Token。实现方式是在网关层累计Token用量超过阈值就拒绝请求。这需要追踪系统提供实时的Token用量数据。语义缓存很多用户提问是重复的或高度相似的。语义缓存的做法是把用户提问向量化在缓存里查找相似问题如果相似度超过阈值就直接返回缓存答案不调用模型。这能大幅降低Token消耗但要注意缓存命中率和对答案新鲜度的要求。Prompt压缩对于长系统Prompt可以通过压缩技术减少Token用量。比如把Few-shot示例从5个减到3个或者用更简洁的表述。但压缩会影响效果需要做A/B测试验证。这三种手段里我认为Token级限流的性价比最高因为它直接防止了成本失控而且实现相对简单。语义缓存的效果最好但实现复杂度高适合调用量大、问题重复率高的场景。5. 踩坑实录那些文档里不会告诉你的细节5.1 流式响应下Span结束时机导致的Token丢失这是我踩过的最大的坑。最初实现时我在流式响应开始时就结束了Span结果发现Token用量字段全是空的。排查后发现usage字段在最后一个chunk才返回Span提前结束就采集不到。修复方案是在流式响应完全结束后再结束Span。但这里有个新问题如果客户端提前断开连接流式响应可能永远不会正常结束Span就会一直挂着。我的做法是设置一个最大超时时间超过时间就强制结束Span并记录client.disconnected事件。另一个细节是部分供应商在流式响应下不返回usage字段或者只在特定参数下返回。比如OpenAI需要在请求里设置stream_options: {include_usage: true}才会在流式响应里返回usage。这个参数很容易被忽略导致Token数据采集不到。5.2 多模型供应商下的属性映射不一致如果你的应用同时调用多个供应商的模型比如OpenAI、Anthropic、国内厂商会发现它们的API返回格式各不相同。OpenAI的Token字段叫usage.prompt_tokens和usage.completion_tokensAnthropic叫usage.input_tokens和usage.output_tokens国内厂商可能又是另一套命名。GenAI规范定义了统一的属性名但需要你在埋点时做映射。我的做法是在网关层写一个适配器把不同供应商的返回格式统一映射到GenAI规范的属性名。这样追踪系统里的数据就是一致的查询和分析都方便。映射时要注意字段的语义差异。比如有些供应商的input_tokens包含了系统Prompt有些不包含。如果不注意成本计算会有偏差。5.3 高并发下的追踪数据采样策略大模型应用通常调用量很大如果每个调用都采集完整的追踪数据存储成本会很高。这时候需要采样策略。常见的采样策略有两种头部采样Head-based Sampling和尾部采样Tail-based Sampling。头部采样在请求开始时决定是否采样实现简单但可能漏掉重要请求。尾部采样在请求结束后根据结果决定是否采样能保证采集到错误请求和慢请求但需要缓存所有Span直到请求结束。我的选择是尾部采样采样规则是所有错误请求100%采集TTFT超过阈值的请求100%采集正常请求按1%采样。这样既保证了问题可追溯又控制了存储成本。但尾部采样对Collector的性能要求较高因为需要缓存Span。如果调用量特别大可以考虑分层采样网关层做头部采样只把采样到的请求发给CollectorCollector再做尾部采样决定最终存储哪些。5.4 敏感信息与Prompt内容的处理边界追踪数据里如果包含完整的Prompt和响应内容会带来隐私和合规风险。但完全不记录内容又会影响问题排查。我的做法是分级处理默认只记录Prompt的哈希值和长度不记录内容。对于需要排查问题的场景通过配置开关临时开启内容记录并且记录的内容要脱敏比如替换掉用户手机号、身份证号等敏感信息。另一个细节是即使只记录哈希值也要注意哈希算法的选择。如果用MD5理论上存在碰撞风险如果用SHA-256计算开销又比较大。我的经验是用SHA-256的前16位碰撞概率极低计算开销也可接受。6. 从追踪数据到成本优化的闭环实践6.1 建立成本基线与异常检测成本治理不是一次性工作而是持续的过程。第一步是建立成本基线在系统稳定运行一段时间后统计出正常的成本水平比如日均成本、单次调用平均成本、各业务线的成本占比。有了基线才能判断什么是异常。异常检测可以用简单的阈值告警也可以用更复杂的时序异常检测算法。我的经验是先用简单阈值比如单日成本超过基线20%就告警。等积累足够数据后再考虑用算法做更精细的检测。告警要分级轻微异常发通知严重异常直接触发限流或降级。比如成本超过基线50%时自动降低非核心业务的模型档次从GPT-4降到GPT-3.5保证核心业务不受影响。6.2 用A/B测试验证成本优化效果任何成本优化手段都可能影响效果所以需要用A/B测试验证。比如你想把系统Prompt从2000 Token压缩到1000 Token就需要对比压缩前后的答案质量。A/B测试的关键是定义好效果指标。对于客服场景可以用答案采纳率、用户满意度、问题解决率等指标。对于内容生成场景可以用人工评分或自动化评分。我的做法是在追踪数据里记录实验分组experiment.group属性这样就能在追踪系统里直接对比不同分组的成本和质量指标。如果压缩后的质量下降在可接受范围内而成本降低显著就可以全量推广。6.3 成本优化的组织协作让工程师对成本负责技术手段之外组织协作也很重要。如果工程师只关注功能实现不关注成本优化就无从谈起。我的做法是把成本指标纳入团队的日常看板让每个工程师都能看到自己负责的模块消耗了多少Token。同时设置成本预算超预算需要说明原因。这样工程师在写Prompt、设计调用逻辑时就会自然地考虑成本。另一个做法是定期做成本复盘分析成本变化的原因分享优化案例。这能形成正向循环让成本优化成为团队文化的一部分。7. 我在实际项目中的几点体会聊了这么多技术和方案最后分享几点个人体会。第一可观测性建设要趁早。我见过太多项目是在成本失控或故障频发之后才开始补追踪这时候历史数据已经丢失排查问题非常被动。如果一开始就按GenAI规范埋点后面会省很多事。第二Token成本治理的核心是可见。只要成本可见工程师自然会想办法优化。最怕的是成本不可见大家觉得反正用的是公司的钱最后账单出来才傻眼。第三不要追求一步到位。GenAI规范还在演进追踪系统也可以逐步完善。先采集核心属性模型、Token用量、TTFT再逐步补充细节。先做基础的成本可见再做精细化的成本管控。第四流式响应的追踪是最容易出问题的环节建议重点测试。特别是客户端断开、超时、重试这些边界情况一定要覆盖到。第五追踪数据本身也有成本。如果调用量特别大要考虑采样和存储策略避免追踪系统本身成为成本负担。这套方案我在三个项目中落地过最大的感受是GenAI应用的可观测性和传统应用有本质区别不能简单套用传统APM的思路。Token是新的CPUPrompt是新的代码成本是新的性能指标。只有建立起针对GenAI特性的观测体系才能真正掌控这类应用。
返回列表