给Agent压测,别瞎造请求,回放线上日志当样本

压测 Agent 这事,我一开始拿一句"你好"循环打几千遍,跑出来 QPS 漂亮得很,结果真上线被真实流量打趴了。问题出在样本——真实用户的问题长短不一、有的触发工具有的不触发、有的命中缓存有的不命中,用一句固定问候压,压的是个假场景。后来我改成回放线上日志,压测结果才跟生产对得上。

为什么固定样本测不准

Agent 的耗时和成本,跟"问题本身"强相关:

  • 短问题模型生成快,长问题(带长文档的 RAG)慢好几倍;

  • 触发工具调用的请求,要多走一次模型推理 + 工具往返,延迟翻倍;

  • 重复问题可能命中缓存,毫秒返回,混进样本会把平均延迟拉得虚低。

你拿单一样本压,要么全是快的、要么全是慢的,压出来的数字没法指导容量规划。

做法:脱敏后回放真实流量

把线上一段时间的请求日志捞出来,脱敏(手机号、姓名这些必须抹掉),做成压测样本池,按真实比例回放。

import json, random, asyncio, time # 1. 从日志导出样本(已脱敏),保留问题文本和它当时的特征 samples = [json.loads(l) for l in open("traffic_sample.jsonl")] # 每条形如 {"msg": "怎么退货", "hit_tool": false, "tokens": 120} async def replay(concurrency=50, duration=60): sem = asyncio.Semaphore(concurrency) latencies, errors = [], 0 end = time.time() + duration async def one(): nonlocal errors async with sem: s = random.choice(samples) # 按真实分布抽 t0 = time.time() try: await call_agent(s["msg"]) latencies.append(time.time() - t0) except Exception: errors += 1 tasks = [] while time.time() < end: tasks.append(asyncio.create_task(one())) await asyncio.sleep(1 / concurrency) await asyncio.gather(*tasks) report(latencies, errors)

我盯的几个指标

别只看平均延迟,平均会骗人。我看这几个:

  1. P95 / P99 延迟:长尾才是用户体验崩的地方。平均 1.2 秒很好看,P99 到了 8 秒就有一批用户在干等。

  2. 首 token 延迟:流式输出场景,用户最在意的是"多久开始出字",不是全部跑完。

  3. 错误率随并发的变化曲线:从 10 并发加到 100,看错误率在哪个点开始陡升,那就是你的容量上限附近。

  4. token 消耗速率:顺手能估出满负载下一小时烧多少 token、多少钱,做预算。

一个反直觉的发现

我压到一定并发,瓶颈居然不在模型 API,而在我自己代码里——RAG 检索那段没做连接池,并发一高数据库连接被占满,请求全堵在检索这步。要不是用真实样本(带 RAG 的请求)回放,纯打问候语根本暴露不出来这个瓶颈。压测的价值一半在数字,一半在逼出这种藏着的坑。

说点不好的

回放线上日志最大的麻烦是脱敏,得认真做,别把用户隐私当压测样本到处传——这条是底线。另外回放是"过去的流量",遇上大促这种没发生过的峰值,历史样本里没有,还得人工构造极端样本补充。压测能测出"现在扛不扛得住已知流量",测不出"没见过的流量",别指望它替你预言未来。

模型推理走的讯飞星辰MaaS 现成 API,压测时按真实样本打过去,它的限流和延迟表现直接反映到我的指标里,我不用自己维护一套推理算力再单独压模型层,省一大块。

你们压 Agent 是造样本还是回放真实流量?P99 卡在哪一步最多?评论区聊聊。