向页面推送任务进度时,后端选择 WebSocket 还是 SSE,为什么?
未标记
考查重点:单向进度推送与双向交互。单向阶段通知可选 SSE,频繁双向消息可选 WebSocket;SSE 的重连和事件 ID 不等于历史回放,补发仍需服务端保存事件并按游标读取
本题目录
参考答案
核心回答
如果只是后端向页面推送任务阶段和结果,优先考虑 SSE;如果前后端需要在同一连接上频繁互发消息,再考虑 WebSocket。 不能因为任务耗时长或需要实时反馈,就认定必须用 WebSocket。
例如研究任务的提交、取消通过普通 HTTP 接口完成,执行过程由服务端单向推送,采用 SSE 就能满足需求。WebSocket 支持全双工通信,适合持续交互、协同操作等场景,但需要自行管理连接、心跳、重连等行为。
无论选哪种协议,都要区分连接恢复和任务进度恢复。浏览器原生 EventSource 提供重连及事件 ID 机制,历史事件能否补发仍取决于服务端是否保存事件、是否按客户端游标读取;WebSocket 重连也不会自动补回断线期间的消息。
怎样做选型
- 单向阶段通知: 页面主要接收“开始检索、正在汇总、生成报告、完成”等事件,提交和取消只是偶尔发生的命令,可以采用
HTTP命令接口加SSE订阅。服务端返回text/event-stream,用event区分事件类型、data携带内容、id标识事件,前端据此更新进度和结果。 - 持续双向交互: 客户端需要频繁发送操作,服务端也要持续响应,或需要双向传输二进制数据,可以采用
WebSocket。它提供双向通道,具体消息含义、确认和恢复仍由应用约定。
已有系统若已使用 WebSocket 承载多类任务事件,也可以继续复用连接。关键是解释它满足了什么需求、付出了什么维护成本,不把“支持更多能力”直接等同于“当前场景更合适”。
进度接口要约定什么
任务启动接口返回“已受理”,不代表任务已经完成。推送消息应包含任务标识、事件类型和必要的数据,让页面能区分阶段更新、最终结果、失败和取消;任务无法合理估算百分比时,展示实际阶段即可,不编造进度。
进度也不等于模型逐字输出。一个事件可以表示工具开始调用、某个子任务完成,也可以携带最终报告。是否推送文本增量,取决于业务事件和模型接口的设计,SSE 与 WebSocket 都不会自动把阶段事件变成逐 token 输出。
部署时检查代理是否及时转发流式数据、连接空闲超时是否与心跳匹配。若源站已不断产生事件,页面却一次收到一大批,应先排查响应缓冲,而不是直接换协议。心跳能帮助维持连接、发现连接异常,不代表任务仍在正常推进。
断线后怎样继续展示
要恢复最新页面状态,可以让客户端重连后查询任务快照,取得当前阶段、终态和结果;若还需要完整过程,就为事件设置递增序号并保留历史,按客户端最后收到的序号补发,前端据序号去重。这是进一步的应用设计,两种协议都需要配套实现。
SSE 的 id 和重连机制能承载恢复游标,但服务端没有历史记录时,游标本身无法生成漏掉的事件。使用 fetch 自行消费流时,也不能直接假定具有原生 EventSource 的自动重连行为。页面断开连接是否同时取消后台任务,则应单独约定。
深度研搜Python
核心回答
深度研搜当前采用 HTTP 提交和取消任务,WebSocket 推送执行事件。研究过程可能调用网络搜索、数据库和知识库,页面需要看到正在使用哪个助手、哪个工具,以及最终结果。后端用 FastAPI 承接请求,借助 asyncio 后台任务执行研搜,再由统一的 monitor 把事件定向发给当前会话。
这里选择 WebSocket,实现上是把会话内的进度和结果集中到一条长连接。当前业务消息主要从服务端发往页面,接收端循环只处理心跳,没有通过这条连接提交或取消任务,因此 SSE 也足以承载这类单向通知。 不能把已有选型解释成双向业务交互的刚性要求。
一次进度怎样到达页面
- 先建立会话连接。 页面连接
/ws/{thread_id},ConnectionManager保存thread_id到连接对象的映射。随后提交POST /api/task,带上同一会话标识和研究问题。 - 把长任务交给后台。 接口通过
asyncio.create_task()调度run_deep_agent(),返回started和thread_id,不等待最终报告。任务运行时把会话标识和输出目录写入ContextVar,让深层工具能读取本次执行的上下文。 - 统一上报业务事件。 工具调用、子智能体调用和结果分别经过
report_tool()、report_assistant()、report_task_result()。monitor组装包含event、message、data和时间戳的monitor_event,读取当前thread_id,再找到对应连接发送。 - 回到连接所属的事件循环发送。 如果上报发生在连接所在的循环,使用
create_task();否则通过asyncio.run_coroutine_threadsafe()投递回该循环。页面按事件类型更新执行记录、最终回答或错误状态。
这套分工让工具只负责上报“发生了什么”,不需要自己保存或操作页面连接。任务接口和事件发送分别负责调度和通知。
推送了哪些内容
当前事件包括 session_created、tool_start、assistant_call、task_result、task_cancelled 和 error。例如调用网络搜索助手时展示助手名称,调用文件工具时展示工具名称,收到 task_result 后展示返回的完整结果。
虽然主智能体使用 astream() 执行,现有代码解析的是节点状态及其中的消息,再生成这些阶段事件,没有把模型 token 增量直接转发给页面。具体分支见主智能体执行。
心跳、重连和取消分别解决什么
前端定时发送文本 ping,服务端返回 pong;断开后,前端会用同一 thread_id 重新连接。连接管理器删除旧连接前还会比较对象身份,避免旧连接稍后触发断开,把已经登记的新连接一起删除。
但当前连接表和后台任务表都在进程内,没有持久事件日志,也没有按事件游标补发的接口。断线期间已产生的通知不会因为重连自动恢复,丢失最终结果事件时,页面也不能仅凭连接恢复就判断任务已完成。若要补齐,应增加任务状态查询或历史事件补发,而不是只缩短重连间隔。
取消通过 POST /api/task/{thread_id}/cancel 向后台任务发出协作式取消请求,关闭页面连接不会自动执行该接口。另一个同会话请求会先对旧任务调用 cancel(),随后创建新任务,未等待旧任务结束,因此不能据此保证两次任务严格互斥。这些行为独立于 WebSocket 或 SSE 的选择。
对应教程
- 《深度研搜:FastAPI 接口与项目闭环》:回看
HTTP命令接口、后台任务、monitor和会话连接的协作;本文进一步区分通信选型、连接重建与历史事件恢复。 - 《电商问数:API 接口基础与 FastAPI 入门》:参考流式响应与
SSE事件格式,说明长任务反馈也可通过单向事件流完成。
参考资料
- 国内:腾讯云:SSE 协议支持:参考单向流式推送及连接生命周期,说明任务进度可用
SSE,部署时还需考虑服务的执行时限。 - 国内:阿里云 OSS:普通模式和 SSE 模式说明:采用事件之间的分隔、
id、event和data的含义,解释事件流如何携带阶段与结果。 - 国内:Datawhale:自动化深度研究智能体:借鉴规划、执行、报告等阶段事件的划分,说明研究进度展示可以使用
SSE,无需等同于模型逐字输出。 - 国内:腾讯云:WebSocket 配置:参考全双工能力、代理空闲超时与心跳的配合,解释双向选型依据及长连接部署成本。
- 国外:MDN:Using server-sent events:核对
EventSource的默认重连、事件 ID 和心跳注释,区分协议机制与服务端历史补发。 - 国外:FastAPI:WebSockets:参考连接管理和断开处理,并根据其内存连接表示例说明进程内连接不具备持久恢复能力。
- 国外:Python 3.12:Coroutines and Tasks:核对任务调度、上下文复制和
cancel()的协作式语义,解释后台执行以及取消请求不等于已经停止。



