LLM Ops 评测与可观测实战(5):LLM 调用链追踪:从请求到 token 的全链路观测

发布时间:2026/10/10 11:11:49

LLM Ops 评测与可观测实战(5):LLM 调用链追踪:从请求到 token 的全链路观测 问题背景上一篇把坏变更拦在了发布门口但门禁只能保证上线前已知的好挡不住上线后才暴露的怪。场景回到那个客服 RAG 机器人产品经理甩来一条用户反馈——“我问退款进度它答非所问”附带一个会话 ID。接下来的三十分钟里你要回答这条请求当时检索到了哪三份文档rerank 把它们排成了什么顺序两次模型调用各自的输入输出是什么工具查订单接口返回了什么答案如果都在日志里排查是一分钟的事如果只在同事的回忆里就是一天的事。调用链追踪tracing就是把一次请求穿过的所有环节记成一棵树每个环节一个 span带起止时间、父子关系、结构化属性事后按 trace ID 一键还原现场。本篇讲追踪的数据模型与属性规范、流式输出的特殊记账以及一个最费钱的工程决策——trace 采样全采存不下随机采又恰好丢掉最有价值的那部分。核心原理**span 模型时间树 属性袋。**每个 span 携带 trace ID、span ID、父 ID、起止时间戳以及一组键值属性。LLM span 的属性集比传统 RPC 丰富得多模型名与版本参数temperature、top_p、完整 prompt 与补全文本脱敏后、输入/输出 token 消耗、缓存命中情况、停止原因正常结束还是截断检索 span 带查询串、命中片段 ID 与分数工具 span 带调用参数与返回码。跨进程用 W3C trace context 传播 trace ID网关、编排服务、向量库、模型网关打到同一条链上。OpenTelemetry 已为生成式调用制定了语义约定GenAI semantic conventions属性命名尽量对齐它别自造方言——方言的代价是每个看板、每个分析脚本都要重写一遍。**LLM 应用追踪区别于传统追踪的三个点。**第一payload 是主角传统 APM 记时间就够LLM trace 不看输入输出内容等于没追慢往往是小概率事件错才是常态问题这带来隐私与存储的双重压力脱敏和分级存储是前置功课。第二流式输出要记两笔账首 token 时延TTFT决定体感整链完成时延决定成本二者要分别在 span 上打点只看端到端会把模型开始吐字前的等待和吐字过程混成一团。第三非确定性使得复现依赖完整现场prompt 版本号、few-shot 快照、检索文档 ID 列表必须进 span 属性否则拿着输入也拼不回当时的输入。**采样价值密度极不均匀随机抽样是懒惰的公平。**一次 LLM 请求动辄几 KB 的 prompt 与补全文本全量留存等于把用户对话整体镜像一份进自己的数据库。但 trace 的价值服从错误与慢查询优先一个 2.6 秒带工具重试的失败请求信息量顶得上一百条 1.4 秒的成功请求。实践中的混合策略是错误与超阈值的 trace 强制全采、其余按路由/租户分层随机采样兜住统计口径、再对头部采样命中的 trace 做父子完整性修复尾部采样需要在采集端做缓冲与决策这是架构上最贵的一块。实验一把一条请求的 span 树立起来本机以确定性模拟演示固定种子生产环境按 OpenTelemetry SDK 真实打点。importrandom rngrandom.Random(42)deflat(mu,sig):returnmax(1.0,rng.gauss(mu,sig))spans[]defadd(name,parent,start,dur):spans.append({name:name,parent:parent,start:start,dur:dur})returnlen(spans)-1t0.0rootadd(request,-1,0,0)tlat(6,2)iadd(route_classify,root,t,lat(42,9));tspans[i][dur]lat(3,1)tlat(4,2)retradd(retrieval,root,t,0)retr_startt iadd(embed_query,retr,t,lat(35,6));tspans[i][dur]lat(2,1)iadd(vector_search,retr,t,lat(58,12));tspans[i][dur]lat(4,2)iadd(rerank,retr,t,lat(190,40));tspans[i][dur]spans[retr][dur]t-retr_start tlat(3,1)iadd(prompt_assemble,root,t,lat(12,3));tspans[i][dur]lat(5,2)iadd(llm_call(function_pick),root,t,lat(430,90));tspans[i][dur]lat(260,30)iadd(tool:order_api,root,t,lat(310,70));tspans[i][dur]lat(8,3)iadd(llm_call(answer_gen),root,t,lat(1180,220));tspans[i][dur]lat(5,2)iadd(output_guard,root,t,lat(22,5));tspans[i][dur]endtlat(15,4)spans[root][dur]end children{}foridx,spinenumerate(spans):ifsp[parent]0:children.setdefault(sp[parent],[]).append(idx)defshow(idx,depth0):spspans[idx]kidssorted(children.get(idx,[]),keylambdaj:spans[j][start])self_mssp[dur]-sum(spans[j][dur]forjinkids)print(%s%s %.0f ms (自身未记账空隙 %.0f ms)%( *depth,sp[name],sp[dur],self_ms))forjinkids:show(j,depth1)print(trace 树(一次请求, 固定种子42):)foridx,spinenumerate(spans):ifsp[parent]0:show(idx)print()print(总耗时 %.0f ms; 两次 llm_call 合计 %.0f ms, 占 %.0f%%。%(spans[root][dur],sum(sp[dur]forspinspansifsp[name].startswith(llm_call)),sum(sp[dur]forspinspansifsp[name].startswith(llm_call))/spans[root][dur]*100))print(request 层未记账空隙主要来自 function_pick 之后那段 ~260ms:)print(模型输出解析与工具调用往返没有子 span 记账, 属追踪盲区, 应补一个 tool_dispatch span。)运行输出trace 树(一次请求, 固定种子42): request 2654 ms (自身未记账空隙 315 ms) route_classify 40 ms (自身未记账空隙 40 ms) retrieval 282 ms (自身未记账空隙 4 ms) embed_query 34 ms (自身未记账空隙 34 ms) vector_search 62 ms (自身未记账空隙 62 ms) rerank 181 ms (自身未记账空隙 181 ms) prompt_assemble 13 ms (自身未记账空隙 13 ms) llm_call(function_pick) 489 ms (自身未记账空隙 489 ms) tool:order_api 258 ms (自身未记账空隙 258 ms) llm_call(answer_gen) 1234 ms (自身未记账空隙 1234 ms) output_guard 22 ms (自身未记账空隙 22 ms) 总耗时 2654 ms; 两次 llm_call 合计 1723 ms, 占 65%。 request 层未记账空隙主要来自 function_pick 之后那段 ~260ms: 模型输出解析与工具调用往返没有子 span 记账, 属追踪盲区, 应补一个 tool_dispatch span。树上一处自身未记账空隙 315 ms值得盯住request 层比子 span 之和时间多了 315 ms这几乎全在 function_pick 与 order_api 之间——SDK 没有给解析模型输出的工具调用参数并发起 HTTP这段编排逻辑打 span它就变成树上的黑洞。LLM 应用追踪的第一批工单往往不是模型慢而是这类记账盲区网关排队、序列化、重试退避全都藏在父 span 的自身时间里。补 span 的原则凡是跨网络或跨进程的边界必须有名字。实验二分位数统计与 10% 预算下的采样策略importrandomdefpct(vals,p):vsorted(vals)k(len(v)-1)*p/100.0loint(k)himin(lo1,len(v)-1)returnv[lo](v[hi]-v[lo])*(k-lo)rngrandom.Random(77)N5000traces[]# (延迟ms, 是否错误)for_inrange(N):basemax(300,rng.gauss(1600,350))has_retryrng.random()0.04# 工具超时重试ifhas_retry:baserng.uniform(2000,9000)errrng.random()(0.09ifhas_retryelse0.005)traces.append((base,err))lat[dford,_intraces]print(端到端延迟分位: p50%.0f p95%.0f p99%.0f p99.9%.0f (ms)%(pct(lat,50),pct(lat,95),pct(lat,99),pct(lat,99.9)))err_lat[dford,eintracesife]print(错误 trace 共 %d 条, 其中位延迟 %.0f ms; 全体中位延迟 %.0f ms - 错误强烈偏向慢尾%(len(err_lat),pct(err_lat,50),pct(lat,50)))budgetint(0.10*N)random_keeptraces[:budget]# 种子已定, 等价随机抽样 10%tail_keepsorted(traces,keylambdax:-x[0])[:budget]# 按延迟留最慢 10%defcoverage(kept,label):tesum(1for_,eintracesife)kesum(1for_,einkeptife)print(%-14s 保留 %d 条: 覆盖错误 %d/%d (%.0f%%)%(label,len(kept),ke,te,ke/te*100))coverage(random_keep,随机头部采样)coverage(tail_keep,延迟尾部采样)print(结论: 两种 10% 策略都漏掉大量错误 - 生产用错误强制全采 慢尾全采 其余随机的混合策略)运行输出端到端延迟分位: p501629 p952393 p998752 p99.910526 (ms) 错误 trace 共 36 条, 其中位延迟 2032 ms; 全体中位延迟 1629 ms - 错误强烈偏向慢尾 随机头部采样 保留 500 条: 覆盖错误 6/36 (17%) 延迟尾部采样 保留 500 条: 覆盖错误 17/36 (47%) 结论: 两种 10% 策略都漏掉大量错误 - 生产用错误强制全采 慢尾全采 其余随机的混合策略p99 是 p50 的五倍多全部来自带工具重试的慢尾36 条错误里 17 条躺在最慢的 500 条里——尾部采样天然能摸到大半错误但仍有近五成错误是快着出错比如模型秒回但内容错它们会同时骗过头部与尾部采样只能靠出错必留的强制规则或事后回捞应用侧记录错误标记采集端收到标记后把缓冲中的该 trace 提升为全采兜住。统计口径上还要防一个陷阱被采样偏置过的样本不能再直接算分位数——尾部采样保留了所有慢请求你的 p99 会永远等于最大值要么分策略加权还原要么统计类指标改用未采样的 metrics 通道算。工程接入清单按依赖顺序落先在模型网关与编排框架LangGraph、LiteLLM 一类接上自动插桩拿到最粗的树再补业务 span路由分类、护栏检查、工具分发消灭父层空隙然后定义属性规范prompt 版本、检索文档 ID、token 开销、TTFT对齐 OTel GenAI 语义约定最后接采样策略与存储分级——payload 文本热存 7 天、结构化元数据长存排查看板配按用户反馈 ID 直达 trace的入口让客服工单系统带上 trace ID 字段。每一步都要有回退开关追踪是辅助系统不能成为故障源SDK 异常必须静默降级而不是阻塞业务线程。常见陷阱其一prompt 与补全明文裸存trace 库成了全公司隐私等级最高却防护最弱的库脱敏必须在客户端出口前完成服务端事后洗永远慢一步。其二把 span 属性当垃圾桶每条 trace 塞入全部中间文本存储爆炸、查询变慢属性只留定位与复现所需最小集全文进对象存储挂链接。其三流式只记完成时间没有 TTFT 打点用户抱怨卡你却在 p99 里找不到证据——把第一个增量包到达作为独立事件记录。其四跨异步队列断链任务进消息队列后没带 trace context下游开的树与上游认亲失败一棵链碎成三截——所有跨进程边界显式透传 trace ID。其五采样后的分位数直接汇报见实验二数字好看得像 bug。落地清单自动插桩起步模型网关 编排框架先有树再谈细节属性对齐 OTel GenAI 语义约定禁止私有方言prompt 版本必记打 TTFT 与整链完成两笔时间账流式体验才有数据混合采样错误全采 慢尾全采 其余随机metrics 通道另记统计量payload 热存 7 天分级回收工单系统回填 trace ID 实现秒级定位链路能复盘单条请求了但它还是一笔糊涂账这个月到底花了多少钱、钱花在哪条路由哪个提示词版本上没人说得清。下一篇《LLM Ops 评测与可观测实战6Token 成本工程缓存、路由与压缩的预算控制》把成本从月底的惊吓变成每天的仪表盘。参考来源OpenTelemetry GenAI 语义约定https://opentelemetry.io/docs/specs/semconv/gen-ai/OpenTelemetry Traces 概念https://opentelemetry.io/docs/concepts/signals/traces/W3C Trace Context 规范https://www.w3.org/TR/trace-context/WikipediaTracing (software)https://en.wikipedia.org/wiki/Tracing_(software)Langfuse 追踪文档https://langfuse.com/docs¥9.9 付费专栏《Python 自动化接单实战从脚本到第一单》共 10 篇、已完结不再更新一次拿走点此直达
延伸阅读

