生产 LLM 应用的耗时分析:用 Langfuse 拆解各段时延

生产 LLM 应用的耗时分析:用 Langfuse 拆解各段时延

导读:这篇文章演示如何用 Langfuse 对生产 LLM 应用做耗时拆解:按端分层、全池随机抽样 80 条 trace,用"独占耗时"口径(父节点时长减去子节点)把 LLM、工具、Skill、框架规划各段分别量化。核心结论是大模型生成耗时占全链路 67.6%~95.9%,输入长度与耗时强正相关(r≈0.71)、多轮上下文累积让成本加速膨胀,超长尾多来自模型服务端抖动。适合做 LLM 应用性能优化与可观测性建设的工程师,采样与归因方法可复制到任何应用。

分类:AI评测 | 标签:LLM、可观测性、Langfuse、性能分析

数据说明:本文数值来自一次真实的生产耗时分析。为保护业务数据,文中数据均为「数据已脱敏示意」——仅保留数量级与相对关系,精确值已做模糊化处理。

为什么要做生产耗时分析

应用上线后总能收到"转圈太久""回答很慢"的反馈。压测和单测都做了,为什么线上还是慢?因为没有哪条链路能靠想象拆清楚。

亲手做一次生产耗时分析之前,先想清楚动机:

  • 压测覆盖不到真实形态。 真实用户的输入长度分布、多轮对话里的上下文累积、并发排队、模型服务端自身的时延抖动,只有生产流量能暴露。
  • 用户感知的是整条链路的端到端耗时,而问题往往藏在链条的某一环。 一次回答慢,可能是多轮 LLM 串行、某个工具调用超时、Skill 嵌套过深、也可能是模型服务端抖动,凭经验猜很容易"按下葫芦浮起瓢"。
  • 优化要先量化。 每一段各占多少时间?瓶颈在哪一段?砍哪里性价比最高?——这些都要有数据支撑。

本文用开源 LLM 可观测性平台 Langfuse,对自研 LLM 应用做了一次系统的生产耗时拆解,回答三个问题:各段各占多少时间、耗时从哪来、先优化哪里。整条思路(采样 → 口径 → 拆解 → 归因 → 优化)可以复制到任何 LLM 应用上。

采样与口径:独占耗时才是「该环节自身的耗时」

采样:在线全池随机抽样,别漏掉长尾

分析对象是自研 LLM 应用在生产环境 Langfuse 实例(地址已省略)上近 4 天的 trace(窗口起点之前无数据)。采样方法:

  • 按端分层。 应用按调用形态分为四个"端",每端代表一类用户角色与链路特征。
  • 全池随机。 先用 6 小时窗口分页枚举出全部 trace(约 4500 条),在完整候选池内随机抽样,再剔除含 level=ERROR 的 trace——只看成功路径的耗时形态。
  • 每端固定抽样 20 条非 error trace,共 80 条。

这里有一个关键点:抽样对象是"完整候选池",而不是"看起来正常的 trace"。 如果只挑快的样本,长尾热点永远不会进入视野。耗时分析的目的是找最差的路径,样本里必须包含它们。

端 识别方式 候选池 抽样数
用户端 chat_role=customer 1489 20
员工端 chat_role=staff 1767 20
服务人员端 chat_role=worker 718 20
外部模型渠道 metadata.channel=qwen 337 20

补充说明:四个端流量差异不小(候选池 337~1767),本例按端等量抽样以保证每种形态都有覆盖;若更看重整体分布,可改用按流量比例的加权抽样,两种口径可以互为校验。

口径:先算整段,再减掉子节点——独占耗时

这是全部分析中最核心的一个认知点:

一个节点的耗时不能直接用它的 startTime ~ endTime 来算,因为那包含它所有子节点的时长。当前节点自身耗时 = startTime ~ endTime 的整段时长 - 各子节点时长。

原因很简单:Langfuse 的观测树里,父节点(比如一次 Agent 运行)会"包住"子节点(比如其中的 LLM 生成、工具调用、子 Agent 链)。直接把父节点时长当成它自己的耗时,会把子节点的时间重复计入,而且层级越深误差越大——这就是"独占耗时(exclusive duration)"口径。

本次拆解的完整口径定义:

  • 独占时长:观测节点的 startTime ~ endTime 总长,减去其所有子观测的时长;各分桶之和 = 链路总耗时。
  • Skill run:观测树中 skill 展示名对应的子 Agent CHAIN(含其内部全部子观测)。
  • sub_skill:在 skill run 内部工具分发观测上标记 sub_skill_slug,把所在 skill run 的整段时长归属到对应子 skill。

不统一这个口径,LLM、TOOL、Skill、框架规划各自的耗时就没法相加、没法横向比较。很多团队"trace 看了但没结论",常见原因就在这里。

