面试日记 第 30 天

屏幕空了约 28 秒,终于出现首字。

面试官没有停表。进入稳定生成后,又过了 14.6 秒,回答才多了一个词。

她把手机横着放到电脑旁边。

“这就是你说的本地大模型?”

“对。”我还有点兴奋,“2.8T 参数,M1 Max 也能跑。还做成了 OpenAI 兼容接口,前端改一下 base URL 就能接。”

她没接我的技术细节,只问了一句:

“首字等约 28 秒,后面还要 14.6 秒一个 token,然后呢?”

我看着那个几乎没动的回复框。

“这种速度肯定不能直接上线。”

“那你刚才为什么一直在讲‘能跑’?”

本地部署、大参数、专家流式加载、OpenAI 兼容接口,每个词都像项目亮点。但用户打开聊天页面,不会因为模型有 2.8T 参数就多等半分钟。

对用户来说,页面不会展示参数量,只会展示明确进度,或者长时间空白。

她把题目写在纸上:

“当大模型API响应延迟超过1秒时,前端可以采取哪些优化策略保证用户体验?”

我先说:“加 loading,至少让用户知道系统还在工作。”

“只加 loading?”

“不够。”我重新整理了一下,“要分成三层:立即反馈、流式输出、状态兜底。”

用户点击发送时,自己的消息应该立刻出现在页面上,回复区进入明确的生成状态。这一步不能等后端返回,界面反馈要和网络请求解耦。

第二层是尽快显示第一个 token。后端支持 SSE 或其他流式协议时,前端拿到数据就追加渲染。完整答案可能还要生成很久,但只要内容已经开始出现,用户感受到的就不是一片空白。

第三层是失败后的去路。请求超时、网络断开、用户切换对话、连续发送多个问题,都要有明确状态。已经生成的内容不能突然消失,旧请求也不能回来覆盖新回答。

她指着秒表:“如果后端就是 14.6 秒一个 token,流式输出有用吗?”

“有用,但救不了根本问题。”我说,“前端只能把真实进度展示出来,不能把慢模型伪装成快模型。这个速度更适合研究、离线任务或明确接受长等待的场景。真要做交互产品,还是得换模型、换部署方案,或者把任务拆成能快速返回的阶段。”

“所以前端优化的边界是什么?”

“后端决定模型实际有多快。”我说,“前端决定等待期间用户看见什么、能做什么,以及失败以后能不能回来。”

她把手机收了回去。

演示结束后,我在项目说明的“已经跑通”后面补了两行:首 token 约 28 秒,稳定生成约 14.6 秒一个 token。

回答重点

大模型 API 动辄 2-5 秒的响应时间,用户干等着界面没反应肯定受不了。前端优化的核心就是让用户感知不到等待,主要靠三板斧:立即反馈、流式输出、状态兜底。

立即反馈是指用户点发送的瞬间,界面就要有动静。消息气泡先出来,哪怕内容是空的,加个 loading 动画或者骨架屏,让用户知道系统在干活。这招叫乐观 UI,核心思路是把用户反馈和网络请求解耦,不等后端响应就先更新界面。

流式输出是大模型场景的标配。OpenAI、Claude 这些 API 都支持 SSE 流式返回,后端每生成一个 token 就往前端推一次,前端拿到就渲染,用户看到的就是文字一个一个蹦出来的打字机效果。心理学上这叫“进度反馈”,用户看着文字在动,等 5 秒也不觉得烦。

// 流式响应处理核心逻辑
const response = await fetch('/api/chat', {
  method: 'POST',
  body: JSON.stringify({ message })
})

const reader = response.body.getReader()
const decoder = new TextDecoder()

while (true) {
  const { done, value } = await reader.read()
  if (done) break

  const chunk = decoder.decode(value)
  // 逐块追加到界面
  appendToMessage(chunk)
}

状态兜底是保底策略。设置 30 秒超时,超了就提示“响应超时,请重试”;网络断了要能检测到并给出提示;请求失败要有重试按钮。这些边界情况处理不好,用户体验直接崩盘。

扩展知识

别把“首字延迟”和“输出速度”混在一起

大模型体验至少要看两个指标。

一个是 TTFT,也就是从用户发出请求,到看到第一个 token 的时间。聊天、搜索和代码补全都很在意它,因为用户首先判断的是“系统有没有响应”。

另一个是 TPOT 或 tokens per second,描述内容开始生成后,后续 token 到来的速度。首字很快、后面很慢,用户会觉得回答断断续续;首字很慢、后面很快,用户会先盯着空白页面发愣。

