1. 项目概述:当AI应用跑起来之后,你真的知道它在想什么吗?
“可观测性”这个词,在传统后端系统里已经讲了十年——日志、指标、链路,三件套一摆,运维心里就有底。但到了GenAI应用这层,老办法突然就失灵了。你调用一个大模型API,返回结果是“好的,明白了”,可背后到底发生了什么?Prompt被切了几段?Embedding用了多少token?RAG检索时查了几个chunk?LLM推理阶段是不是卡在某个temperature参数上反复重试?更关键的是——这一轮对话花了多少钱?是$0.02还是$2.37?没人能说清。
这就是标题里那个沉甸甸的短语“AI应用可观测性”真正要解决的问题:不是让系统“不挂”,而是让AI行为“可解释、可归因、可计量”。OpenTelemetry GenAI规范不是凭空冒出来的,它是开发者被现实反复毒打后的集体共识——当你的SaaS产品按token计费,当客户投诉“响应慢但账单高”,当内部A/B测试发现微小的system prompt调整让成本飙升47%,你就必须把AI调用过程像拆解一台精密钟表一样,一颗齿轮、一根游丝地看清楚。
我去年带团队落地一个智能客服Agent,上线第三周财务同事找上门:“上个月API账单涨了3倍,但对话量只涨了12%。”我们翻遍CloudWatch日志,只看到一堆200状态码和模糊的“request_id”。最后靠硬写埋点代码,才定位到是某次向量库检索失败后触发了5次重试,每次重试都带着完整prompt重新调用embedding API——单次对话实际消耗了17倍的embedding token。这件事让我彻底明白:在GenAI场景下,没有细粒度的调用链追踪,所谓“优化成本”就是闭着眼睛猜谜。而OpenTelemetry GenAI规范,正是把这种“猜谜”变成“读数”的基础设施。它不替代你的业务逻辑,但会给你一把刻着毫米精度的尺子,去量每一次prompt、每一个token、每一毫秒延迟的真实重量。
2. OpenTelemetry GenAI规范:为什么不是简单复用传统链路追踪?
2.1 传统OTel链路追踪在GenAI场景下的三大断点
OpenTelemetry本身是一套成熟的可观测性标准,但直接把Web服务那套span结构套在AI调用上,会立刻撞上三堵墙:
第一堵墙:语义鸿沟——Span的“操作名”无法承载AI行为意图
传统HTTP span的operation name可能是GET /api/users或POST /order/create,它描述的是“做什么”。但AI调用中,POST /v1/chat/completions这个操作名毫无信息量——它既没告诉你这是RAG增强后的问答,还是纯指令遵循,也没说明prompt里是否包含敏感PII数据,更无法区分这是用户真实提问还是后台健康检查。GenAI规范强制要求在span属性中注入gen_ai.operation.name(如chat_completion、embedding、retrieval),并配套定义gen_ai.request.*和gen_ai.response.*系列属性,把原本藏在body里的语义显式暴露出来。
第二堵墙:Token不可见——传统指标体系无法穿透模型API的黑盒
HTTP metrics只能统计请求次数、响应时间、状态码。但AI成本的核心单位token是模型厂商在API网关后计算的。OpenTelemetry GenAI规范通过gen_ai.usage.input_tokens、gen_ai.usage.output_tokens、gen_ai.usage.total_tokens三个标准化属性,要求SDK在收到API响应后,从usage字段(OpenAI/Anthropic等主流厂商已支持)中解析出token用量,并作为span的属性持久化。这意味着你不需要反向工程厂商文档,就能在Jaeger里直接筛选“output_tokens > 1000”的异常span。
第三堵墙:上下文丢失——LLM调用链天然具备多跳、异步、条件分支特性
一个典型RAG流程:用户提问 → LLM生成检索query → 向量库检索 → LLM整合检索结果生成答案。传统trace可能只记录最外层的chat_completionspan,中间的embedding和retrieval被当作“内部函数调用”淹没。GenAI规范强制要求为每个AI操作创建独立span,并通过gen_ai.system(如openai、cohere、local_llm)标识执行环境,再用gen_ai.prompt.template记录原始prompt模板。这样在trace视图里,你能清晰看到一条垂直链路:chat_completionspan下挂载两个子span——embedding(耗时82ms,input_tokens=24)和retrieval(耗时12ms,top_k=3),最后回到chat_completion的output_tokens=156。这才是真实的AI决策路径。
提示:很多团队试图用自定义HTTP middleware拦截OpenAI SDK的请求/响应来提取token,这不仅违反厂商ToS(OpenAI明确禁止中间件篡改请求头),且在流式响应(streaming)场景下根本无法获取最终token数。GenAI规范要求SDK原生支持,正是为规避这类灰色方案。
2.2 GenAI规范的核心实体与属性设计逻辑
OpenTelemetry GenAI规范并非大而全的百科全书,而是聚焦AI可观测性最关键的四个实体,每个实体都对应一个明确的业务问题:
| 实体名称 | 解决的核心问题 | 关键属性示例 | 设计意图 |
|---|---|---|---|
| Chat Completion | “这次对话花了多少钱?为什么贵?” | gen_ai.request.model,gen_ai.request.temperature,gen_ai.usage.input_tokens,gen_ai.response.finish_reason | 将LLM调用从“黑盒API”还原为可审计的计算单元,finish_reason能区分stop(正常结束)、length(超长截断)、content_filter(内容过滤)等成本异常信号 |
| Embedding | “向量化过程是否低效?有没有冗余计算?” | gen_ai.request.dimensions,gen_ai.request.input_count,gen_ai.usage.total_tokens | 支持批量embedding(input_count>1)的计量,避免将100个文本的向量化记为100次调用,真实反映向量库构建成本 |
| Retrieval | “检索环节是否拖慢整体响应?召回质量如何?” | gen_ai.retrieval.top_k,gen_ai.retrieval.score_threshold,gen_ai.retrieval.document_count | 将向量检索从“数据库查询”升维为AI工作流环节,document_count直接关联RAG的上下文长度与LLM输入token消耗 |
| Tool Call | “Agent调用外部工具是否成功?失败原因是什么?” | gen_ai.tool.name,gen_ai.tool.description,gen_ai.tool.parameters,gen_ai.tool.response | 为AI Agent的function calling提供可追溯的执行日志,parameters和response明文记录,便于审计工具调用合规性 |
这些属性的设计逻辑非常务实:所有属性名采用gen_ai.*前缀确保命名空间隔离;所有数值型属性(如token数)强制要求为整数类型,避免浮点精度误差影响成本核算;所有字符串属性(如model name)要求小写+下划线格式,保证跨语言SDK的一致性。我见过有团队把gen_ai.request.model存成GPT-4-turbo,结果在Prometheus里做count by (gen_ai_request_model)聚合时,gpt-4-turbo和GPT-4-turbo被算作两个不同值——规范里明确要求“model name must be lowercase”。
2.3 为什么必须是“规范”而非“SDK”?——生态协同的底层逻辑
很多人问:既然有LangChain、LlamaIndex这些框架,为什么还要GenAI规范?答案在于责任边界。LangChain的CallbackHandler可以记录token,但它只对使用LangChain的代码有效;而GenAI规范是OTel Collector层面的协议标准,意味着只要你的应用接入了OTel SDK(无论用Python/Go/Java),只要调用的AI服务SDK实现了GenAI规范(如openai-pythonv1.0+、anthropicv0.25+),数据就能自动流入统一的后端(Jaeger/Prometheus/Grafana)。
这种解耦带来的好处是颠覆性的。举个真实案例:我们有个客户同时用Python跑LLM服务,用Go写向量检索微服务,用TypeScript开发前端Agent。过去每种语言都要写一套token解析逻辑,监控大盘数据来源五花八门。接入GenAI规范后,三套服务只需各自配置OTel Exporter指向同一个Collector,所有AI调用数据自动对齐到同一套gen_ai.*属性体系。运维同学在Grafana里用一条PromQL就能查出:“过去24小时,Go服务的retrievalspan平均延迟是否显著高于Python服务的chat_completion?”——这种跨语言、跨服务的横向对比能力,是任何单一SDK无法提供的。
注意:规范本身不解决数据采集,它解决的是“采集什么”和“怎么命名”。具体实现依赖各语言SDK的适配。目前Python生态最成熟(
opentelemetry-instrumentation-openai已全面支持),Go和Java社区正在快速跟进,但Node.js的@opentelemetry/instrumentation-openai仍停留在beta阶段,生产环境需谨慎评估。
3. 调用链追踪实战:从零搭建GenAI可观测性流水线
3.1 环境准备与依赖选型:避开那些坑了三年的版本陷阱
搭建GenAI可观测性流水线,核心是三条链路:应用端埋点 → Collector数据处理 → 后端存储与可视化。每条链路上都有必须避开的“经典陷阱”,我按实际踩坑顺序列出来:
应用端(Python为例):
- 必须使用
openai>=1.0.0:旧版openai<1.0(即openai==0.28)完全不支持GenAI规范,其Completion.create()方法返回的usage字段是字典而非标准对象,且无prompt_filter_results等新字段。升级后所有调用自动注入gen_ai.*属性。 - OTel Python SDK必须≥1.22.0:早期版本(如1.19.0)的
opentelemetry-instrumentation-openai插件存在token解析bug——当API返回"usage": {"prompt_tokens": 123, "completion_tokens": 45}时,插件错误地将prompt_tokens映射为gen_ai.usage.input_tokens,而规范要求input_tokens应包含system prompt + user prompt + tool call等全部输入。正确版本会合并所有输入片段后计算总token。 - 禁用
httpx的默认连接池:OpenAI Python SDK底层用httpx,其默认连接池在高并发下会导致span丢失。必须在初始化时显式配置:from openai import AsyncOpenAI client = AsyncOpenAI( http_client=httpx.AsyncClient( limits=httpx.Limits(max_connections=100, max_keepalive_connections=20) ) )
Collector端:
- 必须使用
otelcol-contrib>=0.92.0:旧版Collector(如0.85.0)的otlpreceiver不识别gen_ai.*属性,会直接丢弃。新版增加了attributes处理器,可对GenAI属性做标准化清洗(如将gen_ai.request.model转为小写)。 - 关键配置陷阱:Collector的
exporter不能直接连Jaeger,必须经过batch处理器。因为GenAI span产生频率极高(单次对话可能生成5-10个span),未批处理会导致Jaeger接收大量小包,内存暴涨。标准配置如下:processors: batch: timeout: 10s send_batch_size: 1024 exporters: otlp/jaeger: endpoint: "jaeger:4317" service: pipelines: traces: receivers: [otlp] processors: [batch] # 必须!否则Jaeger OOM exporters: [otlp/jaeger]
后端可视化:
- Jaeger UI必须启用
search功能:默认Jaeger安装不开启搜索,导致无法按gen_ai.usage.total_tokens > 1000筛选。需在启动参数加--search.max-num-spans=10000。 - Prometheus必须配置
metric处理器:单纯用OTel Collector导出trace无法做成本趋势分析。需在Collector中添加metrics处理器,将gen_ai.usage.*属性转换为Prometheus指标:processors: metricstransform: transforms: - include: gen_ai.usage.* action: insert new_name: gen_ai_usage_total units: "tokens"
实操心得:我们曾因Collector版本过低,在上线首周丢失了37%的embedding span。排查过程耗时两天——先怀疑是SDK埋点问题,重装Python依赖;再怀疑网络丢包,抓包分析;最后才发现Collector日志里有一行被忽略的警告:“unknown attribute gen_ai.usage.input_tokens”。记住:GenAI可观测性是端到端的,任何一个环节版本不匹配,整条链路就断在暗处。
3.2 核心埋点代码:让每一次AI调用都留下可追溯的指纹
埋点代码不是越复杂越好,关键是精准、轻量、无侵入。以下是以FastAPI服务为例的GenAI埋点最佳实践,全程不修改业务逻辑,仅通过OTel装饰器注入:
# main.py from fastapi import FastAPI, Request, Response from opentelemetry import trace from opentelemetry.instrumentation.fastapi import FastAPIInstrumentor from opentelemetry.instrumentation.openai import OpenAIInstrumentor from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import BatchSpanProcessor # 初始化OTel Tracer provider = TracerProvider() processor = BatchSpanProcessor(OTLPSpanExporter(endpoint="http://otel-collector:4318/v1/traces")) provider.add_span_processor(processor) trace.set_tracer_provider(provider) app = FastAPI() # 自动为所有FastAPI路由创建span FastAPIInstrumentor.instrument_app(app) # 自动为所有OpenAI调用注入gen_ai.*属性 OpenAIInstrumentor().instrument() @app.post("/chat") async def chat_endpoint(request: Request): # 业务逻辑:解析用户请求 data = await request.json() user_prompt = data.get("prompt", "") # 创建子span,显式标注AI操作语义 tracer = trace.get_tracer(__name__) with tracer.start_as_current_span("chat_completion") as span: # 关键:手动设置GenAI规范要求的必需属性 span.set_attribute("gen_ai.operation.name", "chat_completion") span.set_attribute("gen_ai.request.model", "gpt-4-turbo") span.set_attribute("gen_ai.request.temperature", 0.3) span.set_attribute("gen_ai.request.max_tokens", 512) # 记录原始prompt(用于后续审计,非必填但强烈建议) span.set_attribute("gen_ai.prompt.template", user_prompt[:200] + "..." if len(user_prompt) > 200 else user_prompt) # 执行LLM调用(此时OpenAIInstrumentor自动注入token用量) from openai import AsyncOpenAI client = AsyncOpenAI() response = await client.chat.completions.create( model="gpt-4-turbo", messages=[{"role": "user", "content": user_prompt}], temperature=0.3, max_tokens=512 ) # 手动记录响应元数据(finish_reason对成本分析至关重要) span.set_attribute("gen_ai.response.finish_reason", response.choices[0].finish_reason) span.set_attribute("gen_ai.response.id", response.id) return {"response": response.choices[0].message.content}这段代码的精妙之处在于分层埋点:
- 外层
FastAPIInstrumentor捕获HTTP层信息(status_code、method、url); - 中层
OpenAIInstrumentor自动解析API响应中的usage字段,填充gen_ai.usage.*属性; - 内层手动
span.set_attribute注入业务语义(model、temperature、finish_reason),形成完整的AI行为画像。
特别注意gen_ai.prompt.template的截断处理:直接记录完整prompt会污染trace存储(单个prompt可能上万字符),且存在安全风险。我们约定只存前200字符+省略号,既保留关键上下文(如“请根据以下合同条款分析违约责任”),又避免存储敏感数据。
常见问题:为什么不用
@tracer.start_as_current_span装饰器?因为FastAPI的@app.post装饰器与OTel装饰器存在生命周期冲突,会导致span嵌套异常。正确姿势是像上面代码一样,在路由函数内显式创建span,确保父子关系清晰。
3.3 调用链深度解析:在Jaeger中读懂AI决策树
部署好流水线后,打开Jaeger UI,搜索一个chat_completionspan,你会看到远超传统HTTP trace的丰富信息。以下是一个真实RAG场景的trace结构解析:
Root Span: chat_completion (service: ai-gateway) ├── Span: embedding (service: vector-db) │ ├── Attributes: │ │ gen_ai.operation.name: "embedding" │ │ gen_ai.request.model: "text-embedding-3-small" │ │ gen_ai.usage.input_tokens: 128 │ │ gen_ai.usage.total_tokens: 128 │ └── Events: │ - "embedding generated for query: '合同违约金计算方式'" ├── Span: retrieval (service: vector-db) │ ├── Attributes: │ │ gen_ai.operation.name: "retrieval" │ │ gen_ai.retrieval.top_k: 3 │ │ gen_ai.retrieval.document_count: 3 │ │ gen_ai.usage.total_tokens: 0 # 检索本身不消耗LLM token │ └── Events: │ - "retrieved documents: [doc_123, doc_456, doc_789]" └── Span: chat_completion (service: llm-service) ├── Attributes: │ gen_ai.operation.name: "chat_completion" │ gen_ai.request.model: "gpt-4-turbo" │ gen_ai.usage.input_tokens: 1247 # system prompt(128) + user prompt(24) + 3 retrieved docs(1095) │ gen_ai.usage.output_tokens: 321 │ gen_ai.response.finish_reason: "stop" └── Events: - "LLM response generated"这个trace揭示了三个关键洞察:
- 成本黑洞定位:
input_tokens=1247中,1095个来自检索到的3个文档,占比88%。优化方向立即明确——不是调低LLM temperature,而是优化向量检索的top_k或文档切片策略。 - 性能瓶颈识别:
embeddingspan耗时82ms,retrieval仅12ms,但chat_completion耗时1240ms。说明LLM推理是主要延迟源,需检查是否启用了stream=True减少首字延迟。 - 质量风险预警:若
gen_ai.response.finish_reason为content_filter,则表明输出被安全策略拦截,需检查system prompt是否过于宽松。
实操技巧:在Jaeger中点击任意span的“Tags”标签页,按住Ctrl键多选
gen_ai.usage.input_tokens、gen_ai.usage.output_tokens、gen_ai.request.model,然后右键“Add to graph”——Jaeger会自动生成一张节点为span、边为token用量的拓扑图。这张图能直观显示“哪个模型吃掉了最多token”,比翻日志高效十倍。
3.4 Token成本治理:从“看数”到“管数”的闭环
可观测性的终极价值不是展示数据,而是驱动行动。基于GenAI规范的数据,我们构建了三级Token成本治理体系:
第一级:实时告警(防突发)
在Prometheus中配置告警规则,捕捉异常成本事件:
# 单次对话总token超阈值(针对高价值客户) sum by (gen_ai_request_model) (rate(gen_ai_usage_total{job="ai-service"}[5m])) > 5000 # 某模型output_tokens突增(可能提示prompt注入攻击) avg_over_time(gen_ai_usage_output_tokens{gen_ai_request_model="gpt-4-turbo"}[1h]) / avg_over_time(gen_ai_usage_output_tokens{gen_ai_request_model="gpt-4-turbo"}[24h]) > 2.0告警触发后,自动在Slack创建工单,并附上Jaeger trace链接,运维可10秒内定位到具体哪次调用异常。
第二级:成本归因(溯源头)
在Grafana中构建“Token成本驾驶舱”,核心看板包括:
- 模型成本热力图:X轴为时间(小时),Y轴为
gen_ai.request.model,色块大小=该模型总token用量,颜色深浅=单位token成本(从云厂商账单API同步)。一眼看出claude-3-opus虽能力强,但单位token成本是gpt-3.5-turbo的8倍。 - 功能模块成本占比:用
gen_ai.operation.name分组,显示chat_completion占72%、embedding占23%、retrieval占5%。若retrieval占比异常升高,说明RAG缓存失效。 - 用户级成本TOP10:关联用户ID(需在span中注入
user_id属性),识别出贡献了35% token用量的3个企业客户,为其定制降本方案(如启用私有化模型)。
第三级:自动化治理(控预算)
在应用层嵌入成本熔断机制:
# 在LLM调用前检查预估token def estimate_input_tokens(model: str, prompt: str, context_docs: List[str]) -> int: # 使用tiktoken估算(gpt-4-turbo用cl100k_base编码) encoder = tiktoken.get_encoding("cl100k_base") tokens = len(encoder.encode(prompt)) for doc in context_docs: tokens += len(encoder.encode(doc)) * 0.8 # 文档压缩系数 return tokens # 成本熔断 if estimate_input_tokens("gpt-4-turbo", user_prompt, retrieved_docs) > 2000: # 自动降级到gpt-3.5-turbo response = await client.chat.completions.create( model="gpt-3.5-turbo", messages=[{"role": "user", "content": user_prompt}], temperature=0.1 ) # 记录降级事件 span.set_attribute("gen_ai.cost_control.action", "model_downgrade")这套机制让成本治理从“事后分析”变为“事中干预”,某客户上线后月度token成本下降41%,且用户满意度未受影响——因为降级逻辑只在高成本场景触发,普通问答仍用高性能模型。
注意事项:token估算永远存在误差(尤其对非OpenAI模型),因此熔断阈值需留20%余量。我们采用“保守估算+动态学习”策略:初始阈值设为2000,每周用实际
gen_ai.usage.input_tokens与估算值对比,自动校准估算系数。
4. Token成本治理进阶:超越计费,直击AI应用健康度
4.1 Token不是成本单位,而是AI应用的“代谢指标”
把token简单等同于“钱”,是初级认知。在深度实践中,token用量是反映AI应用健康度的黄金指标,其意义远超财务范畴:
诊断模型幻觉(Hallucination)
当gen_ai.response.finish_reason == "length"且gen_ai.usage.output_tokens接近max_tokens时,大概率发生幻觉。因为LLM在达到长度限制时被迫截断,常以编造事实收尾。我们统计过:某法律咨询Bot中,finish_reason=length的对话,用户二次追问率高达68%(正常为22%)。解决方案不是增加max_tokens,而是优化prompt——加入“若不确定请回答‘我不知道’”的约束,使output_tokens稳定在300-500区间,幻觉率下降至9%。
识别Prompt工程缺陷
对比相同业务场景下不同prompt的token分布:
- Prompt A(直白指令):“总结以下合同条款” →
input_tokens=128,output_tokens=89 - Prompt B(结构化指令):“请用JSON格式输出:{summary: string, key_clauses: array}” →
input_tokens=187,output_tokens=156表面看B成本更高,但B的输出100%可被下游系统解析,而A的输出需额外NLP清洗。单位有效信息token成本(output_tokens / JSON字段数)B仅为0.8,A为3.2。这才是真正的效率指标。
暴露数据管道瓶颈
在RAG流程中,gen_ai.usage.input_tokens由三部分构成:system prompt(固定)、user prompt(变量)、retrieved documents(变量)。若某天retrieved_documents部分token激增,但retrieval.document_count未变,说明向量库返回的文档片段变长——根源可能是文档切片策略未适配新业务(如新增了长篇法规原文)。此时token数据比任何监控指标都早2小时发出预警。
实操心得:我们给每个AI服务定义了“token健康度”公式:
health_score = (1 - abs(actual_output_tokens - expected_output_tokens) / expected_output_tokens) * 100。预期值来自历史同场景P90分位数。当score < 70时,自动触发Prompt Review工单。这套机制让团队从“救火”转向“防火”。
4.2 Token失效的真相:不是技术故障,而是权限治理失控
热搜词中高频出现的token exchange failed: token endpoint returned status 403 forbidden,表面是认证失败,深层是权限治理的溃败。GenAI可观测性在此场景的价值被严重低估:
定位失效根因
传统做法是查日志找403,但GenAI trace能精确到毫秒级:
- 若
gen_ai.token.expiration_time属性存在且早于当前时间 → 纯粹过期,需续签; - 若
gen_ai.token.scope属性缺失或为["read"],但调用需要["read", "write"]→ 权限不足; - 若
gen_ai.token.issuer为https://auth.internal,但API endpoint为https://api.openai.com→ 令牌域不匹配。
我们在一个跨境项目中遇到country报错,trace显示gen_ai.token.claims.country == "CN",而OpenAI API要求country == "US"。根源是前端SDK错误地将用户IP属地写入token claims。没有trace,这个问题会归咎于“网络策略”,实际是前端代码缺陷。
构建Token生命周期看板
在Grafana中创建“Token健康度”看板,核心指标:
token_expiration_minutes_avg:所有活跃token剩余有效期(分钟)均值,低于30分钟触发告警;token_renewal_failure_rate:token续签失败率,持续>5%说明密钥轮换流程有缺陷;token_scope_mismatch_count:权限不匹配次数,关联gen_ai.request.endpoint,定位哪个API调用最常越权。
自动化续签治理
基于trace数据,我们实现了智能续签:
# 当检测到token即将过期(剩余<5分钟),且当前span为chat_completion if span.attributes.get("gen_ai.token.expiration_time", 0) < time.time() + 300: # 触发续签,但仅对高优先级用户 user_tier = get_user_tier(span.attributes.get("user_id")) if user_tier in ["enterprise", "premium"]: new_token = renew_token() span.set_attribute("gen_ai.token.renewed", True) # 注入新token到后续调用 os.environ["OPENAI_API_KEY"] = new_token这套机制让token失效率从12%降至0.3%,且无需用户感知。
4.3 面向未来的扩展:GenAI可观测性与AI Agent自治
当AI应用进化到Agent形态,可观测性必须升级为“自治神经系统”。我们已在生产环境验证的两个前沿方向:
Agent决策路径可视化
Agent的tool_calls不再是简单函数,而是带条件分支的决策树。GenAI规范的tool_call实体支持嵌套:
Span: agent_orchestration ├── Span: tool_call (name="search_contracts") │ └── Span: chat_completion (model="gpt-3.5-turbo") # 生成检索query ├── Span: tool_call (name="analyze_clause") │ └── Span: chat_completion (model="claude-3-haiku") # 分析条款 └── Span: tool_call (name="draft_response") └── Span: chat_completion (model="gpt-4-turbo") # 生成终稿通过gen_ai.tool.name和gen_ai.tool.parameters,可在Jaeger中生成交互式决策图:点击search_contracts节点,查看其生成的query;点击analyze_clause,查看其输入的条款原文。这不再是日志,而是Agent的“思维过程录像”。
成本-效果帕累托优化
最终极的治理,是让AI自动选择最优成本路径。我们训练了一个轻量级回归模型,输入为:
gen_ai.request.modelgen_ai.usage.input_tokensgen_ai.response.finish_reasonlatency_msuser_feedback_score(用户点赞/点踩)
输出为cost_effectiveness_score。当新请求到达时,Agent并行调用多个模型(gpt-3.5、claude-haiku、本地Llama3),实时计算各路径的cost_effectiveness_score,选择得分最高者返回结果。上线后,单位token产生的用户满意度提升2.3倍,成本反而下降17%——因为模型选择从“拍脑袋”变为“数据驱动”。
最后分享一个小技巧:在所有GenAI span中强制注入
gen_ai.environment(如production、staging),并在Collector中用attributes处理器添加gen_ai.version(从Git tag读取)。这样在Jaeger中搜索时,可精准对比v1.2.0和v1.3.0版本间gen_ai.usage.output_tokens的差异,让每一次模型升级的效果可量化、可归因。这才是工程师该有的确定性。