怎么读:用 P90/P99 与平均的差距判断有没有热点

平均耗时对长尾很不敏感:10 个 1s 的请求加 1 个 100s 的请求,平均只有 10s,看起来"还行",但其实是 9% 的用户在长时间等待。判断标准很简单:

  • 平均 ≈ 中位数 ≈ P95:分布健康,瓶颈是"普遍慢",优化要整体提效;
  • P95 远大于平均与中位数:存在明显热点与长尾,优先抓最慢的那条 trace 逐段拆分,通常能定位到具体环节。

下面所有数据,都会用这个视角来读。

各段拆解结果(脱敏示意)

四端总耗时:谁最慢、慢在哪

端 平均 中位数 P95
用户端 40.0s 25.3s 119.0s
员工端 26.2s 16.3s 92.2s
服务人员端 8.2s 2.0s 64.4s
外部模型渠道 10.4s 3.8s 68.7s

两点解读:

  • 用户端最慢(平均 40s / P95 119s):以多轮 LLM 串行(平均 3.7 次)加 Skill 嵌套为主;中位数 25.3s 说明"普遍慢"与"长尾更慢"并存。
  • 服务人员端与外部模型渠道约 8~10s:以单次 LLM 调用为主,链路形态简单得多。

各端调用次数印证了上面的判断:

端 平均 LLM 调用次数 平均工具调用次数
用户端 3.7 6.8
员工端 3.3 2.6
服务人员端 1.6 1.6
外部模型渠道 1.9 1.2

步骤拆分:LLM / TOOL / Skill 各占多少

端 大模型(LLM) 工具(TOOL) Skill 子 Agent 框架/规划/间隙
用户端 36.5s(91.3%) 0.8s(2.1%) 1.6s(4.0%) 1.0s(2.6%)
员工端 17.7s(67.6%) 5.2s(20.0%) 0.2s(0.6%) 3.1s(11.8%)
服务人员端 7.7s(94.3%) 0.3s(3.4%) 0.0s(0.0%) 0.4s(4.4%)
外部模型渠道 9.9s(95.9%) 0.4s(3.6%) 0.0s(0.0%) 0.1s(0.5%)

口径:LLM = GENERATION 独占时长;TOOL = 工具调用独占;Skill = Skill 链独占;框架/规划 = 其余 CHAIN/AGENT 独占(模型节点预处理、中间件、节点间间隙)。

核心结论:大模型生成耗时是全链路绝对主导——四个端 LLM 占比 67.6%~95.9%。工具与框架开销整体很小(唯一例外是员工端工具占 20%)。换句话说,"省框架代码、省工具调用"不是主要优化空间,真正的杠杆在 LLM 本身:调用次数、上下文长度、模型时延。

Skill 层:三个智能体的长尾差异

Skill 运行次数 总耗时 平均 P50 P95 最长 慢(>10s) 极慢(>30s)
用户端智能体 154 1443s 9.37s 0.17s 64.96s 114.4s 34 18
服务人员智能体 36 240s 6.66s 0.31s 59.89s 59.9s 6 4
员工助手 48 96s 2.01s 0.25s 2.20s 72.0s 1 1

(来源:80 条抽样共 238 次 Skill 运行。)

  • 用户端智能体长尾最严重:P50 只有 0.17s,P95 却高达 64.96s、最长 114.4s——中位数几乎为 0、P95 却接近一分钟,典型的"少数调用拖垮整体体验"。
  • 服务人员智能体 P95 59.9s,同样需要关注。
  • 员工助手整体健康(P95 2.2s),仅个别运行最长到 72s。

子 Skill:把长尾定位到具体名称

sub_skill 统计(slug 已替换为中性示例名,数据已脱敏示意):

子 Skill 触发次数 总耗时 平均 P95 最长 所属端
服务人员检索 17 773s 45.4s 114.4s 114.4s 用户端×17
信用查询 6 84s 14.0s 14.4s 14.4s 用户端×6
意向客户推荐 6 542s 90.3s 114.0s 114.0s 用户端×6
意向客户档案推荐 5 174s 34.9s 45.8s 45.8s 用户端×5
薪酬市场顾问 4 155s 38.7s 114.4s 114.4s 用户端×4
合同条款顾问 3 142s 47.5s 114.4s 114.4s 用户端×3
服务人员档案推荐 3 180s 59.9s 59.9s 59.9s 服务人员端×3
订单列表 2 85s 42.4s 45.8s 45.8s 用户端×2
H5 助手 2 23s 11.4s 17.6s 17.6s 服务人员端×2
接单 2 68s 33.8s 33.8s 33.8s 服务人员端×2
售后 1 39s 39.1s 39.1s 39.1s 用户端×1
优惠券营销 1 39s 39.1s 39.1s 39.1s 用户端×1
服务履约互动 1 16s 16.4s 16.4s 16.4s 用户端×1
AI 智联 1 114s 114.4s 114.4s 114.4s 用户端×1
会员体系 1 114s 114.4s 114.4s 114.4s 用户端×1
服务人员入职 1 18s 17.6s 17.6s 17.6s 服务人员端×1

