AI大模型应用部署之Flask框架使用以及FastApi比较
如何将机器学习算法模型深度学习模型制作、打包成一个可以直接交付的软件、并可以快速部署, 这里就要使用到Flask以及Docker,相关工作流程架构图如下:

相关技术如下:
- 了解问题解决的工作流程
- 使用Flask框架将服务封装成接口
- 使用Docker技术将服务容器化介绍
一.Flask服务接口
如何使用 Flask 框架来实现从浏览器远程访问、使用我们的模型服务
1.Flask作用与流程
1.1. 为什么要学习 Flask?
对于模型部署来说,Flask 的作用是什么呢?
当训练好一个模型后,为了能够让它能够对外提供服务,一般会将其部署到某一台存在于互联网的服务器上,想要使用该服务的人可以通过网络来远程和部署在服务器的模型进行交互以获得想要的服务
此时,就需要在服务端去编写一个用于和用户交流的应用程序,而 Flask 就是一款可以解决此问题的 Web 框架,它具有轻便、灵活、安全且容易上手,能够在短时间内构建出一个 Web 服务站点,通过 Flask 用户可以提交数据到服务端,服务端可以处理用户请求,并返回处理结果
1.2. 流程
- Flask 在整个业务流中承担的角色是什么?
- 用户的请求和响应到底是一个什么样的流程?

简要的流程如下:
(1)浏览器给服务器监听在某个端口上的应用程序发送请求
(2)服务器程序接收到请求之后,将请求转发给具体处理该请求的程序
(3)该程序解析请求,然后找到该请求对应的处理函数,并执行该函数
(4)将函数的返回值返回给服务器软件,服务器软件再将返回值返回给浏览器
2.Flask入门
Flask 是使用 Python 语言编写的轻量级的 Web 应用程序框架。使用该技术,可以将训练得到的模型封装一个能够提供预测的网络应用程序,即: 可以对外提供一个服务接口
Flask中文文档:欢迎使用 Flask — Flask 0.10.1 文档
安装命令如下:
pip install Flask==2.1.0
2.1. Flask Hello World
首先,创建一个简单的 Flask 应用程序:
# 1.导入依赖包
from flask import Flask
# 2.实例化Flask
app = Flask(__name__)
# 3.定义路由, 这里使用根路径, 请求方式为Get
@app.route('/', methods=['GET'])
# 4.请求响应函数, index 代表根路径函数
def index():
return 'Hello World'
if __name__ == '__main__':
# 5. 启动服务, 监听端口, 这里没有写端口,则默认监听5000
app.run()
运行该程序,发现报错:
ImportError: cannot import name 'url_quote' from 'werkzeug.urls' (D:\ProgramData\anaconda3\envs\my_conda\Lib\site-packages\werkzeug\urls.py). Did you mean: 'unquote'?
上面的错误是由于当前环境中安装的
Flask和Werkzeug版本不兼容导致的
在较新的
Werkzeug(3.0.0及以上版本)中,url_quote函数被移除或重命名了,而旧版本的Flask仍然尝试从旧路径导入它,从而引发报错。
可以通过以下两种方案之一来解决这个问题:
方案一:降级 Werkzeug 版本(推荐用于旧版 Flask 项目)
如果正在学习或使用较老版本的 Flask 教程,建议将
Werkzeug降级到兼容的版本,可以在终端中运行以下命令:
pip install Werkzeug==2.2.2
(注:部分极老版本的 Flask 可能需要降级到
Werkzeug==2.0.3,如果 2.2.2 仍报错可以尝试此版本)
方案二:升级 Flask 版本(推荐用于新项目)
如果希望使用最新的依赖库,可以直接将
Flask升级到最新版本。新版 Flask 已经修复了依赖关系,能够完美兼容新版Werkzeug:
1pip install -U Flask
额外建议:使用虚拟环境
由于使用的是 Anaconda 环境,为了避免全局包版本冲突(例如其他项目依赖不同版本的 Werkzeug),强烈建议为每个 Flask 项目创建独立的虚拟环境
如果使用的是 Conda,可以执行:
conda create -n flask_env python=3.10
conda activate flask_env
pip install flask
这样就能彻底隔离依赖,避免后续开发中再次出现类似的版本冲突问题
针对上面的解决方案,这里使用的是方案二解决: 运行命令pip install -U Flask后, 启动程序,输出结果如下:
* Serving Flask app 'flask_demo'
* Debug mode: off
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on http://127.0.0.1:5000
在浏览器输入: http://127.0.0.1:5000, 得到如下显示:

