在本地跑 Agent 的时候,你觉得它是诸葛亮:上知天文下知地理,工具调得飞起,甚至还能帮你写周报。
一旦推上线,并发量稍微冲一冲,它立马变成"人工智障":页面转圈转到天荒地老,后台日志满屏通红,504 Gateway Timeout、ReadTimeout、429 Too Many Requests 扎堆开派对,最后服务直接装死。
Agent 是个极其脆弱的"连环套"系统:它不仅要算 Token、查向量库,还要疯狂发 HTTP 请求调工具,最后再让大模型唠嗑。链路里只要有一个环节拉胯,全局就会瞬间上演多米诺骨牌式雪崩。
想要把你的 Agent 从 ICU 里捞出来,先看明白它是怎么在下面这 5 个阶段把自己"作死"的。
案发全链路实况图
每次用户发个消息,Agent 的内部心路历程长这样:
css
[前端用户满怀期待发起提问]
│
▼
【阶段一:Prompt 装配】(翻箱倒柜查历史聊天,算算 Token 超没超标)
│
▼
【阶段二:RAG 知识检索】(把字变成向量,满大库里翻相关资料,拼命重排)
│
▼
【阶段三:LLM 首次思考】(大模型抓耳挠腮:我是该直接答呢,还是调个工具?)
│
▼
【阶段四:外部工具执行】(跑去查业务库、抓外部网页、调第三方 API)
│
▼
【阶段五:LLM 汇总输出】(把工具吐出的几万字废话缩水成答案,端给用户)
阶段一:Prompt 装配 ------ 还没出发,自己先在家里累趴了
很多同学以为把系统提示词和用户输入用 f"{sys_prompt} + {user_input}" 拼一下只需 0.1 毫秒。但在生产环境里,很多人硬是把这一步玩成了 CPU 熔断现场。
1. 它怎么掉链子的?
-
数据库连环夺命连环 Call:每次组装上下文,都老老实实去 MySQL 里现翻最近 50 条聊天记录。并发一来,数据库连接池瞬间被借光,新来的请求只能在门口绝望排队,光是"借车"就借了 5 秒。
-
分词器(tiktoken)把 CPU 烧冒烟 :为了不让 Prompt 超出模型窗口,很多人写个
while循环:拿tiktoken算一遍,砍掉一条历史,再算一遍,再砍一条......分词这玩意儿是极耗 CPU 的!并发稍微多几十个,服务器 CPU 直接飙到 100%,整个进程的事件循环彻底卡死。
2. 抢救指南
-
别拿尺子一毫米一毫米量布 :截断历史记录别再死板地用分词器狂算了!直接按中文字符数乘以 1.3∼1.5 粗略估算长度,切个大概,最后送入模型前整体校验一次即可。
-
上缓存,别硬刚磁盘和数据库:会话历史塞进 Redis,静态 Prompt 模板在服务启动时常驻内存,别每次请求都现读一次本地文件。
阶段二:知识召回(RAG)------ 搜着搜着,向量库先崩了
RAG 是 Agent 的"外挂大脑",但这个大脑在高并发时很容易脑溢血。
1. 它怎么掉链子的?
-
Embedding API 先跪为敬 :并发一来,所有文本排队调云端接口转向量,OpenAI 直接甩你一脸
429 Too Many Requests;要是自己显卡上部署开源模型,显存直接被打穿,排队等到天荒地老。 -
向量库在死亡计算:向量检索本质是在高维空间里算几何距离。并发量一冲,向量库服务器 CPU 瞬间拉满,延迟从 20ms 一路狂飙到 20 秒。
-
贪多嚼不烂的 Rerank:辛辛苦苦搜出前 100 条切片,反手塞给高延迟的 Cross-Encoder 模型做重排。模型表示:"你在为难我胖虎",直接卡死在路上。
2. 抢救指南
-
偷懒是程序员的美德(上缓存) :高频问题别老去翻向量库。用 Redis 做精确哈希缓存,再加个语义缓存(比如 GPTCache),只要相似度达到 95% 以上,直接复用上次搜出的结果。
-
分批筛选,别全量硬抗 :先用 BM25 关键词把候选切片压到 20 条以内,再去跑重排;同时调低向量库的检索精度参数(比如 HNSW 的
efSearch),牺牲 1% 的精度换来 QPS 翻几倍,血赚。
阶段三:LLM 首次思考 ------ 共享池里的"嘈杂邻居"
到了大模型决定"该干嘛"的环节。这个环节的坑,往往不是你的代码写得烂,而是你根本不知道厂商在怎么坑你。
1. 它怎么掉链子的?
-
公有云共享池的"抢菜风暴" :你买的是几分钱一千 Token 的公共 API,相当于和千军万马挤一个公共服务器。隔壁友商突然来了一波大流量,厂商集群排队排成长龙,你的请求首字返回延迟(TTFT)从 800ms 狂飙到 30 秒。
-
给大模型塞了本字典:一口气注册了几十个 Tool Schema,每轮思考光是把这些工具说明读完,模型就得耗尽大量算力,生成速度慢得像老牛拉破车。
2. 抢救指南
-
狡兔三窟,准备备胎:上 LiteLLM 或 OneAPI 网关,手里多攥几个备用 Key 轮询。主选模型如果卡在 5 秒内没吐字或者报 429,程序立马无缝降级切到备用厂商或自建模型。
-
工具断舍离(Tool Pruning) :别一次性把所有工具丢给它。先用个轻量规则识别当前意图,只挂载 3~5 个相关工具。
阶段四:外部工具执行 ------ 队员掉线,队长陪葬
大模型思考完毕,下发命令让 Agent 去调用现实世界的工具。这里往往是车祸重灾区。
1. 它怎么掉链子的?
-
老实人没有设超时:调某个外部接口没配 Socket Timeout(默认是永不超时或几十秒)。结果人家服务器挂起,你的 Agent 协程就傻傻坐在那儿一直等,把所有并发连接数全占满。
-
ReAct 死循环(鬼打墙) :工具报错了,模型不信邪:"一定是姿势不对,我再调一次";工具又报错,模型继续重试......几轮下来,耗光所有最大步数和超时上限。
-
排排坐吃果果(串行调用) :明明查天气、查日历、查库存彼此毫无关联,代码里却写成了串行
await,硬是把 3 秒的事干成了 9 秒。
2. 抢救指南
-
铁面无私配硬超时:每个工具单独限制时间(内部接口 2 秒,爬虫 5 秒)。超了直接当场掐断,把"调用超时"当作错误文本扔回给模型:"超时了,别死等了,换个思路或者告诉用户"。
-
异步并行,直接起飞 :无依赖的工具,直接用
Promise.all(Node.js)或asyncio.gather(Python)并发去抓。 -
死循环直接击毙:限制最大执行步数最多 3~5 步。检测到两次拿相同参数调同一个工具且报错,强制熔断,直接让大模型基于现有信息总结。
阶段五:LLM 汇总输出 ------ 网关苦等 60 秒,最终含泪报 504
工具把数据拿回来了,大模型准备组织语言输出最终大结局。这时候千万别在终点线前摔倒。
1. 它怎么掉链子的?
-
憋大招把网关憋死了 :没开流式输出(Streaming),Agent 在后台默默拼长篇大论。外层的 Nginx 或负载均衡器等了 60 秒没见到任何回包动静,以为后端死掉了,手起刀落直接返回
504 Gateway Timeout。 -
工具吐了一地垃圾信息:外部接口返回了几万字的完整 HTML 或庞大 JSON,代码不经清洗全喂给模型二次整理。上下文瞬间爆炸,模型光是吞下这些内容就得喘粗气,生成速度暴跌。
-
用户走了,你还在打空气:用户等得不耐烦直接关了网页,但后端根本没监听取消信号(Cancellation),服务器依然在哼哧哼哧调大模型烧钱,白白占着并发坑位。
2. 抢救指南
-
必须开流式(SSE / WebSocket) :别憋大招!大模型吐出一个字就往前端推一个字。只要网络通道一直有数据在跳动,网关就不会判你超时。
-
进嘴前先洗菜:工具拿回来的数据,用几行代码把核心字段抠出来,冗余的 HTML 标签和多余 JSON 字段统统丢掉再喂给模型。
-
透传断连信号 :接入
AbortController或框架的取消上下文。前端一断开,后台立马干脆利落地把模型任务掐断,把珍贵的并发资源留给下一个活人。
终极保命符:耗时太长?赶紧改成异步排队!
如果你的 Agent 本身就背负着深度调研、写代码、甚至写长文等重型任务,单次耗时注定突破 30 秒,就别再执着用常规的 HTTP 请求死撑了。
赶紧改造为异步任务流模式 : 用户点提交 → 后端塞进消息队列(如 Redis Stream / BullMQ) → 瞬间给前端返回一个 Task ID → 后台分布式 Worker 集群慢慢算 → 前端通过 SSE 拿着 Task ID 订阅打字机效果。
这样一来,哪怕并发瞬间涌入一万人,大家都在队列里乖乖排队,没人会再吃到 504 超时大礼包。