更多相关文章

2026/10/10 11:11:49

Java性能评估核心指标详解:QPS、TPS、RT与实战排查

1. 面试官问性能指标,真正在考的是这三层东西先还原一个场景。你坐在面试官对面,对方问:"聊聊你对性能评估指标的理解。"很多人第一反应是背定义:QPS是每秒查询数,TPS是每秒事务数,RT是响应时间……

2026/10/10 11:11:49

SWAT模型参数率定太头疼?试试Sobol与PAWN全局敏感性分析对比

搞水文模型的人,大多都有过被参数折腾到怀疑人生的阶段。新拿到一个SWAT(Soil and Water Assessment Tool)模型,光输入文件就十来个,可调参数动辄三四十个——CN2、SOL_AWC、ALPHA_BF、GW_DELAY、ESCO、SURLAG……每个…

2026/10/10 11:11:49

Answer me with HTML 实测:让 AI 用一页图文网页回答复杂问题

Answer me with HTML 实测:让 AI 用一页图文网页回答复杂问题 一、一个所有 Agent 用户都遇到的痛点 你问 Claude Code、Codex 或者 Cursor 一个稍微复杂点的问题——比如"帮我梳理这个仓库的模块关系"、“比较一下 Redis 和 Memcached 的取舍”、“解释…

