首字只要 800ms,用户为什么还是等了 7 秒?
“模型首 Token 延迟已经做到 800ms,为什么用户还说慢?”
因为很多系统把收到第一个 SSE 事件当成“首字”。那个事件可能只有 role、空白符,甚至只是心跳。用户真正需要的是“订单能否退款、要补什么材料、下一步点哪里”。如果答案到第 7 秒才出现,800ms 对体验没有解释力。

性能对象不是一个模型接口,而是一条业务链
以售后助手为例,一次请求可能经历:租户排队、意图路由、订单检索、权限校验、模型生成、退款规则工具、内容安全检查和前端渲染。任一环节的 P99 抬头,都会让用户觉得“AI 卡住了”。
因此,测试报告至少要拆出四个时间:
- queue_ms:请求进入到真正开始执行;
- first_text_ms:首个非空、可见字符出现;
- useful_ms:首次出现可行动结论,例如“可以退款”以及原因;
- complete_ms:结构化结果和全部文本完成。

下面的探针用 httpx 读取流式响应。关键点是过滤空事件,并由业务判定函数识别“有效答案”,而不是把 socket 收到字节当体验完成:
import json, time, httpx
ACTION_FIELDS = {"decision", "reason", "next_step"}
async def probe(payload: dict) -> dict:
start = time.perf_counter()
first_text = useful = None
merged = ""
async with httpx.AsyncClient(timeout=30) as client:
async with client.stream("POST", "http://sut/chat", json=payload) as resp:
resp.raise_for_status()
async for line in resp.aiter_lines():
if not line.startswith("data:"):
continue
event = json.loads(line[5:])
text = event.get("delta", "")
if text.strip() and first_text is None:
first_text = time.perf_counter()
merged += text
state = event.get("business_state", {})
if useful is None and ACTION_FIELDS <= state.keys():
useful = time.perf_counter()
end = time.perf_counter()
ms = lambda t: None if t is None else round((t - start) * 1000)
return {"first_text_ms": ms(first_text),
"useful_ms": ms(useful), "complete_ms": ms(end)}
为什么常规并发脚本会把结果测得太乐观
很多压测脚本是“一个虚拟用户收到完整响应后,再发下一次”。当服务变慢,发压端也跟着变慢,于是进入系统的新请求反而减少。最危险的排队时刻被脚本自动回避,这就是协调遗漏。
对客服高峰这类明确到达率的业务,应采用开放到达模型。下面的调度器每秒固定发起请求,不因上一个请求变慢而停下:
import asyncio, time
async def open_loop(rate_per_sec: int, seconds: int, payload: dict):
tasks, loop = [], asyncio.get_running_loop()
begin = loop.time()
total = rate_per_sec * seconds
for i in range(total):
due = begin + i / rate_per_sec
await asyncio.sleep(max(0, due - loop.time()))
tasks.append(asyncio.create_task(probe(payload)))
return await asyncio.gather(*tasks, return_exceptions=True)
def percentile(values, p):
xs = sorted(v for v in values if v is not None)
return xs[min(len(xs) - 1, int((len(xs) - 1) * p))]

性能门禁必须与答案质量一起看
单纯压低延迟,可能诱导模型更早输出未经工具确认的答案。建议将样本按纯问答、检索、单工具、多工具、超时恢复分桶,分别统计 P50/P95/P99,并同时检查:业务字段完整率、工具成功率、降级正确率、单请求 Token 与外部调用成本。
一个可执行的门禁可以是:纯问答 useful P95 小于 2.5 秒;单工具场景小于 4 秒;工具超时后 1 秒内明确告知用户并提供人工入口;任何场景不得为了抢首字而先承诺后核验。
所以,TTFT 没有过时,只是它只能回答“流什么时候动了”。用户关心的是“什么时候拿到能做决定的信息”。把这两个问题分开,性能测试才真正站到了业务一侧。
- 点赞
- 收藏
- 关注作者
评论(0)