Agent 高频题 16. 流式输出、延迟、并发:怎么流、慢在哪、并发怎么控
1. Agent 高频题 16. 流式输出、延迟、并发:怎么流、慢在哪、并发怎么控
[!abstract] 速览
流式、延迟、并发优化的是三件事:流式管用户多久看到东西,延迟管一次任务多久做完,并发管同时来很多时扛不扛得住。流式只解决等待,不解决慢;延迟要先拆到每一段再降;并发要在入口处限住。题单里这题是 26 位作者、31 场面试;重读库里记过的牛客链接共 516 篇以后,问到这道题的有 72 场、56 位作者。
百度一场后端一面问:假如 SSE 流式传输只返回了一半的 JSON,要不要执行这个工具?遇到这种不确定的情况,你采取什么策略?
满帮一场 AI 全栈一面顺着候选人的项目往下问:你用 SSE 推模型的输出和节点的进度,还支持断线重连,这是怎么做的?断联了,是重新再推一遍,还是按上次的位置接着推?
字节一场 Agent 二面问到问题改写:改写这一步的模型推理没法直接流式输出给用户,会不会让用户等太久?腾讯一场大模型算法面连着问了三句:怎么区分端到端延迟来自工具调用、数据库查询,还是模型推理?要不要埋点?单 Agent 场景下可以从哪些方向优化端到端时延?
这是 Agent 高频面试题单第 16 题的详解。题单里这题是 26 位作者、31 场面试。这次和第 12 到 14 题一样,重读库里记过的牛客链接,共 516 篇。按 SSE、流式、WebSocket、首 token、断线、重连、延迟这些词找出 97 篇,按并发、异步、限流、耗时、吞吐这类词又找出 238 篇。滤掉线程池参数、Redis、MySQL 这类八股以后逐条读过,再用心跳、进度、P99、降级这类词扫了一轮补漏。去掉的有文件的分片上传和断点续传、延迟双删、延迟队列、游戏的延迟渲染,还有只问线程池参数或协程原理的语言题;MCP 的 stdio 和 Streamable HTTP 归第 18 题。剩下问到这道题的有 72 场、56 位作者。追问分十一个方向(按作者数,一场可以算进几类):
问得最多的是延迟,其次是 SSE 本身。前端岗也问这题,72 场里有 9 场是前端岗的面经,问的多是 SSE、断线重连和渲染。
下面先把三件事分开,然后沿着一次流式回答经过的路线讲:模型服务商怎么流给你,你推什么给前端,SSE 和 WebSocket 怎么选,断了怎么办,前端怎么渲染,流到一半的工具调用怎么处理;再讲延迟怎么拆、怎么降,速度和准确、成本怎么取舍,长耗时的步骤怎么异步;最后是并发,从工具并行一直讲到推理服务的批处理。工具并行和依赖第 11 题讲过,长耗时工具怎么异步是第 11 题第三节,模型调用的限流和重试是第 11 题第九节,并发下的预算是第 12 题第七节,缓存的原理在第 12 题,MCP 的传输协议第 18 题、结构化输出第 22 题单讲,这里只讲它们和流式、延迟、并发有关的部分。
1.1. 先把三件事分开
1.1.1. 三个问题,各看各的指标
题单把流式、延迟、并发归成了一题,它们优化的是三件事:
几个词先说准。TTFT 是从发出请求到收到第一个输出 token 的时间。之后相邻两个 token 的间隔叫 TPOT(time per output token),换个说法就是每秒输出多少 token,常写作 OTPS 或 TPS。一次模型调用的耗时,大致是 TTFT 加上输出 token 数乘以 TPOT。
放到 Agent 上,一次任务通常要调好几次模型,中间还有工具。端到端耗时大致是每一轮模型调用的时间加起来,再加上工具的耗时(并行的那一批取最慢的),再加上排队和编排的开销。这是估算用的写法,第七节拆延迟时就按它拆。
1.1.2. 流式不让生成变快
流式让模型边生成边发,生成本身没有变快。Claude 的降延迟文档说,它提升的是感知到的响应速度。OpenAI 的延迟优化指南把流式放在「让用户少等」这一条下面,说它是最有效的办法,能把等待缩到一秒以内。指南还补了一句:把应用和用户放在一起看,流式和分块处理确实缩短了总时间,因为用户可以边生成边读,读完得更早。
[!note] 两个时间别混
模型生成完要多久,流式不改变它;用户多久能开始读、什么时候读完,流式能提前。前一个要靠第七节那些办法。
Agent 的一次回答要调好几次模型,规划、改写、选工具这几次调用没有给用户看的正文,没东西可流。字节二面那个问题就出在这里,第七节第 4 小节单独讲。
面试常问:有流式输出吗?平时调模型用的是 stream 还是非 stream?项目响应时间大概多少?整轮耗时大概多久?
1.2. 流式输出是怎么实现的
1.2.1. 一次流式回答经过三段
一次流式回答从模型到屏幕,经过三段:
- 模型服务商到你的后端。Claude 和 OpenAI 的 API 用的都是 SSE:请求里设
stream: true,响应是一条迟迟不结束的 HTTP 响应,服务端一个事件一个事件地往里写。 - 你的后端到前端。可以再转成一条 SSE,也可以用 WebSocket。这一段的事件由你自己设计。
- 前端收到事件,解析、渲染。
字节一场客户端一面问:Agent 请求是怎么做到流式返回的,协议层用的是 HTTP 还是别的协议?按这三段答:前两段的传输都可以是 HTTP 上的 SSE(第二段也可以换成 WebSocket),原理是响应不一次发完,服务端边生成边写,客户端边收边解析。
以 Claude 为例,第一段的事件顺序是:以 message_start 开头;每个内容块一组 content_block_start、若干 content_block_delta、content_block_stop;然后是 message_delta,带停止原因和用量;最后 message_stop。中间可能夹着 ping。文档提醒了三件事:message_delta 里的 token 数是累计值;高峰时流里可能来一个 error 事件,比如 overloaded_error,对应非流式时的 529;以后可能加新的事件类型,代码要能跳过不认识的。
第一条是说,几次 message_delta 的数不能加起来。最终用量要到流快结束时才有,亚信那道题问的就是这个:并发时只能先预占预算,第 12 题讲过。第二条决定了错误处理的写法:HTTP 状态码早已返回 200,后面的错误只能从事件里认出来。
有时候必须用流式。Claude 的错误文档说,有的网络会断掉长时间没有数据的空闲连接;SDK 会检查非流式请求,预计超过 10 分钟的直接报错。所以 max_tokens 设得很大时,SDK 要求用流式。不需要边收边显示的,也可以走流式,让 SDK 在内部攒成完整的消息再返回。
1.2.2. 你的后端推什么
满帮那场,候选人用 SSE 推的是模型的输出和节点处理的进度;字节一场全栈二面问的是 Agent 可视化工作台的 Event Trace 为什么用 SSE。第二段推的不只是 token:
每个事件带上这次运行的 ID 和一个递增的序号,断线续传、去重、排序都靠它,第四节会用到。结束事件要单独有一个:没收到它,前端就不能认定回答已经完整。这张表是我的设计,不是哪家的标准。
1.2.3. 中间隔着代理时
后端到前端之间常隔着 nginx 或网关。nginx 默认开着响应缓冲(proxy_buffering on),会先把上游的响应收进缓冲区再往下发,流式就变成了一段一段地来。可以在配置里关掉,也可以由后端在响应头里带上 X-Accel-Buffering: no,只对这一个响应关掉缓冲。
另一个是超时。nginx 的 proxy_read_timeout 默认 60 秒,算的是两次读之间的间隔:上游 60 秒没发任何东西,连接就被关掉。推理模型想得久,或者在等一个慢工具,很容易超过这个数。办法是把它调大,同时定时发心跳。SSE 规范自己也提醒:有的老代理会在不长的超时后断开连接,可以每 15 秒左右发一行注释;另一层做的 HTTP 分块也可能影响这个协议的可靠性。
面试常问:Agent 请求是怎么做到流式返回的,原理是什么?流式返回在协议层用的是 HTTP 还是其他协议?具体的流式响应是怎么处理的?项目有没有实现类似 ChatGPT 的流式输出?需要用户交互的 Agent 任务支持流式返回吗?Java 服务和 Python 写的 Agent 之间用什么协议通信?SSE 流式事件的作用是什么?
1.3. SSE 和 WebSocket 怎么选
1.3.1. SSE 是什么
SSE(Server-Sent Events)是 HTML 标准的一部分,用的还是 HTTP:客户端发一个普通的 HTTP 请求,服务端返回 Content-Type: text/event-stream,然后不结束响应,一直往里写。格式是按行写字段:
id: 42
event: delta
data: {"text": "你好"}
: 冒号开头的是注释,可以当心跳
data 是内容,event 是事件类型,id 是事件编号,retry 可以改重连间隔;一个空行表示一个事件结束,冒号开头的行是注释。内容只能是 UTF-8 文本。
浏览器自带的 EventSource 帮你做两件事:连接断了自动重连,间隔由浏览器定,规范说大约几秒,服务端可以用 retry 改;重连时把收到的最后一个 id 放进 Last-Event-ID 请求头带给服务端。服务端返回 204,浏览器就不再重连。
所以「SSE 和普通 HTTP 请求有什么区别」,答案是协议相同,区别在三处:响应不一次结束,客户端边收边解析,以及事件格式和重连约定。「SSE 基于什么协议」:HTTP。
1.3.2. 和 WebSocket 比
SSE 还有一个限制,来自 MDN:不走 HTTP/2 时,SSE 受浏览器连接数的限制,每个浏览器对每个域名只有 6 条,所有标签页共用,Chrome 和 Firefox 都把这个问题标成了不修;走 HTTP/2 时是一条连接上的多路复用,同时的流数由双方协商,默认 100。多会话、多标签页同时流式的产品会碰上它,百度前端那场问「多会话流式管理」,答的时候可以提这一条。
百度一场 Agent 一面有一问:WebSocket 是半双工吗?不是,是全双工,两边可以同时发。
对话场景用 SSE 合适,是因为交互的形状:用户发一条消息,服务端回一段长长的单向输出,下一条消息是下一个 HTTP 请求。SSE 走的是普通 HTTP,现有的鉴权、网关、日志、负载均衡都不用改。
需要 WebSocket 的情况:真正双向的实时交互,比如语音对话,音频持续往上传,用户随时插话;或者在同一条连接上来回很多轮。OpenAI 的 Responses API 现在有一个 WebSocket 模式,就是给工具调用很多的工作流用的:连接一直开着,每一轮只发新增的输入,文档说 20 次以上工具调用的流程,端到端最多快了约 40%。这是你的后端和模型服务商之间那一段。你的前端和后端之间用什么,按前端的交互来定,两段可以不一样。
短轮询最简单,客户端每隔几秒问一次。它适合查一个后台任务的状态(第九节),不适合逐字输出。
1.3.3. 为什么不用原生 EventSource
百度前端那场问:为什么选基于 Fetch 的 SSE,而不用原生 SSE 或 WebSocket?
原生 EventSource 的构造函数只接受一个 URL 和一个「是否带凭证」的选项:只能发 GET,不能加自定义请求头,也不能带请求体。对话请求要把消息、会话 ID 放在 POST 的请求体里,鉴权常用 Authorization 头,这几样它都做不到。所以要发 POST,就得用 fetch,拿到 response.body 这个 ReadableStream,自己按 SSE 格式解析,用 AbortController 取消。
代价是 EventSource 送的两样都没了:自动重连和 Last-Event-ID 要自己实现,下一节讲怎么做。
面试常问:了解 SSE 吗,SSE 是一个怎样的协议?SSE 和普通 HTTP 连接有什么区别?SSE 和 WebSocket 有什么区别、怎么权衡,WebSocket 是半双工吗?聊天为什么用 SSE 不用 WebSocket?Event Trace 为什么选 SSE,只是因为只需要单向推送吗?流式问答的技术选型考虑哪些维度,为什么选基于 Fetch 的 SSE?HTTP 短轮询、SSE、WebSocket 各适合什么场景?SSE 为什么适合 AI 流式输出?测 SSE 接口怎么设计用例?
1.4. 断了怎么办
1.4.1. 先分清断的是哪一段
「断线重连」至少有三种情况,处理方法不一样:
- 前端和你的后端之间断了:网络切换、页面刷新、代理超时。
- 你的后端和模型服务商之间断了:上游中途报错、超时、连接被重置。
- 没断,但一直没有数据。快手一场前端一面问到了这一种:后端的数据源被阻塞了,前端收不到任何消息,也不知道到底断没断。
1.4.2. 前端断了:重推还是续推
满帮那场问:断联了,是重新再推一遍,还是按上次的位置接着推?有三种做法:
后两种有一个共同的前提:生成和连接分开。连接断了,后台的生成不停;用户点了停止,才真的取消。生成如果跟着连接走,断线就只能重来。
续推可以这样做:生成任务把每个事件按运行 ID 写进一个有序的日志(Redis Stream、带过期时间的列表都行),同时推给当前连着的前端。前端重连时带上 Last-Event-ID,用 fetch 的话自己带一个游标参数;服务端先从日志里补发这个编号之后的事件,再接上实时流。任务结束以后,日志保留一段时间再清掉。
OpenAI 的 background 模式就是这个结构:请求同时设 background 和 stream,客户端记住每个事件的 sequence_number,断了以后用 starting_after 加上这个编号重新取流,从断点往后接着收;保活事件不算位置。零数据保留的项目,响应数据大约只存 10 分钟。
也有反过来选的。MCP 2026-07-28 版规范里,Streamable HTTP 每个请求有自己的响应流,客户端关掉这条流就等于取消这个请求,服务端要停下来;基于 Last-Event-ID 的续传,2025-03-26 到 2025-11-25 那几版里有,这一版去掉了。这样服务端就不用为流保存状态。所以续不续传是一个取舍:要续传,服务端就得为每次运行存事件;SSE 本身只给了 id 和 Last-Event-ID 这两个钩子。
小厂一场全栈面问:SSE 怎么处理分片乱序?一条 SSE 连接跑在一条 TCP 连接上,本身有序。乱序会出在重连的时候:补发的旧事件和新的实时事件交错,或者新旧两条连接同时在推。所以前端按序号处理,比已收到的最大序号小的丢掉,跳号了先停下来等补发。度小满一场 AI 全栈面问:流式文本、卡片下发,怎么保证有序可靠?也是这几样:单条连接内有序,序号去重和补洞,结束事件确认完整,卡片作为一个完整的事件下发。
1.4.3. 上游断了:模型那一段
上游断了,已经收到的部分不一定要扔。Claude 的文档给了恢复的办法:把已经收到的内容存下来,再发一个请求让模型接着写。Claude 4.5 及更早的模型,把已收到的部分作为一条助手消息的开头;Claude 4.6 及以后,改成在用户消息里附上已收到的部分,并说明上次的回复中断了,请从断点继续。文档同时说,工具调用块和思考块不能部分恢复,只能从最近的文本块接着来。
所以文字可以续写,半截的工具调用只能整个重来,第六节接着讲。
1.4.4. 没断但没数据:心跳
快手那一问可以这样答:心跳由你的后端按固定间隔发,和上游有没有数据无关。前端只要还在收心跳,就说明连接活着,上游只是慢,界面上显示「还在处理」和当前步骤;连心跳都没了,才认为断了,开始重连。后端那边也要有一个超时:上游太久没有任何事件(包括 ping),就当它卡住了,取消这次调用,重试或者换模型。
心跳的间隔要比路上最短的空闲超时更短。SSE 规范建议 15 秒左右发一行注释;nginx 默认 60 秒没数据就断;Claude 的流里有 ping 事件,OpenAI 的 background 流里有 keepalive 事件,MCP 规范也建议长时间开着的流定期发注释保活。
1.4.5. 停止和切换会话
字节一场 AI 应用面问:一个 Agent 任务耗时很长,用户等不及了,怎样停止这个任务?度小满那场问:并发场景下,中断回答、切换会话重答,怎么保证上下文不乱?
停止要一路传下去:
- 前端用 AbortController 断开,或者调停止接口。
- 后端给这次运行打上取消标记,Agent 循环在每一步开始前检查它。
- 正在进行的模型调用要停掉。OpenAI 的文档说,同步请求断开连接就是取消,
background请求另有取消接口。 - 还在跑的子 Agent 和工具一起取消。MCP 工具在 Streamable HTTP 上关流就是取消,在 stdio 上要发
notifications/cancelled。有副作用的工具,要么让当前这个原子操作做完,要么事后补偿。 - 最后把状态记成「已取消」,连同已经做了哪些步骤一起存下来。
上下文不乱,靠给每次生成一个 ID。一个会话同一时间只允许一个活跃的生成;用户点了重答,旧的生成先取消,迟到的旧事件按 ID 丢掉。中断的回答要不要写进历史,要事先定下来。我倾向于把已生成的部分存进去并标上「被中断」,执行过的工具调用和结果照实存,不然下一轮模型不知道哪些事已经做了。重答是从同一条用户消息分出一个新分支,不要把两次回答都接在后面。
1.4.6. 怎么测
某大厂一场测开面问:SSE 流式输出,自动化测试怎么校验输出流的完整性和中断场景?可以测这几类:
| 类别 | 测什么 |
|---|---|
| 完整性 | 最后一个事件是结束事件;序号连续;增量拼起来和最终结果一致;用量字段在 |
| 中断 | 在随机位置断开连接;用代理模拟 60 秒没数据被断;在上游中途注入一个 error 事件;让上游变慢、心跳照常 |
| 续传 | 带着最后的序号重连,检查没有重复,也没有缺失 |
| 取消 | 点了停止以后,确认上游调用被取消,之后没有再发起工具调用 |
| 并发 | 同一个浏览器开多个会话,在 HTTP/1.1 下看第七条连接会怎样 |
面试常问:断线重连能讲下吗,是设了一套机制,还是有什么策略?断联了是重新推一遍,还是按上次的位置继续推,怎么实现的?SSE 怎么做断线补发?SSE 怎么处理分片乱序、异常断开、断线重连以及恢复输出?后端数据源阻塞、前端收不到消息,怎么知道是不是断了?编排过程中怎么保持前后端之间的心跳?网络中断页面会怎样?心跳保活用什么通信方式?用户断网了怎么重连?SSE 对话续传怎么做?流式文本、卡片下发怎么保证有序可靠?并发场景下中断回答、切换会话重答,怎么保证上下文不乱?自动化测试怎么校验输出流的完整性和中断场景?
1.5. 前端怎么渲染
1.5.1. 别每个 token 都 setState
字节一场前端一面问:AI 对话通过 SSE 流式输出 token,如果每收到一个 token 就执行一次 setState,会有什么影响?React 里怎么解决高频 token 更新导致的性能问题?能不能用 requestAnimationFrame?
模型每秒输出几十到上百个 token。每个都 setState,组件就可能跟着每秒重渲染几十上百次;如果每次还要把整段 Markdown 重新解析、代码重新高亮,主线程被占满,页面就卡了。
办法是攒一攒再画:收到的 token 先追加到一个不触发渲染的缓冲里(React 里用 ref),在 requestAnimationFrame 的回调里一次性提交,一帧最多更新一次;或者按固定间隔提交,比如每 50 到 100 毫秒一次。渲染次数就和 token 数脱了钩:按帧提交,每秒最多六十次左右;按 100 毫秒提交,每秒十次。逐字出现的观感差别不大。
东方财富一场一面问打字机效果。token 是一阵一阵地到的,直接显示会一顿一顿。想要匀速,就在缓冲里按固定节奏往外放字。这只是视觉效果,积压多了就放快一点,别让显示落后真实进度太多。
1.5.2. 流式 Markdown
百度前端那场问:流式过程中渲染大量 Markdown,卡顿怎么优化?Chrome for Developers 上 2025 年 1 月有一篇讲流式渲染 LLM 回答的指南,结论是:别每来一块就把全部内容交给 Markdown 解析器,再整体赋给 innerHTML。这样有两个问题:
- 性能:每来一块,前面所有内容都要重新解析成 HTML、重新渲染,代码高亮这类开销大的格式也跟着重做。
- 安全:模型的输出要当作用户生成的内容看待。有人用提示词注入让模型输出一段带
onerror的 HTML,直接赋给 innerHTML 就执行了。
指南推荐两样东西。一是流式 Markdown 解析器,只把新内容追加到已有的 DOM 后面,比如 streaming-markdown。二是 DOM 清洗器,比如 DOMPurify,对累积的全部内容做清洗,不能一块一块单独洗,因为危险的代码可能被拆在几块里;清洗器一旦删掉了东西,就停止渲染这次回答。纯文本用 append() 追加,不要用 textContent +=。
不换解析器,也能先做一步:把内容分成已经完整的块和正在写的最后一块,前面的块缓存起来不再重算,只重新解析最后一块;代码块闭合以后再高亮。对话很长时再加虚拟列表,字节一场全栈二面就追问了候选人项目里 SSE 对话续传和虚拟列表这一块。
1.5.3. 增量输出会出什么问题
字节一场 Agent 全栈面问:结构化的 JSON 卡片,是流式出来的,还是所有东西都准备好了以后一下出来?增量流式输出会出现哪些问题?
会碰到这些:
- 半截的语法:表格、代码块、列表还没闭合,渲染出来会闪,或者先错后对。
- 半截的 JSON:直接
JSON.parse会失败,下一节讲。 - 半个字符:一个汉字在 UTF-8 里占三个字节,可能被拆在两个网络包里。用 TextDecoder 的流模式解码(
decode(chunk, {stream: true})),它会把不完整的字节留到下一块。 - 审核:内容是边生成边给用户看的。OpenAI 的流式文档提醒,部分输出更难审核;和生成一起请求的审核分数,要等全部输出完才有。要审核,可以照 OpenAI 延迟指南里的分块处理:先流到后端,一块一块审过再推给前端。
卡片有三种做法:
| 做法 | 特点 |
|---|---|
| 等卡片完整、校验通过了再整个下发 | 简单,出现得晚一点 |
| 用部分 JSON 解析器,字段完整一个就填一个,先出骨架再填 | 快,前端复杂 |
| 让后端把模型的输出拆成文字事件和卡片事件 | 卡片只作为一个完整的事件下发 |
我倾向第三种,前端只处理完整的卡片,校验放在后端。
面试常问:AI 问答用 SSE 流式输出时,前端是怎么处理数据的?每收到一个 token 就 setState 一次,会有什么影响?React 里怎么解决高频 token 更新的性能问题?能不能用 requestAnimationFrame 解决?实现 AI 流式问答的前端会话状态管理。怎么实现打字机效果?流式过程中渲染大量 Markdown,卡顿怎么优化?流式渲染怎么做,结构化 JSON 卡片是流式出来的还是一次出来的?增量流式输出会出现哪些问题?
1.6. 流到一半的 JSON 和工具调用
1.6.1. 工具调用也是流出来的
模型调工具时,参数也是一段一段流出来的。Claude 的 tool_use 块用 input_json_delta 事件发参数,每个事件是一段 JSON 字符串的片段,比如 {"location": "San Fra,到这个块的 content_block_stop 才完整。文档建议攒齐以后再解析,也可以用 Pydantic 这类库做部分解析,SDK 的流式辅助函数也能边收边给出已经解析的部分。文档还说,当前的模型一次只吐一个完整的键值,所以参数流中间会有停顿。
默认情况下,API 会先缓冲、校验每个参数的值,再往下发。Claude 有一个细粒度工具流式的选项:在工具定义上设 eager_input_streaming: true,就跳过这层缓冲,大参数(一整份文档、一大段代码)的第一个片段来得更早。代价是收到的可能是不合法、不完整的 JSON;碰上 max_tokens 截断,参数也可能断在中间。
1.6.2. 只收到一半 JSON,要不要执行
百度那道题的答案是不执行。执行一个工具调用之前,三个条件都要满足:
[!warning] 三个条件都满足才执行
- 这个工具调用块已经结束,收到了它的
content_block_stop;- 这一轮正常结束,停止原因是
tool_use,而不是max_tokens、报错或者流被截断;- 参数能解析,并且通过了 schema 校验。
Claude Agent SDK 的文档说:如果 message_stop 到了,工具调用的块还没关,就把这个调用的参数当作不完整。第 11 题也讲过,被 max_tokens 截断的那一轮,工具参数可能断在中间,不能拿去执行。
不执行以后怎么办,分情况:
- 流被截断、上游报错:重新调一次模型。工具调用块不能部分恢复,只能整轮重来。
max_tokens不够:调大重试。Claude 文档给的就是两条路,调大max_tokens重试,或者修复部分输入。修复只适合只读的工具,有副作用的不要猜。- JSON 不合法(开了细粒度流式时会有):不执行,回一个
is_error: true的工具结果。Claude 文档建议把原始字符串包在一个单键的对象里,比如{"INVALID_JSON": "..."},让模型知道它发出的参数坏了,同时留下原文方便排查。模型下一轮会重新发。 - 有副作用的工具:参数完整了也要走第 11 题那一套,幂等键、确认、副作用分级。
「这种未知情况采取什么策略」,可以收成一句:默认不执行,把问题回给模型,有副作用的工具再加一道确认。
能不能为了快,参数没收全就先动手?只读、能取消的操作可以提前开始,比如参数里的文件路径已经完整了,先把文件读起来。思路和 OpenAI 那份指南里的投机执行一样:猜对了白赚时间,猜错了取消。有副作用的不行。这一条是我的分析。
1.6.3. 流式输出里怎么解析结构化结果
拼多多一场 Agent 一面问:流式输出下,怎么解析结构化结果,比如 JSON?
几种做法:
| 做法 | 说明 |
|---|---|
| 等完整结果再解析 | 不需要边收边用的,最省事 |
| 用部分 JSON 解析器展示进度 | 以最后完整、校验过的结果为准。Claude Agent SDK 的结构化输出也是这样:流里来的是没校验过的 input_json_delta,只有最终的 structured_output 是校验过的 |
| 让结构适合流 | 要先展示的字段放前面,长文本字段放最后;或者用 JSON Lines,每行一个完整的对象,一行结束就能用 |
JSON 怎么稳住,是第 22 题的内容。
面试常问:假如 SSE 流式传输只返回了一半的 JSON,要不要执行这个工具,你会采取什么策略?流式输出下怎么解析结构化结果?结构化 JSON 卡片是流式出来的,还是都准备好了再出来?增量流式输出会出现哪些问题?
1.7. 延迟:先拆开,再降
1.7.1. 先拆:时间花在哪
腾讯那场问:怎么区分端到端延迟来自工具调用、数据库查询,还是模型推理?要不要埋点?答案是要,而且要按第一节那个式子拆到每一段:
- 每次模型调用:排队时间、TTFT、输出 token 数、总耗时,用的哪个模型、什么 effort,缓存读了多少。
- 每次工具调用:耗时、重试了几次、有没有超时。
- 检索、数据库:各自的耗时。
- 编排:排队、等锁、序列化这些不属于上面任何一段的时间。
这些记成 trace 的 span,第 13 题讲过 trace 怎么记。先定好端到端的起点和终点。百度一场 Agent 后端三面就追问过:你说的「端到端」是什么场景,起点和终点在哪?用户发出到看到第一个字、到回答完整、到可以接着操作,是三个不同的数。
字节一场 Agent 一面出了一道排查题:一个平时 5 分钟跑完的长任务,这次跑了 15 到 20 分钟,你怎么分析延迟是什么导致的?拿一次正常的 trace 来对比,我会按这个顺序查:
[!tip] 按这个顺序查
- 轮数变了没有。多走了很多步,就查模型为什么多走:提示词或工具结果变了,还是模型在打转(第 13、28 题)。
- 每一轮变慢了没有。看 TTFT 和输出 token:上下文变长了,缓存断了(第 12 题),effort 被调高了,模型换了,或者服务商在排队、请求一直 429 在退避重试。
- 工具变慢了没有。某个工具超时重试,或者它依赖的外部服务慢了。
- 是不是在排队。同时跑的任务变多了,抢限额、抢沙箱。
- 是不是在等人。中间有没有一步在等审批。
美团一场 Agent 一面问:你说把某项耗时从 1 小时压到了 3 分钟,瓶颈在哪,哪些环节快、哪些慢?携程一场 Agent 一面问:Agent 主要缩减了哪些环节的耗时?这类问题要拿拆开的数字答,哪几段原来多久、现在多久。
1.7.2. 模型调用这一段
我在 API 速度那篇写过:不开思考时,TTFT 除了排队,主要花在预填充,也就是把输入一次算完,这一步吃算力,输入越长越慢;之后逐个生成 token,每生成一个都要把模型权重从显存搬一遍,吃的是显存带宽,它决定了每秒能吐多少 token。推理模型开了高档位,TTFT 的大头往往是思考时间。
长输入的 TTFT 可以很长。本地部署那篇引过一组双 DGX Spark 跑 DeepSeek V4 Flash 的测试:128K 输入、没有可复用的缓存,首 token 要等约 79 秒,之后的生成速度还有约 65 token/s。只看生成速度,会以为它很快。
OpenAI 那份指南给了一个经验:生成 token 几乎总是最慢的一步,输出 token 砍掉一半,延迟大约也少一半;输入砍掉一半,延迟可能只少 1% 到 5%,除非上下文真的很大。所以先看输出:回答短一点,结构化输出的字段名短一点,不需要的推理就别开。
模型这一段能调的旋钮:
- 换小模型、降 effort。Claude 的降延迟文档推荐对速度敏感的场景用 Haiku 5.5,把 effort 设成 low,并说 effort 是它控制速度和成本的主要手段。
- fast mode。Claude Opus 5.5 可以在请求里设
speed: "fast",输出速度最多到 2.5 倍,价格更高;提升的是每秒输出的 token 数,不是 TTFT。它有单独的限额,限额满了退回标准速度时缓存会失效,因为两种速度不共享前缀缓存。 - 缓存。前缀缓存命中的部分不用再算预填充,长提示词的 TTFT 会明显下降。Claude 文档说缓存不影响输出,结果和不用缓存时一样。Anthropic 2024 年发布提示词缓存时给的数字是长提示词的延迟最多降 85%。机制见第 12 题。
- 换服务商。API 速度那篇用的是 8 月的数据:同一个开源模型,在不同托管方之间速度能差 5 到 10 倍,官方 API 反而常常最慢。
三七互娱一场线下面问:有没有关注过 LLM 的首 token 返回时间?面经在括号里注了 kvcache。答的时候把三件事分开说:输入长度决定预填充要算多少,缓存命中能跳过多少,思考档位决定还要想多久。
1.7.3. 七条原则落到 Agent 上
OpenAI 的延迟优化指南把办法归成七条。指南里的例子用的还是 GPT-4 和 GPT-3.5,七条原则照样适用。落到 Agent 上,我是这样对应的:
投机执行是指:步骤 2 依赖步骤 1,但步骤 1 的结果大概率是某一个,那就两步同时开始,步骤 1 的结果不对,再取消步骤 2。指南举的例子是输入审核和生成同时开始。
并行的效果可以很大。Anthropic 讲他们的调研系统时说,早期的 Agent 一个接一个地搜,慢得难受;改成主 Agent 同时派出 3 到 5 个子 Agent,每个子 Agent 同时调 3 个以上的工具以后,复杂查询的调研时间最多缩短了 90%。
1.7.4. 没法流式的前置步骤
字节那场的完整追问是:问题改写和子问题拆分,是在一次模型推理里完成,还是要调用多次?这一步的模型推理没法直接流式输出给用户,会不会让用户等待的时间过长?小厂一场 Agent 一面也问:意图识别用模型,会不会导致识别耗时过长?
前置步骤都排在首 token 前面,多一步,首 token 就晚一步。可以这样处理:
- 先量。这一步的 P50、P95 是多少,占了首 token 的几成。小厂那场候选人的回答就是做了全链路测试,意图路由那一段耗时不长(面经里写的是 LongFuse,应该是 Langfuse)。
- 让它短。规则或小模型先判,判不了再上大模型;只输出几个字段的 JSON;不开思考,或者用低 effort。
- 合并。改写、拆子问题、要不要检索,放进一次调用。
- 并行。改写的同时,先用原问题检索一路。
- 跳过。简单的问题不改写。
- 推进度。这一步跑的时候,推「正在理解问题」「正在检索 3 个来源」这样的事件,让用户看到在进行。
同一场还问了入库:每张图片都要做一次模型推理,会不会让文档保存、入库的耗时过长?入库可以放到离线做,用户不必等。异步处理,后台并发跑,限住并发数;不急的可以走批处理接口,第 12 题讲过它更便宜也更慢。
1.7.5. 多层 Agent 会不会变慢
阿里一场一面问:引入多层 Agent 和多个子 Agent 以后,执行效率会不会变慢?
可能会。每加一层,就多出几次串行的模型调用:分派、汇总、复核。并行只在活能拆成互相独立的几块时才省时间,还要扣掉协调的开销。Anthropic 那 90% 是广度优先的调研任务。我那次多 Agent 复盘里,主 Agent 带三个子 Agent 用了约 62 分钟;同一道题另有一次 Codex 运行,约 44 分 43 秒。两次的模型、工作量、验证强度都没控制一致,也没有同一 Harness 下的单 Agent 对照,测不出并行本身省了多少时间,但至少没有显示出时间优势;主 Agent 一直要协调、读报告、检查版本。所以这题的答法是:看任务能不能拆,量过再说。怎么拆是第 3 题的内容。
1.7.6. 看分位数,定 SLA
延迟要看分位数,平均数会把慢的请求藏起来。Agent 的尾部更明显:一次任务要走很多步,步数越多,碰上某一步慢的机会越大。报的时候分开报:TTFT 的 P50、P95,端到端的 P50、P95,每一类步骤的 P95。
同花顺一场三面问:接入大模型以后延时上去了,模型又不能太大,要靠工程策略兜,吞吐和低延时怎么保证?整个对外服务的 SLA 是怎么定的?SLA 按场景定:交互式的回答定首 token 和完成时间,后台任务定多久之内完成、失败率多少,具体的数看业务。我在 API 速度那篇的选型表里,把实时语音的首 token 定在 300 毫秒以内,因为人对超过半秒的停顿很敏感;写代码的 Agent 更在意整套流程是几十秒还是几分钟。
面试常问:怎么区分端到端延迟来自工具调用、数据库查询,还是模型推理,要不要埋点?单 Agent 场景下可以从哪些方向优化端到端时延?端到端延迟的解决方案是什么?平时 5 分钟的长任务跑了 15 到 20 分钟,怎么分析、怎么解决?优化前后的耗时分别是多少,端到端的起点和终点是什么?提速减少了哪些环节?耗时瓶颈在哪,哪些环节快、哪些慢?Agent 主要缩减了哪些环节的耗时?意图识别用模型会不会导致耗时过长?这一步没法流式输出,用户会不会等太久?每张图片都要做一次推理,入库会不会太慢?引入多层 Agent 后执行效率会不会变慢?你的模型延迟会不会很高,怎么看待回答延迟?RAG 系统在线运行时,怎么统计和优化整体耗时?Agent 上线后在执行耗时上做过哪些调优?接入大模型后延时上去了,吞吐和低延时怎么保证,SLA 怎么定?在线链路的性能和稳定性怎么保证?端到端 P95、P50 怎么采集统计?关注过 LLM 首 token 返回时间吗?
1.8. 速度、准确、成本怎么取舍
恒生的 AI 面试官问:你说为了降低 TTFT 换成了豆包 1.8 flash,还做了 KV 缓存优化,在响应速度、回答准确性、调用成本三者之间,最终做了哪些关键取舍?百度那场三面问:如果多花约一分钟,能把准确率提高 20 到 30 个百分点,质量和性能怎么权衡?九方智投一场一面问得最短:响应速度和模型选择,怎么取舍?
这类问题我会按这个顺序答:
- 先定哪个是硬约束。实时语音的首 token、金融问答的准确率、单次调用的成本上限,一般有一样是不能让的,另外两样在它的约束下优化。
- 用数据选。在同一个评测集上,把每个配置(模型、effort、要不要改写、要不要重排)跑一遍,每个配置都是「延迟、准确率、成本」上的一个点,在约束之内挑准确率最高的。评测集怎么建是第 4 题的内容。
- 分步骤。路由、改写、摘要用小模型,最后的回答用大模型;小模型拿不准时再升级。第 12 题讲过模型分层。
- 分场景,或者交给用户选。交互式问答和后台报告用不同的配置;产品上可以给「快速」和「深度」两档。
- 有一样是三者同向的:缓存命中率。命中多了,成本降,TTFT 也降,输出不受影响。所以恒生那题里,KV 缓存优化如果指的是提示词前缀缓存,就是一步不用取舍的改动;换 flash 模型才是真的取舍,要拿评测数字说准确率掉了多少。
多花一分钟换 20 到 30 个百分点:先看这个场景的用户愿不愿意等一分钟。愿意就用慢的。不愿意,就看能不能先给一个快答,后台跑完再补一个深答;或者只在快答置信度低的时候,才走那一分钟。百度那场紧跟着还问了谁对单个 Skill 和整体的效果负责,这个要按团队的分工答。
字节抖音电商一场二面问:如果重点追求性能和低延迟,CLI、MCP、Skill 应该怎样选型和设计?面经里没记答案,下面是我的看法。延迟的大头是模型的轮数,接入方式本身的差别小,所以先想办法少走几轮:确定的多步逻辑写进一个脚本或一个工具,一次做完(第 7 题);工具说明写短,常用的常驻,别每次现搜;MCP 能用本地 stdio 就不走远程 HTTP,远程的复用连接;CLI 启动开销大的,做成常驻进程。
面试常问:为了降低 TTFT 换了 flash 模型、做了 KV 缓存优化,在响应速度、回答准确性、调用成本之间做了哪些关键取舍?多花约一分钟能把准确率提高 20 到 30 个百分点,质量和性能怎么权衡?优化后的稳定性如何,怎么权衡效率和稳定性?响应速度和模型选择怎么取舍?模型和向量数据库的选型,对检索效果、响应速度和成本有什么影响?如果重点追求性能和低延迟,CLI、MCP、Skill 应该怎样选型和设计?
1.9. 长耗时步骤:异步、进度、取消
1.9.1. 哪些步骤不该卡住主链路
小鹏一场 Agent 一面问:用户偏好信息为什么采用异步处理?腾讯一场社招二面问:上下文压缩是同步做的,还是异步做的?字节一场后端一面问:为什么通过 RabbitMQ 让聊天记录异步入库,入库会是一个很慢的动作吗,造成了什么时间瓶颈?
原则是:用户在等的那条路上,只放这次回答必需的步骤,其余的往后挪,或者放到旁边。
入库本身未必慢。字节那一问的后半句问的是造成了什么时间瓶颈,要拿数字说同步写多花了多少。压缩的取舍第 5 题讲过,这里只看延迟:同步压缩的那一轮最慢,用户能感觉到。
1.9.2. 慢工具:同步等还是异步
途游一场 Agent 二面问:ReAct Agent 调的外部工具启动很慢(比如要等 Pod 启动),应该同步调用还是异步调用?异步的话,后台任务和结果回调怎么实现?抖音电商一场后端一面问:主节点必须等工具结果时,长耗时任务怎么处理,有没有心跳或者进度反馈?
这两问第 11 题第三节答过:长任务提交以后立刻返回一个句柄,Agent 拿着句柄轮询或者等回调,MCP 的 tasks 扩展就是这个模式;进度通知让调用方分得清「还在干」和「挂了」。这里补三点和延迟、并发有关的:
- 等的时候别占着资源。Agent 要拿到结果才能往下走,逻辑上就得等,但可以把当前状态存起来(检查点见第 6、10 题),这次运行先挂起,连接和线程放掉,结果回来再从存下的状态接着跑。不需要马上用结果的,直接扔到后台,Agent 接着做别的。
- 等的时候给用户推进度。工具的进度通知转成第二节那种步骤事件推给前端,心跳照发,用户看到的是「正在等环境启动」,不是一个卡住的转圈。
- 超时跟着进度走,但要有上限。MCP 规范允许收到进度通知时重置超时的计时,因为这说明活确实在干;同时要求无论如何都有一个最长超时,防止客户端或服务端出了问题时一直等下去。
模型调用本身很长的,OpenAI 的 background 模式也是这个思路:异步跑,轮询或者流式接收,可以续接,可以取消。
趣链一场面试问:你说耗时工具会阻塞对话主链路,为什么这类任务不能直接放在 Agent 的主链路里执行?因为它占着连接,代理默认 60 秒没数据就可能断开;占着线程和内存,同时等的请求一多就扛不住;用户这期间什么也做不了;失败了也不好恢复。紧接着又问:为什么用 RabbitMQ,不直接用一个线程池?线程池在进程里,进程重启任务就没了,也没法分给别的实例;消息队列有持久化和重试,能跨实例消费,代价是多一套组件。
1.9.3. 在线链路和离线链路
阿里国际一场三面问:在线链路和离线链路(同步和异步)的整个架构是怎样的?在线链路的性能和稳定性怎么保证?
能提前做的放到离线:文档入库时就生成摘要、向量、图片描述,在线只管检索;离线可以用更大的模型、批处理接口、多次重试。在线只做必须实时做的,每一步有严格的超时,超时就降级:换小模型,跳过可选的步骤(比如重排),只走检索、不走 Agent。
停止一个跑了很久的任务,第四节第 5 小节讲过,取消要一路传到子 Agent 和工具。
面试常问:ReAct Agent 调用的外部工具启动很慢,应该同步调用还是异步调用,后台任务和结果回调怎么实现?主节点必须等待工具结果时,长耗时任务怎么处理,有没有心跳或进度反馈?耗时工具为什么不能直接放在 Agent 的主链路执行?为什么用 RabbitMQ 而不直接用线程池?用户偏好信息为什么采用异步处理?上下文压缩是同步做的还是异步做的?Agent 基座在压缩、同步和异步上怎么定制?为什么聊天记录异步入库,入库很慢吗,造成了什么时间瓶颈?Agent 的异步路由是做什么的,怎么做的?在线链路和离线链路的架构是怎样的?Agent 任务耗时很长、用户等不及了,怎样停止这个任务?
1.10. 并发:从工具并行到扛住高并发
1.10.1. 工具能不能并行
百度一场 Agent 一面问:工具是并发调用的吗?淘天一场一面问:Agent 调的工具和脚本目前是串行的,怎么判断能不能改成并行?阿里云一场一面反过来问:为什么选串行而不是并行,并行会带来什么问题?
判断标准第 11 题讲过。Claude 文档的说法是:独立的只读操作通常可以并行,换来更低的延迟;有副作用、共享状态、有顺序要求的,串行更稳妥。Claude Agent SDK 就是这么执行的:Read、Glob、Grep 和标了只读的 MCP 工具可以并发;Edit、Write、Bash 这类会改状态的按顺序执行;自定义工具默认串行,在注解里设了 readOnlyHint 才会并行。
把串行改成并行之前,逐个工具问四件事:输入是不是来自另一个工具的输出;会不会写同一个资源;并行以后会不会把下游打爆、撞上对方的限流;业务上有没有先后顺序。四个答案都是否定的,才能并行。改完再量一次端到端时间,确认真的变快了。
模型自己也不一定会并行。Claude 文档说,Claude Fable 5.1 在长的 Agent 循环里,可能比之前的模型发出更少的并行调用,建议在系统提示词里写明:有多个独立操作时,同时调用所有相关的工具。
并行的坑不只在工具上。我那次多 Agent 复盘里,有一轮测试读到了实现还在修改的中间产物,只能作废重跑:实现和测试虽然并行了,测试的对象却没有固定下来。并行的任务各自要有固定的输入。
1.10.2. 为什么要限制 Agent 的并发数
百度一场 Agent 面问的就是这句。原因有这几条:
| 原因 | 说明 |
|---|---|
| 下游有限额 | Claude API 按每分钟的请求数、输入 token 数、输出 token 数限流,用的是令牌桶:额度持续补充,不是到整分钟才清零。文档提醒,每分钟 60 次可能按每秒 1 次执行,短时间的突发就会触发 429;用量突然大涨还会撞上加速限制,要逐步加量 |
| 资源 | 每个 Agent 可能占一个沙箱或容器,还有内存、文件句柄、数据库连接 |
| 钱 | 并发越高,失控的时候烧得越快。并发下的预算怎么预占,见第 12 题 |
| 冲突 | 多个 Agent 改同一份状态(第 3 题) |
| 尾延迟 | 超过处理能力以后请求开始排队,所有人一起变慢 |
所以并发数要在入口处限住,用信号量或队列控制同时在飞的请求数,别等被 429 了再退避。429、529 怎么处理,见第 11 题第九节。
1.10.3. 并发地调模型
百度一场 Agent 二面问:FastAPI 异步调用大模型,用 await 等 LLM 返回结果时,CPU 会让出吗,底层的线程是什么行为?
会让出。调模型是 I/O 密集的,大部分时间在等网络。用异步客户端时,await 把控制权交还给事件循环,同一个线程可以去处理别的请求,一个进程能同时挂着很多个模型调用。坑在于在 async def 里调了同步的 SDK,或者别的阻塞操作:事件循环被堵住,这个进程上的所有请求一起卡住。FastAPI 的文档说,用普通 def 声明的接口函数,会放到外部的线程池里执行再等待,就是为了不堵住服务。所以要么全程用异步的客户端,要么把同步调用放进线程池。
同时在飞的数量还是要限。百度前端那场的手写题就是这个:写一个 async pool,给定最大并发数和一组请求地址,同时最多发 N 个,完成一个补一个,最后返回所有请求的结果。调模型时,把这个 N 设在服务商的限额以内。
1.10.4. 限流怎么做
网易互娱一场日常实习面问:实习中遇到过模型限流吗,错误码是多少,怎么处理?对大模型 API 的并发调用怎么控制?亚信那场问:智能体后端要调大模型,怎么用 Redis 做预算管理或者限流?
| 方面 | 做法 |
|---|---|
| 错误码 | 是 429。Claude 的 429 响应可能带 retry-after 头,告诉你等几秒再试,早了还会失败;响应头里还有一组 anthropic-ratelimit-* 字段,告诉你还剩多少额度、什么时候补满 |
| 自己的限流 | 按服务商的维度来,请求数和 token 数都要限,用令牌桶或滑动窗口;多实例时放在 Redis 里,用 Lua 原子执行(第 12 题) |
| 缓存 | 能抬高有效吞吐。Claude 默认的输入限额只算没命中缓存的输入 token。文档的例子是:限额每分钟 200 万,命中率 80%,实际每分钟能处理 1000 万输入 token。输出限额按实际生成的 token 实时算,max_tokens 设得大,不占额度 |
| 多租户 | 阿里云那场问:多租户场景下,不同租户怎么做资源隔离、并发限制?每个租户一份配额,外面再套一个全局上限;交互式的请求优先,批处理和后台任务走另一个池子,别让一个大租户的批任务把所有人的对话拖慢 |
1.10.5. AI 应用来了高并发
小红书一场一面问:如果这个 AI 应用出现高并发,你会怎么处理?金山一场一面问:系统的 QPS 某一天突然升高了,你怎么办?阿里云一场二面问:模型是怎么做负载、排队调度的?测开那场问:RAG 系统用户并发上来,Milvus 检索、大模型调用、文档解析分别成为瓶颈,从测试侧怎么定位?
按层答:
哪一层先成为瓶颈,要靠观测说,看哪一段的排队时间涨得最快。答题时把这几层都点到,再讲自己项目里实际先满的是哪一层。
沙箱单独说一句。百度一场 Agent 一面问:沙箱实例的并发数量,要考虑哪些问题?阿里千问一场二面问:为什么不为每个用户单独启动一个 Docker,当前方案怎么处理资源和并发?一个用户一个容器,内存和启动时间都是成本,冷启动也要时间,同时在线的用户一多,机器就不够。可以这样折中:预热一个池子,按会话分配,用完重置再放回去;同时限制沙箱的总数,满了排队。隔离要做到什么程度,是第 20 题的事。
1.10.6. 推理侧:自部署时吞吐怎么上去
拼多多一场 AI 平台工程面问:vLLM 里有哪些队列,continuous batching 是怎么实现的?
先说为什么要它。早期的推理服务按请求组批:一批请求一起跑,整批跑完才换下一批。先结束的请求要等最长的那个,新来的请求要等整批结束。Orca(OSDI 2022)改成按迭代调度:每生成一步就重新排一次,结束的请求先返回,新的请求在下一步就能加进来。论文在 GPT-3 175B 上,同样的延迟下,吞吐是 NVIDIA FasterTransformer 的 36.9 倍。continuous batching 说的就是这种调度。
vLLM 在这个基础上用 PagedAttention 管 KV Cache:像操作系统分页一样,把 KV 存成固定大小的块,按需分配,几乎不浪费,还能在请求之间共享。论文报告,同样的延迟下,吞吐是 FasterTransformer 和 Orca 的 2 到 4 倍。
队列的问题,我看的是 vLLM main 分支 11 月初的 V1 调度器源码。主要是两个:waiting 放还没开始和被抢占的请求,按调度策略(先来先服务或者优先级)排;running 放正在跑的。另外还有几个给 KV 加载用的等待队列。每一步,调度器在一个 token 预算里先给 running 里的请求分配(解码,或者长输入剩下的那段预填充),再从 waiting 里收新的请求。长输入会被切成几段预填充,代码里有一个阈值,专门防止一个长预填充把预算占光、饿着别的请求。KV 块不够时就抢占:被抢的请求释放 KV 块,已算的 token 清零,放回 waiting 的最前面,之后重算。高并发时尾延迟上升,这是原因之一。
吞吐和单个请求的延迟此消彼长:批越大,吞吐越高,每个 token 的间隔也越长;一个长输入的预填充插进来,正在解码的请求都会被拖慢。DistServe 把预填充和解码分到不同的 GPU 上,分别按 TTFT 和 TPOT 的要求配资源。
南方基金那篇面经提到了推测解码:用一个小模型先猜几个 token,大模型做一次前向并行验证,猜对的直接收下。输出的分布和大模型自己生成的一样,论文在 T5-XXL 上快了 2 到 3 倍。效果取决于猜中的比例。本地部署那篇引过一个 Mac 上的测试:同一个 4-bit 模型,聊天约快 1.68 倍,代码和数学约快 2.6 倍。我在 API 速度那篇写的是,它在低并发下有 30% 到 50% 的提速。并发高的时候 GPU 的算力本来就吃紧,验证要多算的那一部分不再是白给的,收益一般会变小。
面试常问:为什么需要限制 Agent 的并发数?工具是并发调用的吗?模型调用工具是一次一个还是能调多个,多个是并行还是串行?Agent 要调的工具和脚本目前是串行的,怎么判断并改成并行?调用某工具为什么选串行而不是并行,并行会带来什么问题?工具之间有依赖,怎么并行执行并处理错误?FastAPI 异步调用大模型,用 await 等 LLM 返回时 CPU 会让出吗?手写一个 async pool,控制异步请求的并发。实习中遇到过模型限流吗,错误码是多少,怎么处理?对大模型 API 的并发调用怎么控制?怎么用 Redis 做预算管理或者限流?多租户场景下,不同租户怎么做资源隔离、并发限制?模型是怎么做负载和排队调度的?项目考虑过高并发吗,AI 应用出现高并发怎么处理?QPS 某一天升高了怎么办?你这个系统的订单量是不是很少,并发、运维、失败归因怎么做?Agent 部署时性能优化、并发控制怎么做?RAG 系统用户并发上来,检索、模型调用、文档解析分别成为瓶颈,怎么定位?大模型 API 调用超时、限流,系统层面怎么处理,怎么自动化模拟限流做测试?沙箱实例的并发数量要考虑哪些问题?为什么不为每个用户单独启动一个 Docker?vLLM 里有哪些队列,continuous batching 是怎么实现的?推测解码是什么?
1.11. 面试怎么答
1.11.1. 两分钟左右的主线
- 先把三件事分开:流式管用户多久看到东西,延迟管一次任务多久做完,并发管同时来很多时扛不扛得住。各看各的指标:TTFT,端到端的 P95,吞吐和排队。
- 流式怎么做:模型服务商用 SSE 流给后端;后端设计自己的事件(正文、步骤、卡片、心跳、错误、结束),每个事件带序号,再推给前端。中间的代理要关缓冲、调超时。
- SSE 和 WebSocket:对话是一问一长答,单向就够,SSE 走普通 HTTP,基础设施不用改;真正双向实时的才上 WebSocket。原生 EventSource 不能 POST、不能加头,要 POST 就用 fetch 自己解析,重连也自己做。
- 断了怎么办:先分清断在哪一段。生成和连接分开,断线不等于取消;事件编号存进日志,重连时从断点补发;心跳用来区分「慢」和「断」;停止要一路传到模型调用和工具。
- 半截 JSON:工具调用块没结束、停止原因不对、参数校验不过,就不执行,回给模型重发;有副作用的工具再加确认。
- 延迟:先拆到每一段,再降。输出 token 往往是大头;少调几轮,并行,换小模型或降 effort,用缓存,再用流式和进度让用户少等;没法流式的前置步骤,量过以后再决定合并、并行还是跳过。
- 取舍:先定硬约束,在评测集上量出每个配置的速度、准确率、成本,再选;缓存是三者同向的。
- 并发:工具按「独立、只读」判断能不能并行;入口限住并发,按服务商的维度限流;异步 I/O 别被同步调用堵住;高并发按入口、连接、模型、工具、观测分层答。
1.11.2. 「你的项目」按这个顺序准备
- 几个数:首 token 的 P50 和 P95,一次任务的端到端耗时,同时在线的会话数,429 的比例。
- 一次请求的时间花在哪:哪几段最慢,各占多少。
- 做过哪一项优化,前后的数字,怎么证明是这项改动带来的。
- 流式和断线是怎么实现的:事件格式、序号、心跳间隔、断了以后怎么接上。
- 一次真实的变慢或者断线:怎么发现的,第一处问题在哪,后来改了什么(第 13 题)。
1.11.3. 常见的失分点
[!caution] 常见的失分点
- 说流式能让模型变快。
- SSE 和 WebSocket 只会说单向、双向,说不出连接数、重连、鉴权上的区别。
- 说做了 SSE 断线重连,讲不出断了以后从哪里接上、靠什么去重。
- 收到半截工具参数就执行,或者执行前不看停止原因。
- 讲延迟优化拿不出拆开的数字,只说换了更快的模型。
- 只报平均耗时,不报 P95。
- 把所有工具都改成并行,不提副作用和依赖。
- 并发控制只会说线程池,说不出服务商的限额按什么维度算。
- 在 async def 里调同步的 SDK。
- 把 Agent 的高并发答成秒杀系统,只讲 Redis 和 MQ,不提模型的限额和长连接。
面试常问:项目响应时间大概多少?整轮耗时大概多久?Agent 部署时遇到过哪些问题,性能优化、并发控制怎么做?
1.12. 出处
文中的面试问法都出自下面这些面经,括号里是发帖日期,链到牛客原帖。
- 开头的例子:百度后端开发一面(09-23) · 满帮 AI 全栈一面(10-08) · 字节 Agent 秋招二面(09-24) · 腾讯大模型算法一二面(10-02)
- 延迟:慢在哪、怎么降:途游 Agent 二面(08-18) · 同花顺 Agent 开发三面(08-30) · 阿里国际 Agent 开发三面(08-31) · 阿里边缘 BU 一面(09-03) · 字节 Agent 开发一面(9.3)(09-04) · 商汤全栈一面(09-08) · 三七互娱线下面(09-11) · 快手日常实习一面(09-16) · 百度 Agent 后端三面(09-21) · 携程 Agent 开发一面(09-23) · 字节 Agent 秋招二面(09-24) · 腾讯大模型算法一二面(10-02) · 拼多多秋招 Agent 一面(10.8)(10-09) · 高斯信息 AI 开发一面(10-09) · 某大厂测开一二三面(10-12) · 美团 Agent 一面(10-12) · 携程 Agent 一面(10-17) · 网易 Agent 开发一面(10-17) · 小厂 Agent 开发一面(10-18)
- SSE 是什么、和 HTTP、WebSocket 怎么选:字节 Agent 开发一面(08-24) · 欢乐互娱前端实习一面(08-24) · 百度前端一二面(09-03) · 百度 Agent 一面(09-06) · 要务科技(09-07) · 字节前端泛抖音一面(09-15) · 快手前端一面(09-17) · 度小满一面(09-19) · 美团 AI 后端一二面(食杂零售)(10-01) · 某大厂测开一二三面(10-12) · 字节 Agent 研发一面(10-14) · 放生面经-百度(10-14) · 字节全栈二面(10-17)
- 模型调用的并发、限流和高并发:百度秋招 Agent 一二三面(08-28) · 百度 Agent 一面(08-29) · 同花顺 Agent 开发三面(08-30) · 百度 Agent 二面(08-30) · 亚信安全 Agent 开发一面(09-01) · 百度前端一二面(09-03) · 北京金山数娱 AI 应用一面(09-13) · Agent 开发面经(09-18) · 阿里千问二面(9.22)(09-22) · 小红书 Product Engineer 一面(09-24) · 阿里云一面(09-28) · 阿里云二面(09-29) · 某大厂测开一二三面(10-12) · 网易互娱 AI 应用日常实习(10-16) · 网易 Agent 开发一面(10-17)
- 断线、中断、续传、有序、心跳:小厂全栈(偏前端)(08-26) · 字节前端泛抖音一面(09-15) · 快手前端一面(09-17) · 度小满一面(09-19) · 虚拟盒子前端实习(09-19) · 字节剪映前端一面(09-22) · 字节全栈二面(交易与广告)(09-24) · 度小满 AI 全栈一面(09-30) · 满帮 AI 全栈一面(10-08) · 某大厂测开一二三面(10-12) · 炎魂网络前端一二面(10-12) · 放生面经-百度(10-14)
- 长耗时步骤:异步、进度、取消:途游 Agent 二面(08-18) · 小鹏 Agent 开发实习一面(08-28) · 字节 AI 应用开发一面(交易与广告)(08-31) · 阿里国际 Agent 开发三面(08-31) · 抖音电商后端一面(09-03) · 趣链科技 Java 开发(09-14) · 腾讯社招二面(09-23) · 字节 AI 应用工程师一面(09-25) · 字节交易与广告后端一面(09-25) · Binance AI 全栈实习一面(10-07)
- 流式返回怎么实现、推什么:拼多多 Agent 复活赛一面(09-03) · 百度 Agent 一面(09-06) · 字节抖音电商客户端一面(09-15) · 虚拟盒子前端实习(09-19) · 字节交易与广告后端一面(09-25) · 美团 AI 后端一二面(食杂零售)(10-01) · 满帮 AI 全栈一面(10-08) · 放生面经-百度(10-14) · 字节全栈二面(10-17)
- 前端怎么渲染:小厂全栈(偏前端)(08-26) · 百度前端一二面(09-03) · 东方财富一面(09-18) · 字节前端一面(09-22) · 字节 Agent 全栈一面(泛抖音)(10-13)
- 速度、准确、成本怎么取舍:百度 Agent 后端三面(09-21) · 恒生电子 AI 面(09-24) · 字节抖音电商 Agent 开发一二三面(10-07) · 美团和阿里二面(10-14) · 九方智投一面(10-18)
- 工具和子 Agent 能不能并行:百度 Agent 一面(09-06) · 拼多多 Agent 一面(09-22) · 淘天供应链 AI 应用一面(09-23) · 东方财富 AI 应用一面(09-24) · 阿里云一面(09-28)
- 流式里的 JSON 和工具调用:拼多多 Agent 复活赛一面(09-03) · 百度后端开发一面(09-23) · 字节 Agent 全栈一面(泛抖音)(10-13)
- 推理侧:批处理和加速:拼多多 AI 平台工程一面(09-08) · 三七互娱线下面(09-11) · 南方基金 AI 应用笔面试(10-12)
- 外部资料:规范和文档 HTML Standard:Server-sent events(11-07 读取)、MDN Using server-sent events、nginx ngx_http_proxy_module、FastAPI Concurrency and async / await、MCP 2026-07-28 规范的 Cancellation、Progress、Streamable HTTP · Claude 文档 Streaming messages、Fine-grained tool streaming、Parallel tool use、Reducing latency、Rate limits、Errors、Fast mode、Prompt caching,Claude Agent SDK How the agent loop works、Stream responses in real-time(都是 11-07 读取) · OpenAI 文档 Latency optimization、Streaming API responses、Background mode、WebSocket Mode(11-07 读取) · Anthropic Prompt caching with Claude(2024-08-14)、How we built our multi-agent research system(2025-06-13) · Chrome for Developers Best practices to render streamed LLM responses(2025-01-21) · 论文 Orca(OSDI 2022)、vLLM / PagedAttention(2023)、Fast Inference from Transformers via Speculative Decoding(ICML 2023)、DistServe(2024) · 代码 vLLM vllm/v1/core/sched/scheduler.py(main 分支,11-07 读取)
- 我自己的文章:云端大模型 API 速度大起底(08-30)· Fable 5.1 和 GPT-6 Astra 发布后,本地部署从我的时间线消失了(09-06)· 62 分钟交卷,Token 用了预算的 192 倍(09-17)