Deltafin 的数据把这个差别放大到了极致:短提示的首 token 中位数约 28 秒,稳定生成阶段也只有约 0.0687 token/s。这种情况下,SSE 可以让用户看见真实进度,却不能改变每个 token 仍要等待 14.6 秒的事实。

乐观 UI 不是“假装成功”

乐观 UI 可以先显示用户消息和回复占位,但不能提前展示一份并不存在的回答。它要为失败预留状态:

  • sending:消息已经进入界面,请求正在发送;
  • streaming:已经收到首个数据块,回答持续生成;
  • complete:服务端正常结束;
  • failed:网络、鉴权或服务错误,可以重试;
  • cancelled:用户主动停止,不再接受旧请求的后续数据。

失败时不要把消息整块删除。原地保留内容,再显示“发送失败”或“回复不完整”,用户不会因为界面突然消失而更困惑。

流式输出要处理半包和粘包

ReadableStream 每次读到的 chunk 不一定刚好是一条完整消息。一个 JSON 事件可能被拆成两段,也可能多条事件一起到达。前端需要维护缓冲区,按协议边界拆包,再解析数据,不能直接把每个 chunk 当作完整 JSON。

如果后端使用 SSE,还要区分正常结束标记、心跳、业务错误和连接中断。网络断开时保留已经显示的内容,并明确告诉用户这是一份不完整回答。

旧请求必须能取消

用户可能连续提问,也可能在生成中切换会话。如果旧请求不能取消,晚回来的数据就可能覆盖新对话。

let controller = null

async function sendMessage(message) {
  controller?.abort()
  controller = new AbortController()

  try {
    const response = await fetch('/api/chat', {
      method: 'POST',
      body: JSON.stringify({ message }),
      signal: controller.signal
    })

    // 处理流式响应
  } catch (error) {
    if (error.name === 'AbortError') return
    throw error
  }
}

请求还要带上唯一的 conversationIdmessageId。收到响应时再次校验它是否仍属于当前会话,不能只相信“这是最后发出去的那个请求”。

超时必须服从真实任务

普通在线对话可以设置 15-30 秒的响应门槛,但不能把同一个数字套在所有任务上。

Deltafin 自己就提醒,长系统提示会让 prefill 非常昂贵,自动化客户端甚至可能需要按小时设置超时。这不是说产品应该让用户盯着页面等几个小时,而是说明它更接近研究工具和离线任务。

如果一个任务天然很慢,更合理的交互通常是异步化:先快速确认任务已接收,后台继续处理,完成后再通知用户查看结果。前端优化不是给所有慢请求换一个更好看的 loading,而是让交互方式符合任务的真实耗时。

面试官追问

追问:流式输出的时候,如果中途网络断了,怎么处理?

回答:监听 SSE 或 fetch 的错误事件,网络断开时立即停止渲染,把当前已经显示的内容保留住,在消息末尾加一个"网络中断,回复不完整"的提示。同时显示一个"重新生成"按钮,点击后带上之前的上下文重新请求。注意不要直接删掉已经显示的内容,用户可能已经看了一部分有用的信息。

追问:乐观 UI 的回滚逻辑会不会导致界面闪烁?

回答:处理得当不会闪。关键是失败时不要直接删除消息,而是原地更新状态为"发送失败"。用户看到的是消息还在,只是多了个红色的"发送失败"标记和重试按钮,不会有东西突然消失又出现的跳动感。动画过渡也能帮上忙,状态切换时加个 200ms 的 fade 效果,视觉上更平滑。

追问:多个对话窗口同时请求,怎么避免响应错乱?

回答:每个请求带上唯一的 conversationId 或者 messageId,响应回来的时候校验 id 是不是当前激活的对话。如果用户已经切到别的对话了,这个响应就丢弃或者静默写入缓存,不更新界面。还有一种做法是用 React 的 key 机制,切换对话时直接销毁重建组件,自然就不会有错乱问题。

追问:超时时间设多少合适?太短怕正常请求被掐断,太长用户等不及。

回答:看业务场景。普通对话建议 30 秒,因为 GPT-4 生成长文本确实需要时间;如果是简单的问答场景,15 秒够了。更好的做法是动态超时,根据问题复杂度估算,问题短就设短一点,问题长或者明确要生成长文就设长一点。另外超时不要直接报错,先给用户一个"还在努力生成中,是否继续等待"的选择。

更多推荐