这个结果表示: 通过 http://127.0.0.1:5000 访问了定义在服务器端一个服务函数,可以理解为:远程调用了一个 Python 函数,并且该函数返回了函数的运行结果
接下来,详细讲解各部分的含义,方便后续代码的编写
2.2. Flask 应用的创建
# 实例化Flask
app = Flask(__name__)
上述代码表示,创建了一个 Flask 应用实例,也可以理解为所有的请求都是由该实例来处理,接下来,调用该实例的 run 方法:
if __name__ == '__main__':
app.run()
# 还可以修改访问的ip,以及端口
app.run(host='0.0.0.0', port=6000) # host='0.0.0.0' 访问服务端所有ip, port=6000 指定访问的端口为6000
run 方法会启动一个用于开发测试的服务器,该服务器会在某个端口监听,来获得客户端发送过来的请求。其流程如下:
请求 --> 测试服务器 --> Flask 应用
注意:在开发阶段使用该测试服务器,部署时,将会使用 Gunicorn 服务器,后面演示部署时,就暂且使用开发服务器
2.3. Flask 请求处理函数
当 Flask 应用获得了客户的请求,此时就需要将该强求转发给某个具体的业务函数来处理,如何定义不同的请求和具体的业务函数之间的映射关系呢?
@app.route('/', methods=['GET'])
def index():
return 'Hello World'
上述代码中,index 函数是我们定义的具体业务函数,它的处理结果就是返回一个字符串 "Hello World".
如何将某个请求映射到该函数呢?首先,得清楚,不同的请求对应的 url 是不同的,例如:
看新闻的请求: http://xxx.xxx.xxx.xxx:5000/news
听音乐的请求: http://xxx.xxx.xxx.xxx:5000/music
当请求的 url 是 /news 时,就可以使用 news 函数来处理,请求的 url 是 /music 时,就可以使用 music 函数来处理
注意: 此处的 URL 是由自己定义的。假设,希望用户访问 /phone-price 时,就可以获得模型的预测结果,就可以编写一个函数来和该 URL 对应起来
定义请求 URL 和具体业务函数映射关系就是使用 app.route 装饰器,该装饰器第一个参数就是自定义的请求 URL,第二个参数 methods 为请求类型
请求类型有多种,主要用到了两种请求类型:GET、POST
(1)GET 请求: 用于只获得数据,而不需要向服务端提交任何数据
(2)POST 请求: 用于向服务端发送请求数据,例如:要进行垃圾邮件分类,需要将邮件数据数据发送给服务端,此时应该使用 POST 方式
3.Flask 表单使用
在进行模型部署时,经常需要用户传递数据,例如: 一张图片、一封邮件、一条新闻等,然后由部署的模型接收数据,并预测,最后给出响应。,表单的用途就是客户用于提交数据的内容,在这里,需要学习:
- 编写表单
- 处理表单
(1). 编写表单
当用户访问一个指定的 URL 时,需要返回给用户一个表单,让用户在表单中填写相关的内容,并点击某个按钮,将数据提交给服务器处理:步骤如下:
- (1). 创建 Flask 项目,并创建 app.py 文件,代码内容如下:
- (2). 在 app.py 同级目录下创建 news_submit.html 文件
app.py 内容如下:
from flask import Flask
# 初始化 Flask 应用
app = Flask(__name__)
@app.route('/news_submit', methods=['GET'])
def news_submit():
with open('news_submit.html', 'rb') as file:
content = file.read()
return content
if __name__ == '__main__':
app.run()
news_submit.html 内容如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>新闻内容提交</title>
</head>
<body>
<form action="" method="post">
<textarea name="content" cols="40", rows="10"></textarea> <br />
<input type="submit" value="提交新闻" />
</form>
</body>
</html>
运行 app.py 文件,并打开浏览器输入: http://127.0.0.1:5000/news_submit, 显示内容如下图所示:

