☰
OpenTelemetry GenAI规范实战:大模型调用链追踪与Token成本治理
2026/10/8 22:57:19 网站建设 项目流程

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 Events(SSE)流式返回,一个请求可能持续几十秒,期间不断推送chunk。标准HTTP Span只能记录整个请求的总时长,无法反映"首Token延迟"(Time to First Token,TTFT)这个对用户体验影响最大的指标。用户感知的"快慢"往往取决于第一个字什么时候出现,而不是整个回答什么时候结束。

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的层级结构,是落地时第一个要做的设计决策。我的经验是采用三层结构:

最外层是会话级Span(Session Span),代表一次完整的用户会话。这个Span的生命周期可能跨越多次模型调用,携带用户ID、会话ID等业务属性。它的作用是让你能从业务视角回溯整个对话过程。

中间层是模型调用Span(LLM Call Span),每次调用大模型API生成一个Span。这一层携带GenAI规范定义的所有属性,是追踪的核心。如果一次对话涉及多轮调用(比如Agent场景下的工具调用循环),就会有多个模型调用Span。

最内层是流式响应Span(Stream 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_hash(Prompt内容的哈希值)。这样当发现某类请求特别慢或特别贵时,可以通过哈希值快速定位到具体的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_tokens,Anthropic叫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。如果调用量特别大,可以考虑分层采样:网关层做头部采样,只把采样到的请求发给Collector;Collector再做尾部采样,决定最终存储哪些。

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是新的"CPU",Prompt是新的"代码",成本是新的"性能指标"。只有建立起针对GenAI特性的观测体系,才能真正掌控这类应用。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询