架构手记(六):打破 I/O 阻塞——FastAPI 异步架构与 SSE 流式输出在大模型长耗时请求中的性能调优
一、 性能瓶颈:ReAct 架构带来的“延时梦魇”
在前面的架构手记中,我们利用 Vue3 状态机和 ReAct 单智能体,构建了严密的用药安全防线。但在内部测试时,一个严峻的性能问题暴露了出来:长耗时请求导致的 I/O 阻塞与极差的用户体验(UX)。
由于我们采用了 ReAct 框架,Agent 在内部需要经历 Thought(思考) -> Action(查图谱) -> Observation(接收规则) -> Final Answer(输出结果) 的完整链路。这意味着,从前端发起 HTTP 请求到拿到最终的 JSON,往往需要等待 5 到 8 秒。
如果是传统的同步框架(如 Django 或 Flask 默认模式),这 8 秒钟内当前的 Worker 线程会被完全挂起(Block)。如果此时门诊有多位医生同时开药,服务器的并发能力会瞬间触顶,导致后续请求排队甚至超时崩溃。
为了打破这个僵局,我从两个维度对系统底层架构进行了彻底的重构:后端底层的异步化(Asyncio)与通信协议的流式改造(SSE)。
二、 后端重构:FastAPI 的原生异步(Asyncio)释放并发能力
首先,在技术选型之初,我力排众议选择了 FastAPI,正是看中了它基于 Starlette 的原生异步特性。
在重构代码时,我将所有涉及网络请求(如调用大模型 API)和磁盘读取的 I/O 密集型函数,全部使用 async def 和 await 进行了重写。
Python
# FastAPI 异步非阻塞处理示例
@app.post("/api/analyze_prescription")
async def analyze_prescription(request: PrescriptionRequest):
# 使用 await 挂起当前协程,释放底层的 Event Loop 去处理其他医生的请求
agent_response = await llm_client.agenerate(
prompt=build_react_prompt(request.query)
)
# 图谱检索属于 CPU 密集型/内存访问,速度极快 (O(1)),同步执行即可
conflict_rules = check_graph_conflict(agent_response.extracted_entities)
return build_response(conflict_rules)
架构收益: 通过这种非阻塞(Non-blocking)的设计,当 Agent 在漫长地等待大模型厂商服务器返回结果时,FastAPI 的事件循环(Event Loop)会自动把线程让出来,去接收其他医生的网络请求。这让我们的单台轻量级服务器也能轻松扛住极高的门诊并发量。
三、 协议升级:引入 SSE 协议实现 ReAct “思维流”的可视化
虽然异步解决了后端的并发问题,但前端医生依然需要对着屏幕干等 8 秒。为了降低用户的“首字节感知延迟(TTFB)”,我决定抛弃传统的 HTTP 一次性返回模式。
针对 LLM 单向高频吐字的特征,我没有使用沉重且维持成本高的 WebSocket,而是采用了更轻量、基建成本更低的 SSE(Server-Sent Events)协议 来实现流式交互。
我将后端的响应改写为 Generator(生成器),通过 StreamingResponse 持续向前端推送大模型的 ReAct 状态:
Python
from fastapi.responses import StreamingResponse
import asyncio
async def react_agent_stream(query: str):
# 模拟 ReAct 工作流的流式推送
yield "event: thought\ndata: 正在解析主诉,提取药物实体...\n\n"
await asyncio.sleep(1) # 模拟解析耗时
yield "event: action\ndata: 调用本地知识图谱进行安全核查...\n\n"
# ... 执行图谱检索 ...
yield "event: result\ndata: {\"action_type\": \"BLOCK\", \"risk_level\": \"High\"}\n\n"
@app.get("/api/stream_analyze")
async def stream_analyze(query: str):
return StreamingResponse(react_agent_stream(query), media_type="text/event-stream")
四、 前端状态机的丝滑衔接
在前端 Vue3 中,我们利用原生 EventSource API 接收这个流式数据。这与我们在【手记(五)】中设计的状态机完美契合。
在 ANALYZING(分析态)下,界面不再是一个枯燥的转圈 Loading,而是像拥有了人类思维一样,实时渲染 Agent 的心智过程:
-
界面闪烁:“正在提取患者特征:孕妇...”
-
界面闪烁:“正在检索底层图谱:利巴韦林 ↔ 孕妇...”
-
状态突变:最终收到
event: result,前端状态机瞬间切入SUCCESS_BLOCK或CLARIFYING状态,弹出耀眼的红色阻断卡或追问弹窗!
五、 结语
通过 FastAPI 的底层异步化与轻量级 SSE 协议的引入,我们不仅彻底打通了长耗时请求造成的 I/O 阻塞,还极其优雅地将大模型“黑盒”的 ReAct 思考过程透明化,将原本令人焦躁的等待时间,转化为了一场极具科技感的视觉演示。
目前,系统的主干道已经完全畅通,高并发与高可用性得到了保障。但在真实的联调开发中,团队还是踩了不少暗坑。在下一篇【架构手记(七)】中,我将复盘我们在前后端联调时遇到的跨域问题、大模型 JSON 解析失败的边缘场景(Edge Cases),以及我们是如何用工程手段逐一化解的。
更多推荐

所有评论(0)