TOP 长尾(建议优先专项):

  • 服务人员检索:17 次 / 平均 45.4s / 最长 114s;
  • 意向客户推荐:6 次 / 平均 90.3s;
  • 服务人员档案推荐:3 次 / 平均 59.9s;
  • 薪酬市场顾问:4 次 / 平均 38.7s。

注意:子 Skill 样本量较小(1~17 次),排序只供参考,正式立项前建议对 TOP 子 Skill 扩大采样复核。

Skill 内部结构:工具密集型 vs LLM 密集型

Skill 运行次数 内部 LLM 内部工具 内部框架/规划
用户端智能体 201 654.3s(86%) 45.8s(6%) 58.7s(8%)
员工助手 62 0.0s(0%) 97.7s(97%) 3.4s(3%)
服务人员智能体 51 113.8s(85%) 16.4s(12%) 3.3s(2%)

两个完全不同的画像:

  • 员工助手是工具密集型(内部工具 97% / LLM 0%):它几乎不"思考",时间全花在调用内部数据接口上,优化方向是检索/查询类 HTTP 工具本身的时延;
  • 用户端智能体与服务人员智能体是 LLM 密集型(85%~86%):时间是模型在"想",优化方向是压缩 Skill 内上下文、减少轮次。

这一步的价值在于:同样的"慢"字,背后的对症方案完全不同——不拆到这一层,很容易把优化资源投错地方。

相关性分析:耗时从哪来

输入长度与耗时:强正相关

对全部 208 次 LLM 生成观测统计:输入长度与 LLM 总耗时 Pearson r≈0.71、Spearman ρ≈0.61(示意值),属于强正相关。

输入长度 平均耗时
0~5k 字符 14.9s
5k~20k 字符 96.1s

输入从 5k 涨到 20k 字符,平均耗时放大约 6 倍。对生产应用来说,输入长度不是"小问题"——多轮对话里每一轮都把历史完整地塞回去,就是拖慢的元凶之一。

多轮累积:上下文随轮次加速膨胀

LLM 调用次数 平均耗时
1 次 2.8s
3 次 14.5s
4 次 50.2s
5 次 62.0s

1 次调用只要 2.8s,5 次就要 62s——后轮携带前轮全部上下文,成本呈加速增长。多轮是功能刚需,但"把历史一字不落带上"不是。

超长尾:模型服务端抖动,不是上下文问题

20s 的超长尾共 12 次,最长 110.2s,全部来自同一个自部署模型,且输入仅 0~6k 字符——这排除了上下文因素,指向模型服务端时延波动(同输入下存在 3.6s~110s 的抖动区间)。

这种抖动单次调用只有几十秒,但在 3~5 轮串行的链路里会被逐轮放大,是用户端 P95=119s 的直接原因之一。

归因小结

  • 普遍慢:输入长度 + 多轮累积(上下文治理可解);
  • 个别极慢:模型服务端长尾抖动(超时/降级/重试可解);
  • 单段与总耗时的关系:LLM 段占 68%~96%,解决 LLM 段的两个根因,就等于解决了全链路七成以上的问题。

结论与优化建议

优先级 方向 依据 建议动作
P0 上下文治理 输入长度 r≈0.71;5k~20k 输入平均 96s;多轮 4 次平均 50s 提示词压缩、会话裁剪、记忆摘要;限制多轮上下文累积
P0 LLM 长尾治理 同一自部署模型同输入下 3.6s~110s 抖动(12 次 >20s) 超时 + 降级:重试、备用模型、流式先行
P0 用户端长链路 平均 40s / P95 119s,多轮 + Skill 嵌套 减少串行轮次、Skill 结果缓存、并行化
P1 员工助手工具链 内部工具占 97%(LLM 0%) 优化检索/查询类 HTTP 工具调用时延
P1 子 Skill 长尾专项 服务人员检索 / 意向客户推荐 / 服务人员档案推荐平均 45~90s 拆分给对应 Skill 负责人专项优化;扩大采样复核

最后一条方法论层面的建议:本次是 80 条抽样的"横截面分析",建议把相关指标(各端 P95、独占耗时分布、输入长度、LLM 时延)沉淀为 Langfuse 上的持续监控看板并接入告警——耗时分析不该是一次性动作,而应该是常驻的"体检"。生产 LLM 应用的性能问题不会消失,但有了这套采样、口径与归因方法,下一次遇到"慢",就能快速定位到具体环节。