生产 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 应用的性能问题不会消失,但有了这套采样、口径与归因方法,下一次遇到"慢",就能快速定位到具体环节。