(2). 处理表单
首先需要接收到表单的数据,然后进行处理,当接收到表单数据之后,给用户显示到浏览器内
在 .html 文件内,修改 form 表单的 action 属性如下:
<form action="/news_handle" method="post">
<textarea name="content" cols="40", rows="10"></textarea> <br />
<input type="submit" value="提交新闻" />
</form>
在 app.py 中增加额外的业务处理函数 news_handle, 专门用来接收表单发来的数据
from flask import Flask
from flask import request
# 初始化 Flask 应用
app = Flask(__name__)
@app.route('/news_submit', methods=['GET'])
def news_submit():
with open('news_submit.html', 'rb') as file:
content = file.read()
return content
# 表单提交
@app.route('/news_handler', methods=['POST'])
def news_handler():
data = request.form.get('content')
print(data)
return 'Received News Data!'
if __name__ == '__main__':
app.run()
重新运行程序,在浏览器输入 http://127.0.0.1:5000/news_submit,并在页面内填写任意文本内容,如下图所示:

点击提交,就可以在后台看到以下内容:

这就表示后台获得了用户提交的数据内容
响应给前端的数据内容如下:

注意: 客户如果需要提交数据给后台,一般都是通过表单的形式提交
(3). 获得其他形式的数据
上面编写了一个用于获得一大串文本内容的表单,假设: 需要用户提交给后台很多数据,就可以编写如下表单:
app.py 内容:
from flask import Flask
from flask import request
# 初始化 Flask 应用
app = Flask(__name__)
@app.route('/userinfo_submit', methods=['GET'])
def userinfo_submit():
with open('userinf_submit.html', 'rb') as file:
content = file.read()
return content
@app.route('/userinfo_handle', methods=['POST'])
def userinfo_handle():
name = request.form.get('name')
age = request.form.get('age')
sex = request.form.get('sex')
print('姓名:', name)
print('年龄:', age)
print('性别:', sex)
return 'Received UserInfo Data!'
if __name__ == '__main__':
app.run()
userinfo_submit.html 内容如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>用户信息内容提交</title>
</head>
<body>
<form action="/userinfo_handle" method="post">
姓名: <input type="text" name="name" /> <br />
年龄: <input type="text" name="age" /> <br />
男:<input type="radio" name="sex" value="男" /> 女:<input type="radio" name="sex" value="女" /> <br />
<input type="submit" value="提交" />
</form>
</body>
</html>
4.Flask 服务交付
在进行模型部署时,首先用户传递给后台数据,大部分情况是json格式的,例如:
{
"content":"新闻内容"
}
然后由部署的模型接收数据,并预测,最后返回一个json格式的输出,在这里,需要学习:
- json格式数据接收
- json格式数据返回
首先需要接收到json格式的数据,然后进行处理,当接收到数据之后,就给用户返回对应的json格式的输出
在 .html 文件内,修改 form 表单的 action 属性如下:
<form action="/news_handle" method="post">
<textarea name="content" cols="40", rows="10"></textarea> <br />
<input type="submit" value="提交新闻" />
</form>
在 app.py 中增加额外的业务处理函数 news_handler, 专门用来接收表单发来的数据:
from flask import Flask, Response, json
from flask import request
# 初始化 Flask 应用
app = Flask(__name__)
app.config['JSON_AS_ASCII'] = False # 解决中文乱码问题
@app.route('/news_submit', methods=['GET'])
def news_submit():
with open('news_submit.html', 'rb') as file:
content = file.read()
return content
@app.route('/news_handler', methods=['POST'])
def news_handler():
# 获取json格式的输入
request_json = request.get_json()
data =request_json['content']
# 定义响应数据格式
respose_data = {
'status': 'success',
'content': data,
}
# 返回请求数据
return Response(status=200, respose=json.dumps(respose_data,sort_keys=False))
if __name__ == '__main__':
app.run()
# 客户端Client
import requests
import time
# 定义请求url和传入的data
url = "http://127.0.0.1:5000/news_handler"
data = {"content": "中国大满贯女单冠军"}
start_time = time.time()
# 向服务发送post请求
res = requests.post(url, json=data)
cost_time = time.time() - start_time
# 打印返回的结果
print('文本类别: ', res.text)
print('单条样本耗时: ', cost_time * 1000, 'ms')
客户端打印返回结果:

