跳转到内容

流式传输与进度事件

流式传输与进度事件

学习目标

理解 Nanobot 如何实现内容、推理和文件编辑三层流式输出,以及 WebSocket 分段输出机制。

前置知识

下文假设你已理解上述概念,直接聚焦 Nanobot 的具体实现。

项目实践

三种流式模式

Nanobot 的 AgentRunner._request_model() 支持三种流式模式:

模式触发条件特点
完整流式hook.wants_streaming() == True直接调用提供商的 chat_stream_with_retry
进度流式提供商支持 progress_deltas 但 hook 不需要完整流式通过 IncrementalThinkExtractor 过滤推理内容
非流式无流式需求直接调用 chat_with_retry

文件编辑进度事件

Nanobot 实现了实时的文件编辑进度流:

live_file_edits = StreamingFileEditTracker(
workspace=spec.workspace,
tools=spec.tools,
emit=_emit_live_file_edits,
)

on_progress 回调接受文件编辑事件时:

  1. 在工具调用 delta 流中追踪文件修改
  2. 实时推送编辑进度到前端(WebUI 显示进度条)
  3. 工具调用完成后应用最终 call_id
  4. 未匹配的工具调用报告错误

WebSocket 分段输出

WebUI 使用分段流式(stream segment),每个工具调用阶段为一个独立段:

stream_base_id = f"{msg.session_key}:{time.time_ns()}"
stream_segment = 0
def _current_stream_id() -> str:
return f"{stream_base_id}:{stream_segment}"

每次 on_stream_end(resuming) 调用时,stream_segment 递增。resuming=True 表示工具调用继续(spinner 应重新启动),resuming=False 表示最终回复。

外超时管理

Nanobot 对不同类型请求应用不同的超时策略:

请求类型超时控制
非流式请求NANOBOT_LLM_TIMEOUT_S(默认 300s)
流式请求不使用外超时,依赖提供商级别的 NANOBOT_STREAM_IDLE_TIMEOUT_S
进度流式不使用外超时

原因:流式请求如果应用外超时,长推理会被错误杀掉,因为总耗时可能远超单次延迟阈值。

问题与规避

流式内容已发送但请求失败

问题:如果流式输出已经开始(内容 delta 已发送),但 LLM 返回错误,不应重试(否则用户看到重复内容)。

规避should_retry_guard=lambda: not has_streamed_content 确保一旦有流式内容输出,不再重试。

推理内容泄漏到正文

问题:某些提供商的推理内容混在普通内容中发送。

规避IncrementalThinkExtractor 监控流式内容,自动识别和提取推理块,通过 emit_reasoning() 分离推送。

设计取舍

为什么区分完整流式与进度流式

原因:不同渠道对流式的需求不同。WebSocket 频道需要完整流式(每段 delta 单独推送),而某些渠道只需要进度提示(如工具调用提示)。进度流式模式允许在不支持完整流式的提供商上仍提供部分用户体验。

参考来源