我会从架构角度回答:先结合当前代码里暴露的问题,再对照主流 Agent 产品/框架对长任务、流式输出、切换会话和恢复订阅的常见做法。主流做法不是让“HTTP/SSE 连接本身等于一次 Agent 执行”。更先进的 Agent 对话系统通常把它拆成两件事:
  1. Agent Run 在后端独立执行
  1. 前端只是订阅这个 Run 的事件流
也就是说,用户切走页面、断开 SSE、切换 history,都不应该影响后端那个 thread_id/run_id 继续生成。
典型架构是:

主流 Agent 的核心模式

最常见的是 Thread + Run + Event Log
  • thread_id:一段聊天历史。
  • run_id:这次用户消息触发的一次 Agent 执行。
  • message store:最终可恢复的对话消息。
  • event log:流式 token、tool_start、tool_end、interrupt、done 等事件。
  • subscriber:前端连接,只负责接收事件,不拥有执行生命周期。
所以用户从 A 会话切到 B 会话时:
  • A 的 run 继续在后端跑。
  • B 的 history 正常加载,不被 A 的 run 堵住。
  • 过一会切回 A,可以先 GET /history 拿已落库内容,再重新订阅 A 的未完成 run。
  • 如果 SSE 断了,可以用 Last-Event-ID 或 cursor 从事件日志补拉。

为什么这样做

因为 Agent 对话已经不是普通 request-response 了。它可能包含:
  • 长时间 LLM 推理
  • 工具调用
  • 人工确认 interrupt
  • 多步骤 workflow
  • 后台任务
  • 浏览器刷新/切换 tab
  • 移动端断网重连
如果把这些都绑在一个 /chat/completions HTTP 请求里,体验就会变脆:连接断了任务也断,任务慢了其它接口也可能受影响,前端状态也很难恢复。

对你当前项目的直接启发

你现在更像是:
更推荐改成:

先进实现一般还会有这些细节

  • 会话级 streaming 状态:不是全局 isStreaming,而是 streamingByThreadId
  • 可重连事件流:SSE/WebSocket 只是一种订阅,断开后可以继续订阅。
  • 事件持久化:token 或至少阶段性 assistant content 会写入后端,history 能看到部分回复。
  • 后台任务隔离:Agent run 放到 asyncio task、worker、队列或 durable execution 系统里。
  • 非阻塞工具调用:所有 LLM/tool/http/db 操作都必须 async,或丢到线程池/worker,不能堵 FastAPI event loop。
  • 明确取消语义:切走页面不取消;用户点“停止生成”才取消 run。
一句话总结:
现代 Agent 对话系统把“生成任务”做成后端可持久、可恢复、可订阅的 run,而不是依赖浏览器当前这条 SSE 请求活着。
 

作者:NA YANG

链接:

声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。

魔鬼出没的世界学会吃饭
Loading...
2025-2026|NA YANG
NA YANG|那洋的小世界🫠