2026/10/10 12:22:15

Unity3D实现MB903工业HMI仿真:协议解析与UI同步开发指南

简介:Unity3D设计MB903是一款面向游戏开发初学者与进阶学习者的3D冒险类项目实战资源,聚焦游戏设计核心流程——从角色战斗系统、装备收集机制到剧情驱动式关卡推进,帮助开发者掌握Unity引擎在真实项目中的综合应用。资源包为ZIP格式&#xf…

2026/10/10 12:22:15

ASP+Access轻量级政务查询系统搭建与加固指南

简介:本资源是一套基于ASP技术实现的核酸检测报告查询系统完整源码,面向Web开发初学者与中小型政务/医疗信息化项目开发者,解决核酸结果信息在线查询、用户身份核验与报告数据动态展示等实际需求。压缩包共733个文件,以137个JS脚本…

2026/10/10 12:22:15

Spring Boot+微信小程序代驾系统:订单状态机与落地避坑指南

简介:围绕微信小程序代驾系统展开的毕业设计论文文档,适合计算机相关专业学生、Java 后端开发者,以及正在完成 Spring Boot 类毕设项目的读者参考。内容以代驾业务为场景,系统阐述从选题背景、需求分析到系统设计、技术选型、模块…

2026/10/10 7:31:36

Jev+Agent接管浏览器:browser-use实战与jev-ultrafast性能优化

1. 从“Jev”说起:为什么我要把Agent接进浏览器“Jev”这个词最近在圈子里出现的频率越来越高,很多人第一次听到会以为是某个新模型的名字,其实它更像是一种思路——把Jev模型的能力当作底座,通过Agent的方式去接管浏览器&#xf…

2026/10/9 20:15:56

多智能体集群实战:DeepAgents编排、MCP与A2A协议及Skills体系

1. 从"单兵作战"到"集群协同":多智能体编排到底在解决什么问题如果你最近在折腾 Agent 相关的东西,大概率会有一种感觉:单个 Agent 能做的事情,其实很快就摸到天花板了。你给它一个提示词,挂几个工…

2026/10/8 6:05:44

无源低通滤波器设计实战:从RC到LC,手把手教你避开那些坑

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/10 0:04:53

从逻辑门到计算机:数字电路核心原理与全加器搭建实战

如果你拆过一台旧电脑的主板,盯着那些黑乎乎的小芯片看上一会儿,可能会冒出同一个疑问:这堆引脚密集的元件,到底是怎么“变”出那么复杂的应用的?答案并不在某个神秘的部件里,而是在所有芯片内部都在反复使…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