AI

AI SDK UI Stream 如何判断输出结束

text-end、finish-step、finish、HTTP Stream close 是四个不同层级的结束信号。把 text-end 当成回答结束,是 Chat UI 里最常见的流式状态误判。

用 Vercel AI SDK 构建 Chat 界面时,一个高频问题是:怎么判断模型这轮输出“结束了”?

表面上看这是一个状态标志的问题,实际上 AI SDK 的 UI Stream 里存在四种不同层级的“结束”,混淆它们会导致 UI 提前停止 loading、消息被截断,或者无法处理多轮工具调用。

四种“结束”

text-end
  ↓ 当前文本片段结束

finish-step
  ↓ 当前模型 Step 结束

finish
  ↓ 当前 assistant message 结束

HTTP Stream close
  ↓ 当前请求彻底结束

此外,当前请求结束后,客户端仍可能根据自动续传逻辑发起下一次请求。所以还要区分第五件事:请求结束后客户端会不会再发请求。

1. text-end:当前文本片段结束

text-end 只表示当前 text part 不会再收到 text-delta

一条 assistant message 可能包含多个文本片段和工具调用:

text-start
text-delta
text-end

tool-input-available
tool-output-available

text-start
text-delta
text-end

finish

例如:

模型输出:我先查询一下天气
→ text-end

模型调用天气工具
→ tool call
→ tool result

模型继续输出:今天杭州有雨
→ 新的 text-start / text-end

因此:

收到 text-end,不代表整个回答已经结束。它只代表当前这段文本结束。

2. finish-step:当前模型 Step 结束

一个 Step 通常对应一次模型调用。

Agent Loop 中,一轮用户请求可能包含多个 Step:

Step 1
模型输出工具调用
→ finish-step

执行工具

Step 2
模型读取工具结果并继续回答
→ finish-step

因此:

finish-step 表示一次模型调用结束,不代表整个 Agent 任务结束。

只要后续还需要执行工具或继续调用模型,就会产生新的 Step。

3. finish:当前 assistant message 结束

finish 是消息级别的结束事件。它表示当前 assistant message 的所有内容已经生成完成,包括:

  • 文本片段
  • 推理片段
  • 工具调用
  • 工具结果
  • 多个模型 Step

通常收到 finish 后,服务端会关闭当前 Response Stream。

因此判断一轮服务端生成是否完成,主要看:

chunk.type === 'finish'

而不是:

chunk.type === 'text-end'

4. HTTP Stream close:当前请求彻底结束

底层通过 ReadableStream 判断时:

const reader = response.body!.getReader();

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

  if (done) {
    // 当前 HTTP 请求彻底结束
    break;
  }

  // 处理流数据
}

done === true,表示当前 HTTP Response Body 已经关闭,当前请求不可能再继续发送数据。

正常流程通常是:

收到 finish
→ 服务端关闭 Stream
→ reader.read() 返回 done: true

5. 当前请求结束,不代表不会发起下一次请求

需要区分两件事:

当前请求是否结束
客户端是否会再次发送请求

即使当前请求已经收到 finish 并关闭,useChat 仍可能根据自动续传规则发起下一次请求。例如配置:

useChat({
  sendAutomaticallyWhen: ({ messages }) => {
    return shouldContinue(messages);
  },
});

完整流程可能是:

请求 A
→ streaming
→ finish
→ HTTP Stream close

执行 sendAutomaticallyWhen
├─ false:停止,进入 ready
└─ true:自动发起请求 B

这种机制常用于:客户端工具执行完成后继续对话、用户确认工具调用后继续、多轮工具调用、自定义 Agent Loop。

所以:

finish 只能证明当前请求结束,不能单独证明客户端不会再发请求。

6. useChat 中如何判断整体状态

使用 useChat 时,通常直接观察 status

const { messages, sendMessage, status } = useChat();

常见状态流转:

submitted
→ streaming
→ ready

异常时:

submitted
→ streaming
→ error

含义:

  • submitted:请求已经提交,等待响应
  • streaming:正在接收流数据
  • ready:当前没有正在进行的请求
  • error:请求失败

因此 UI 中通常这样判断:

const isLoading =
  status === 'submitted' ||
  status === 'streaming';

const isIdle = status === 'ready';

status === 'ready' 表示当前 useChat 已经没有正在执行的请求。但如果配置了自动续传,状态可能短暂回到 ready,随后再次进入 submitted → streaming

7. 各种结束信号对照表

信号表示什么后续还能有内容吗
text-end当前文本 part 结束可以
finish-step当前模型调用结束可以
finish当前 assistant message 结束当前请求通常不再有业务内容
Stream done: true当前 HTTP 请求关闭当前请求不可以
status === 'ready'当前 useChat 没有执行中的请求之后仍可由用户或自动逻辑发起新请求

一句话总结

text-end 是文本片段结束,finish-step 是一次模型调用结束,finish 是当前 assistant message 结束,HTTP Stream close 是当前请求彻底结束;请求结束后是否还会发送下一次请求,由 sendAutomaticallyWhen 等客户端逻辑决定。

Back to Blog

Related Posts

View All Posts »