AI 应用怎么观测:首字延迟、总耗时与成本
拆分检索、模型和工具耗时,正确计算延迟分位数,用最小示例建立请求级观测。
区分首字延迟与完成时间,记录足够定位问题且不包含用户正文的指标。
用户感觉“快”,可能是更早看到第一个字,也可能是更早拿到完整结果。首字延迟与完成时间是两个不同指标,流式展示不会自动缩短模型总耗时。
先把请求经过的步骤看清楚,再决定优化检索、模型、工具还是界面。
沿请求链路拆开观察
一次任务可能包含排队、检索、模型请求、工具执行和结果校验。用统一请求标识关联这些阶段,保留每个阶段的开始、结束和状态。
Trace 描述请求路径,span 表示一段工作。并行阶段的耗时不能简单相加当作总延迟,因为它们可能重叠。
首字延迟应明确测量位置:服务端收到请求到第一个可显示输出,还是浏览器发起请求到真正显示。不要混合两种口径。
记录定位问题所需的信息
保留模型版本、错误类别、重试次数、输入输出 token 数和工具类型。默认不要把完整提示词、用户正文、凭据或资料内容写进日志。
对于需要查看正文的失败,使用独立受控样本流程。指标和日志的目的,是定位问题,而不是收集所有内容。
用小样本练习分位数
下面使用最近秩法计算分位数,全部数字为教学数据。五个样本只能说明算法,不能代表线上 P95 的稳定表现。
from math import ceil
def percentile(values, p):
if not values or not 0 < p <= 1:
raise ValueError("samples and valid percentile required")
ordered = sorted(values)
return ordered[ceil(p * len(ordered)) - 1]
latencies = [100, 120, 200, 250, 600]
assert percentile(latencies, 0.5) == 200
assert percentile(latencies, 0.95) == 600
# Invented rate units, not any service's actual price.
input_tokens, output_tokens = 1200, 200
input_rate, output_rate = 0.2, 0.8
estimated_units = (input_tokens * input_rate + output_tokens * output_rate) / 1000
assert abs(estimated_units - 0.4) < 1e-9
print({"p95_ms": percentile(latencies, 0.95), "cost_units": estimated_units})
真实成本需要使用当前计费规则,还可能涉及缓存、推理、工具调用和服务费用。代码里的 rate 仅演示运算,不能拿来做实际预算。
先定位,再优化
如果检索耗时高,检查索引和查询范围;如果首字慢,区分模型排队、输入长度和前置工具;如果总耗时高,观察输出长度和重复调用。
重试可能改善单次成功率,却增加尾部延迟和成本。缓存可能降低重复计算,但要考虑资料版本与权限,不能跨用户共享不该共享的结果。
改变策略后,同时看质量与延迟。更快但漏掉证据的回答,不是完成了同一个任务的等价优化。
下一步
为一条任务记录首字、完成时间和各阶段 span。固定测量口径,按任务类型观察中位数、尾部延迟与失败率。
每次只调整一个瓶颈,并用原有任务集回归质量。下一篇把这些观测与失败行为放进上线验收。
资料来源与延伸阅读
资料核验:2026-11-10。本文为原创工程讲解,教学示例不代表线上实测。