除了使用客户端访问外,实际工作中一般使用Postman/ApiFox进行服务测试
使用Postman向 http://127.0.0.1:5000/news_handler发送请求,并返回响应结果
请求体:
{
"content":"新闻内容"
}
响应体:
{
"Status": "success",
"content": "新闻内容"
}
Postman下载地址:Download Postman | Get Started for Free
ApiFox下载地址:Apifox官网
操作如下图所示:

5.Flask 服务封装
接下来将通过自己训练的《投满分项目-新闻文本分类器》通过 Flask 进行部署,使得用户能够通过浏览器提交新闻文本数据,模型能够对新闻进行分类,并返回给用户结果, 业务实现代码见: 【NLP自然语言处理-投满分项目】2.模型训练与测试之随机森林&FastText模型的应用
二.Flask和Fastapi比较
Flask: 同步的框架,如果有并行处理的请求,后到的请求会被阻塞
FastAPI: 异步框架,如果有并行处理的请求,它可以进行处理
以上两个框架等同于java中的servelet, 如果生产使用,还需要一个服务器的服务(java:servelet + tomcat = spring-core)
生产级的部署:
- flask一般和gunicorn(相当于tomcat)一起使用
- fastapi一般和unicorn(相当于tomcat) 一起使用
对于IO密集型任务,使用fastapi, 对于CPU密集型,使用flask
1.FastAPI和Flask的区别和联系
| 特性 | Flask (同步) | FastAPI (异步) |
|---|---|---|
| 核心机制 | 同步阻塞 (一步接一步) | 异步非阻塞 (利用等待时间处理其他事) |
| 比喻 | 单任务 | 多任务 |
| 代码风格 | 标准 Python 函数 (def) |
使用 async def 和 await |
| 擅长场景 | CPU 密集型任务 (需要持续计算) | I/O 密集型任务 (大量等待时间,如网络、磁盘、LLM) |
| 并发能力 | 较低 (依赖多进程/多线程) | 非常高 (单个进程能处理大量并发连接) |
| 性能 | 对于 I/O 密集场景,性能较低 | 对于 I/O 密集场景,性能非常高 |
| 学习曲线 | 相对平缓 (同步逻辑易理解) | 稍陡峭 (需要理解 async/await 和事件循环) |
| 数据校验 | 需额外库 (如 Flask-WTF, Marshmallow) | 内置 Pydantic,自动数据校验和文档生成 |
联系:
- 它们都是 Python Web 框架,用来创建 API 或 Web 应用
- 它们都能接收 HTTP 请求,根据 URL 路由到相应的处理函数,然后返回 HTTP 响应
- Flask 也可以通过一些方式(比如使用
gevent或asyncio扩展)实现类似异步的功能,但不是其天生设计,FastAPI 也可以运行同步代码(它会自动在线程池里运行,避免阻塞主循环)
2.FastAPI和Flask示例
想象一下你在经营一家餐厅:
Flask 的同步路由 (Synchronous): 就像只有一个厨师,而且他一次只能做一个菜
工作方式:
- 第一个客人点单(一个请求来了)
- 厨师开始做这道菜(处理这个请求)
- 关键点: 在这道菜完全做好、端给客人之前,即使第二个客人已经点好单,厨师也不会开始做第二道菜,他必须等第一道菜完成,如果第一道菜是“佛跳墙”(一个很慢的操作,比如等待数据库、等待外部 API、或者等待大模型生成结果),那么后面所有的客人都要排队等着
代码示例 (简化示意):
# Flask (同步) from flask import Flask import time app = Flask(__name__) @app.route('/cook_sync') def cook_sync(): print("开始做菜 A...") time.sleep(5) # 模拟做一个很慢的菜 (比如等大模型响应) print("菜 A 做好了!") return "菜 A 完成" # 当一个请求访问 /cook_sync 时,服务器会卡住 5 秒, # 这期间无法处理其他请求(除非你开了多个服务员/进程)。特点: 简单直接,符合通常的编程思路(一步接一步),但是,如果某个步骤很慢(等待 I/O 操作),整个流程就会被阻塞,效率不高,无法同时处理很多“等待中”的任务
FastAPI 的异步路由 (Asynchronous): 就像一个很厉害的厨师,他可以同时处理多个任务
工作方式:
- 第一个客人点单(请求 1),厨师开始做菜 A,比如先把米饭放进电饭煲(开始一个需要等待的操作,比如请求大模型)
- 关键点: 在等待米饭煮熟(等待大模型响应)的时候,厨师不会闲着,他会立刻去看有没有其他客人的订单(处理请求 2)
- 第二个客人点单(请求 2),厨师开始处理菜 B,比如开始洗菜(执行一些快代码)
- 如果菜 B 也需要等待(比如等水烧开),厨师又会去看有没有其他事做(处理请求 3 或查看米饭是否熟了)
- 一旦米饭煮熟了(大模型的响应回来了),厨师就会回来继续完成菜 A 的后续步骤
代码示例 (简化示意):
# FastAPI (异步) from fastapi import FastAPI import asyncio # Python 的异步库 app = FastAPI() @app.get('/cook_async') async def cook_async(): # 注意这里的 async print("开始做菜 A (异步)...") await asyncio.sleep(5) # 模拟做一个很慢的菜,但这里用 await # 表示“在这里等待,但允许服务器去做别的事” print("菜 A (异步) 做好了!") return "菜 A (异步) 完成" # 当一个请求访问 /cook_async 时,服务器在 `await asyncio.sleep(5)` 这里 # 会“暂停”这个任务,转而去处理其他进来的请求。5秒后,当等待结束, # 服务器会回来继续执行这个任务。这使得服务器能同时处理很多“等待中”的请求。特点: 需要使用
async和await关键字,特别擅长处理那些包含等待时间(I/O 密集型,如网络请求、读写文件、数据库查询、等待大模型响应)的任务,同一个厨师(服务器进程)可以同时“照看”很多道菜,大大提高了效率(并发能力)
3.在利用大模型(LLM)部署时怎么选择?
答案:强烈推荐使用 FastAPI (异步)
原因:
- LLM 调用是典型的 I/O 密集型操作: 当你的 API 调用一个大模型(无论是本地部署的还是云上的 API),你的程序需要等待模型进行计算并返回结果,这个等待时间可能从几百毫秒到几秒甚至更长
- 异步能极大提高并发处理能力:
- 使用 Flask (同步),当一个请求在等待 LLM 响应时,处理这个请求的那个服务器进程/线程就完全卡住了,不能为其他用户服务,如果你想同时服务 100 个用户,你可能需要启动100个(甚至更多)Flask 工作进程,这会消耗大量内存和 CPU 资源
- 使用 FastAPI (异步),当一个请求
awaitLLM 的响应时,服务器会把这个任务暂时挂起,然后立即去处理其他用户的请求,当 LLM 的响应回来后,服务器再继续处理之前挂起的任务。这样,一个 FastAPI 进程就能同时处理成百上千个“等待 LLM 响应”的并发请求,资源利用率极高- FastAPI 的内置特性很方便: FastAPI 基于 Pydantic 进行自动的数据校验和文档生成(Swagger UI / ReDoc),这对于定义清晰的 LLM 输入(如 prompt、参数)和输出结构非常有帮助,能减少很多模板代码,提高开发效率和接口质量。
简单来说:
- 如果希望LLM 应用能够同时服务很多用户,并且高效地利用服务器资源,FastAPI 的异步特性是巨大的优势
- 如果只是做一个内部小工具,用户量极少,或者团队完全不熟悉异步编程且不愿意学习,用 Flask 也能实现功能,但性能和并发能力会差很多,需要更多服务器资源来弥补
结论: 对于需要调用大模型的场景,FastAPI 的异步模型几乎是必然的选择,因为它能更好地应对 LLM 调用带来的等待时间,实现高并发、高性能的服务
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)