用FastAPI接ollama大模型,asyncio难题让我崩溃了吗?
摘要:很多人在用FastAPI调用外部API时会遇到阻塞、超时甚至崩溃。本文从asyncio原理讲起,通过FastAPI+httpx异步调用本地ollama模型,带你一步步搭建一个对话窗口,并分享我踩过的坑和解决方案。
📝 摘要:很多人在用FastAPI调用外部API时会遇到阻塞、超时甚至崩溃。本文从asyncio原理讲起,通过FastAPI+httpx异步调用本地ollama模型,带你一步步搭建一个对话窗口,并分享我踩过的坑和解决方案。
嘿,朋友们,我是一枚程序媛👩💻。今天咱们来聊聊最近让我又爱又恨的 asyncio,尤其是用FastAPI去调用本地ollama大模型时踩的那些坑。你是不是也觉得FastAPI既然是异步框架,调用外部API应该很丝滑?结果一上线,接口卡死、超时、甚至服务直接挂掉?别急,这篇文章就是来帮你排雷的。
🎯 先说个真事儿
前阵子我做一个AI对话服务,用FastAPI接本地的ollama模型。刚开始图省事,直接用 requests 库同步调用,结果并发上来后,CPU直接飙满,请求排长队,最后服务彻底没响应。后来换成 httpx 异步客户端,以为万事大吉,结果又遇到了流式解析错误、超时设置不当的问题……折腾了两天,总算摸清了门道。
今天就把这些经验掰开揉碎讲给你听,保证你能少走弯路。
📌 本文能帮你解决什么
✅ 搞懂asyncio在FastAPI中到底怎么工作的(用餐厅比喻)
✅ 正确使用httpx异步调用外部API,避免阻塞
✅ 处理ollama流式响应,实时返回给前端
✅ 搭建一个简单的对话窗口,可以直接运行
🚨 第一部分:为什么异步调用外部API那么容易挂?
很多新手(包括当年的我)以为用了FastAPI就是异步了,路由函数前面加个 async def 就万事大吉。但真正的坑在于:如果你在异步函数里用了同步的IO操作(比如requests.get),事件循环就会被阻塞,整个服务都会卡住。
就好比你去餐厅吃饭,服务员(线程)就一个人,他帮你点完菜后不去服务其他桌,而是站在厨房门口等你的菜做好。那其他桌的客人就只能干等着。这就是典型的阻塞。
所以,调用外部API必须用异步HTTP客户端,比如 httpx.AsyncClient 或 aiohttp。但光是换库还不够,还得注意超时、连接复用、流式处理等细节。
🧠 第二部分:先懂原理,再动手
🍽️ 用餐厅比喻理解asyncio
想象一个餐厅只有一个服务员(一个线程)。他负责点菜、上菜、结账。如果每个客人点完菜后服务员都站在旁边等,那效率极低。聪明的服务员会:
▪️ 给客人A点完菜后,告诉厨房做菜(发起网络请求)
▪️ 然后立刻去服务客人B(交出控制权,await)
▪️ 等厨房喊“菜好了”(请求返回),再继续给A上菜
这就是asyncio的核心:在等待IO时让出事件循环,去执行其他任务。 所以你的异步代码里必须要有 await 点,否则就会阻塞。
🔧 httpx.AsyncClient 的正确姿势
httpx 是requests的异步兄弟。但有个坑:很多人每次请求都创建新的client,这会导致连接无法复用,性能反而更差。正确的做法是:全局复用一个client,或者用依赖注入确保单例。
另外,ollama的API支持流式返回,我们需要用 client.stream() 方法,并且实时解析JSON行。
⚡ 第三部分:实战!FastAPI + ollama 对话窗口
假设你已经本地运行了ollama,并且拉取了模型(比如 qwen3:1.7b)。我们来实现一个简单的聊天接口,并提供一个简陋但可用的前端页面。
1️⃣ 项目结构
.
├── main.py # FastAPI应用
├── static/ # 存放HTML
│ └── chat.html
└── requirements.txt
2️⃣ 安装依赖
fastapi
uvicorn
httpx
jinja2 # 可选,为了简单我们直接返回HTML
3️⃣ 编写后端 main.py
这里要特别注意:httpx.AsyncClient 要声明为全局单例,并在应用关闭时清理。
