输出质量回答“结果好不好”,系统健康度回答“服务是否稳定、及时且成本可控”。这两类评估应一起运行:正确但经常超时的服务难以使用,响应很快却频繁降级的服务也不能仅凭延迟达标就判定健康。
先定义请求成功,再计算延迟
普通 HTTP 请求不能只看状态码;流式响应还需要检查协议是否正常结束、是否返回错误事件、最终结果是否符合约定。列表查询成功也不代表生成、检索和工具调用链路正常。
一条请求记录至少应包含请求 ID、任务类型、开始与结束时间、成功状态、错误类型、降级原因和模型版本。失败与取消请求应分别统计,不能因为只分析成功样本而从报告中消失。
TTFT 与完整响应耗时分别反映什么
TTFT 是从约定的请求起点到第一个有效生成内容到达的时间;完整响应耗时则从同一起点计到任务正常结束。连接建立、心跳和空事件不应被当作首 Token。对流式应用,两项指标共同决定用户感受。
测量耗时宜使用单调时钟,例如 Python 的 time.perf_counter()。同时明确测量位置:浏览器端、网关端和模型调用端的耗时范围不同,不能直接互相替换。
import time
start = time.perf_counter()
first_content_at = None
# 以下为事件处理示意,event 由实际协议解析器提供。
# 收到第一个非空内容事件时:
# first_content_at = time.perf_counter()
# 收到合法完成事件时:
# completed_at = time.perf_counter()
# ttft_ms = (first_content_at - start) * 1000
# total_ms = (completed_at - start) * 1000
服务端发送的事件名即使叫 token,一个事件也可能携带多个 Token 或只包含一段字符。事件数量不能用作模型 Token 消耗,更不能直接参与计费。
分位数必须说明算法与样本范围
P50 是中位位置的延迟,P95 描述尾部体验。平均值不是 P50。不同分位数算法可能在小样本上得到不同结果,因此报告需要固定算法、样本量和观察窗口。
from math import ceil, isfinite
def nearest_rank(values, p):
if not values or not 0 < p <= 1:
raise ValueError("invalid sample or percentile")
ordered = sorted(float(v) for v in values)
if not all(isfinite(v) and v >= 0 for v in ordered):
raise ValueError("invalid latency")
return ordered[ceil(p * len(ordered)) - 1]
samples = [4231, 3812, 5120, 3677, 4389]
print(nearest_rank(samples, 0.50)) # 4231
print(nearest_rank(samples, 0.95)) # 5120
这 5 个数只是计算示例。此时 P95 接近最大值,不能代表稳定的线上尾延迟。少量顺序请求适合冒烟检查;性能评估还应覆盖并发、冷启动、缓存命中与未命中、输入长度及任务类型,并报告足够长的观察窗口。
成功请求延迟应与失败率并排展示,另列超时和取消情况。否则某些最慢请求超时后被排除,P95 反而可能看起来更好。
降级率要有明确分母与原因
请求级降级率可以定义为:观察窗口内至少触发一次降级的合格请求数,除以该窗口全部合格请求数。一条请求多次降级,在请求级口径中只计一次;如果统计调用级口径,需要另列分母。
Fallback 率 = 触发过降级的请求数 / 纳入统计的请求数
事件记录应区分模型超时、检索失败、重排不可用、限流和主动策略切换。正常业务路由不应与故障降级混为一谈。降级成功也可能牺牲回答质量,最好单独抽查降级样本。
如果系统尚未记录 fallback_used 与原因,就只能说明缺少数据,不能报告降级率为零。
Token 与费用来自真实调用记录
优先使用模型服务返回的 usage 信息,并区分输入、输出、缓存等计费类别。一条 Agent 请求可能包含改写、生成、裁判和重试等多次模型调用;请求成本应汇总所有相关调用,同时防止重复采集造成重复计数。
Langfuse 支持使用调用的用量与成本信息进行追踪;各 usage_details 类别应避免重叠计数。具体接入方式以所用 SDK 版本为准,参见 Langfuse Token 与成本追踪文档。
单次请求模型成本
= Σ 各调用、各计费类别的(用量 × 对应单价)
总任务成本
= 模型成本 + 检索/搜索等外部服务成本
不要在监控脚本里长期写死旧价格。价格配置应包含供应商、模型版本、币种、生效日期和计价单位。缺失 usage 应单列缺失比例;如使用估算值,也要与供应商实际用量区分。
历史查询需要真正覆盖观察窗口
“最近 50 条 Trace”与“最近 24 小时”不是同一口径。统计指定时间范围时,要显式过滤开始、结束时间并完整分页,记录总请求量和缺失字段数量。日志中没有报错,也不等于所有请求都已被完整采集。
按任务建立告警与排查路径
| 异常 | 优先排查 |
|---|---|
| TTFT 上升 | 排队、检索、预处理与模型首响应 |
| 总耗时上升,TTFT 稳定 | 输出长度、生成速度与后处理 |
| 降级率上升 | 供应商错误、限流及依赖服务状态 |
| Token 激增 | 历史上下文、检索片段、重试与调用次数 |
| 列表接口正常,生成失败 | 真实业务链路、工具调用与完成事件 |
阈值应根据任务复杂度、用户等待容忍度和历史基线制定。问答、简历分析与单轮面试不宜共用同一个延迟目标。Token 增加也不能直接证明提示词泄露,应先检查上下文和调用结构。
一份有用的健康报告应同时展示成功率、延迟分布、降级率、成本与数据完整性,并明确观察窗口和样本量。指标不足时标记“待确认”,比用一个“系统健康”标签掩盖未知更有价值。