如果你手上已经有一个训练好的 Python AI 模型,想让同事或用户直接在浏览器里上传文本、点一下按钮看到预测结果,Streamlit 和 Gradio 是两条很实用的路线。它们的价值不是替代 Flask、FastAPI,也不要求你掌握 React 或者 Vue,而是把最常见的“网页表单 + 推理函数 + 结果展示”封装成几段 Python 代码,让几小时内出一个能点、能看、能部署的 Web 应用成为现实。
我用这类工具做过不少模型 Demo,最大的感受是:模型本身能不能用,和模型能不能被别人用,完全是两件事。算法文件放在笔记本里,再厉害也只有自己能跑;一旦你想让产品同学、业务同事或者远程用户也能接触到,就一定会需要一个界面。做这个界面,人力成本和迭代速度往往比想象中更关键。Streamlit 和 Gradio 解决的就是这个问题。
这篇内容围绕“怎么把一个 Python AI 模型变成 Web 应用”展开,从环境准备、最小 Demo、模型加载、批量输入一直讲到部署和排查。不是只罗列功能,而是按实际落地顺序拆开讲。
1. 先判断:Streamlit 和 Gradio 到底怎么选
1.1 一个偏“数据应用”,一个偏“模型接口”
第一次接触这两个库的人,很容易被风格差异劝退。Streamlit 给我的感觉更像“用 Python 写数据应用”:页面结构可以自由组合,放标题、放输入框、放图表,然后通过按钮或输入事件触发逻辑。适合做带报表、可视化、多页面跳转的内部工具。
Gradio 更像是“给模型做展示接口”:你把一个 Python 函数告诉它,告诉它输入是什么类型、输出是什么类型,它就能生成一个网页。不需要自己写按钮逻辑,文本、图片、音频、视频这类常见输入输出都有现成组件。
如果你的目标是做一个分类 Demo,给测试者上传图片、语音或文本后看返回结果,G radio 起步更快。如果你的目标是一个数据后台,比如模型训练日志看板、样本标注工具、带着筛选条件的结果分析页面,Streamlit 会更顺手。
1.2 从实际输入输出方式去选,不要只凭热度
结合热度词里经常出现的“Streamlit 菜鸟教程”“Gradio 如何部署本地模型”这类搜索,可以看出大部分人其实不是缺功能文档,而是缺选型判断。
选型时建议明确几个问题:
- 用户主要输入文本,还是图片、音频、视频、文件?
- 是单个用户做测试,还是一群人并发访问?
- 除了推理结果,是否需要展示图表、统计、历史记录?
- 是否要做登录限制、访问权限、多页面?
如果只是文本输入 + 预测结果,两个都能做。如果涉及图片、音频、视频等多模态输入,Gradio 的组件更贴近模型测试场景,Input 和 Output 都能直接选类型。如果要做复杂的企业后台,则 Streamlit 的布局能力更强。
下面用一个表格做简单对比:
| 比较点 | Streamlit | Gradio |
|---|---|---|
| 上手难度 | 需要理解页面元素和事件 | 最快一个函数就能出界面 |
| 页面布局 | 类似数据应用,可做多页面 | 以单个/多个 Interface 为主 |
| 多模态组件 | 通过文件上传等方式处理 | 原生支持文本、图像、音频、视频等 |
| 登录控制 | 一般靠应用层或第三方方案 | launch 时提供 auth 参数可加简单限制 |
| 典型场景 | 数据分析、模型看板、内部工具 | 模型 Demo、接口演示、快速共享 |
实际使用中,两个可以组合:Gradio 负责对外演示,Streamlit 负责复杂业务后台。这不冲突。
2. 开始前把环境理干净:Python、虚拟环境和文件结构
2.1 先确认 Python 版本,再装包
新手最容易卡住的位置不是模型代码,而是 Python 环境。很多报错看起来是某个库不存在,实际是 Python 版本不匹配、pip 装到了错误环境、或者系统里同时存在多个 Python 解释器。
先打开终端确认当前环境:
python --version pip --version如果电脑上装了 VSCode、PyCharm、Anaconda 多个工具,很容易出现终端里用的是 Python 3.9,编辑器里却指向另一个环境。建议新建一个虚拟环境,避免依赖互相影响。
python -m venv .venvWindows 下激活:
.venv\Scripts\activatemacOS 或 Linux 下激活:
source .venv/bin/activate这里给的是通用做法,Python 版本建议选 3.9 或 3.10 以上。实际兼容范围要以你准备安装的 Streamlit、Gradio 当前版本文档为准。先把版本确认清楚,比一上来就安装库更重要。
激活虚拟环境后,再安装两个库:
pip install streamlit gradio如果是在已有项目里新增,不要随手动pip install -U把所有依赖全部升级,避免把项目里其他依赖冲掉。
2.2 建议的目录结构
模型代码和页面代码不建议写在一个文件里。虽然几百行的 Demo 也能写成一个 py 文件,但后面扩展批量任务、接口调用、更换模型时,会非常痛苦。
我习惯用这样的目录结构:
my_ai_demo/ ├── app.py # Streamlit 页面 ├── gradio_app.py # Gradio 页面 ├── predictor.py # 模型加载和推理逻辑 ├── models/ # 本地模型文件 │ └── model.pkl └── requirements.txt # 依赖锁定predictor.py负责加载模型和调用模型,页面文件负责接收用户输入、展示结果。这样无论是换 Streamlit 还是 Gradio,推理层不用重写,只需要替换页面文件的调用方式。
3. Streamlit 版:把模型推理包装成一个可点击的表单页面
3.1 把模型加载与推理独立成模块
先不急着写页面,先让模型跑通一次。以最简单的文本分类模型为例,假设你有一个训练好的model.pkl文件。predictor.py可以长这样:
import joblib _model = None def load_model(): global _model if _model is None: _model = joblib.load("models/model.pkl") return _model def predict(text: str): model = load_model() label = model.predict([text])[0] return str(label)这是一个通用示例,实际模型可能是 PyTorch、ONNX 或大模型 API。核心思路是把“加载”和“推理”拆开,避免页面每次刷新都重新加载模型。
3.2 用 Streamlit 写第一版 Web 界面
app.py里可以写一个最简版本:
import streamlit as st from predictor import predict st.title("文本分类 Demo") st.write("输入一段文本,点按钮查看模型预测结果。") text = st.text_area("待分类文本", value="") if st.button("开始预测"): if not text.strip(): st.warning("请先输入文本") else: with st.spinner("模型推理中..."): result = predict(text) st.success(f"预测结果:{result}")页面启动后,用户在输入框输入内容,点击按钮,程序调用predict函数,然后把结果呈现在页面上。没有写任何 HTML、JavaScript,也没有路由和接口层。
这套流程背后的运行逻辑是:Streamlit 每次交互都会重跑整个页面脚本。所以页面里才建议用st.button控制触发范围,避免一上来就自动推理。按钮没有点击时,页面只负责渲染。
3.3 启动命令与验证标准
启动 Streamlit 应用:
streamlit run app.py默认地址通常是http://localhost:8501,终端会显示完整的本地地址。如果端口冲突,可以手动指定:
streamlit run app.py --server.port 8502第一次跑通后,用三条标准判断是否成功:
- 页面能否正常打开。
- 输入一条测试文本后,按钮能否触发推理。
- 页面是否显示预期结果,并且没有刷新白屏。
只要这三条都通过,Streamlit 这边的路就算走通了。
4. Gradio 版:几行代码先立一个接口
4.1 Interface 是最快入口
Gradio 写起来通常更短。gradio_app.py可以这样:
import gradio as gr from predictor import predict def classify(text: str): return predict(text) demo = gr.Interface( fn=classify, inputs=gr.Textbox(label="输入文本"), outputs=gr.Label(label="分类结果"), title="文本分类 Demo", ) if __name__ == "__main__": demo.launch()运行方式:
python gradio_app.py默认地址一般是http://127.0.0.1:7860。到这里你会发现,Gradio 只需要把一个普通 Python 函数包进Interface,就能自动生成输入框、按钮和结果展示区域。
4.2 理解 inputs、outputs 和 fn 的关系
Gradio 的核心逻辑是“函数包装”:页面是把fn接到界面上的桥梁。你给fn一个输入,它返回一个输出,Gradio 负责把前端交互和 Python 调用之间的转换处理好。
常见参数:
inputs:接收用户输入的类型,比如 Textbox、Image、Audio、Video、File。outputs:展示结果的类型,比如 Textbox、Label、Image、Audio。title:页面标题。examples:提供几个示例输入,方便用户一键测试。
实际用的时候,不要照抄复杂例子,先从一个基础函数开始。函数返回类型和outputs类型不一致,是 Gradio 最常见的报错来源之一。
例如文本分类函数返回一个字符串,outputs用gr.Textbox没问题;如果返回一个字典或列表,就要用对应的组件类型。遇到输出为空或显示异常,优先检查这里。
4.3 launch 参数和本地部署注意点
本地运行时可能需要关注几个launch参数:
demo.launch( server_name="0.0.0.0", server_port=7860, )server_name设置为"0.0.0.0",代表监听服务器上所有网络地址,这样局域网内其他设备可以通过服务器 IP 访问。默认"127.0.0.1"时只能本机访问,安全但不够方便。
如果你暂时不打算部署公网,先用127.0.0.1测试即可。需要给同事演示时,可以在同一个局域网下改成0.0.0.0,并注意访问路径是否是http://你的IP:端口。实际网络环境不同,这一步需要以现场条件为准。
注意:不要一上来就试公网部署。先在本地和局域网跑通,确认输入输出正常,再考虑外网访问。
5. 本地模型加载:代码之外最常出问题的环节
5.1 加载和推理的分离为什么重要
很多 Demo 把模型加载写在函数外面,页面脚本每次交互都会执行一遍。Streamlit 中这不是小问题,因为页面脚本会在按钮点击后重跑,模型加载如果写在顶层,就会重复加载,耗时和内存都会成倍增加。
Streamlit 提供了缓存装饰器:
import streamlit as st @st.cache_resource def load_model(): return joblib.load("models/model.pkl")这样模型加载一次后,后续调用直接复用缓存结果。页面交互不再重复读模型文件。
在 Gradio 中,模型加载可以放在predict函数外部做一次,也可以借助 Python 模块的全局变量。关键是让模型只初始化一次,不要在每次推理时都重新加载。
5.2 输入格式不对的隐蔽坑
模型代码本身跑通过,不代表网页调用就能成功。Web 界面拿到用户输入的文本,有可能带额外换行、前后空格、编码不同。图片输入更敏感,上传图片的尺寸、通道、格式、色彩空间都可能和训练时不匹配。
我遇到过一个案例:离线测试时图片正常,上传到 Gradio 后模型返回形状错误。排查到最后,不是推理逻辑问题,而是页面组件输出的是 RGB 图片,模型内部却按 BGR 处理。这个坑必须在封装推理函数时提前处理,而不是等报错再回头找。
稳妥的做法是在predict函数入口统一做数据校验:
- 文本先
strip(),空输入直接返回提示。 - 图片先转换尺寸和通道格式,再做预测。
- 文件上传类任务先检查扩展名,再读取内容。
这样即使输入不干净,函数也能给出明确提示,而不是抛一个看不懂的底层报错。
5.3 模型体积和资源占用怎么估算
如果模型文件只有几十 MB,CPU 环境下几秒内能完成加载。如果是大语言模型或视觉模型,几个 GB 的权重文件加载后,内存或显存占用会很高,第一次打开页面可能要等很长时间。
判断标准不是“能不能跑”,而是:
- 加载耗时是否还在可接受范围。
- 推理时内存、显存是否被打满。
- 多个用户同时请求时,服务器是否还能响应。
低配机器可以跑通,但不代表适合做多人并发。如果只是学习或做内部演示,默认配置通常够用;如果要持续服务,就得考虑独立显卡、足够的内存、以及任务队列。
6. 批量场景:从 Demo 走向真实任务
6.1 文件上传批量预测
单个输入框适合测试,但真实业务里用户往往有一批文本或图片需要处理。这时可以加一个文件上传组件,读入后逐条推理,并把结果合并导出。
Streamlit 里可以用st.file_uploader接收文件,再逐行处理。示例逻辑:
uploaded_file = st.file_uploader("上传文本文件", type=["txt", "csv"]) if uploaded_file is not None: content = uploaded_file.read().decode("utf-8") lines = [line.strip() for line in content.splitlines() if line.strip()]Gradio 里可以用gr.File接收文件。处理完结果后,可以同时输出一个结果文本或文件。
这个阶段要注意的不是推理本身,而是输出一致性。批量任务必须考虑:
- 输入顺序是否保持。
- 某一条数据失败时,是跳过、重试还是中断。
- 输出文件命名是否唯一。
- 是否有日志记录,方便定位哪一条失败。
很多批量坑不是模型问题,而是“几百万条数据跑完后不知道哪些成功哪些失败”。建议从一开始就加上计数和日志。
6.2 队列、缓存和并发控制
Gradio 中,可以在Interface上启用队列:
demo.queue().launch()队列的作用是把同时发来的请求排队处理,避免服务器瞬间被打满。对于重量级模型推理,这很重要。
并发参数不要一上来就调最大。Gradio 里concurrency_count之类的参数如果设置太大,CPU、内存、显存会同时被多个任务占满,最终导致集体超时。正确顺序是先在低并发下测试单个任务耗时,再逐步加并发。
判断标准很简单:单条推理耗时多少,单个任务占用多少内存,然后倒推可以同时跑几个任务。假设单条任务需要 2 秒、占用 2GB 内存,服务器只有 8GB 内存,那同时处理 2 到 3 个任务已经算冒险,不能直接把并发调到 10。
6.3 失败记录落到本地
批量演示和批量生产之间,差的往往不是模型精度,而是错误处理。
建议统一返回结构化结果,例如每条数据包含index、input、result、status、error。前端页面展示进度条,后台把失败项单独写成一份 CSV 或日志文件。这样跑完后,不需要人肉从页面复制结果。
7. 部署:从本地可见变成“别人也能访问”
7.1 三条常见部署路线
本地 Demo 只在开发机上运行,如果想给团队或公网用户访问,通常有三种路线。
第一种:部署到一台有公网 IP 的云服务器,用streamlit run或python gradio_app.py后台运行,再通过 Nginx 或应用托管方式对外提供服务。模型文件较大时,这种方案最可控,资源自己掌握。
第二种:使用 Streamlit Community Cloud 或 Hugging Face Spaces 这类托管平台,把项目推到代码仓库,平台自动构建并分配访问地址。适合演示项目,但公开平台会有访问限制,具体以平台规则为准。
第三种:如果模型必须跑在内网,不做公网暴露,只供内部同事使用,可以在内网服务器上监听固定端口,配合权限系统或简单认证。
建议顺序是本地跑通、内网验证、云服务器部署、再做公网访问。不要跳过中间步骤。
7.2 依赖固定、密钥保护和简单认证
部署时最容易踩的坑是“本地能跑,服务器一启动就报错”。原因基本集中在依赖版本不一致、系统差异、模型路径不对。
在项目目录生成依赖清单:
pip freeze > requirements.txt部署时使用同一份requirements.txt安装。如果模型依赖复杂,可以考虑 Docker 打包,但这不是必须的,先保证版本一致即可。
密钥、Token、数据库连接串不要直接写死在代码里。G radio 的launch里可以加简单认证参数,用于内部演示:
demo.launch(auth=("admin", "your_password"))Streamlit 侧通常靠应用层认证或第三方组件处理。注意,通过认证参数写进代码的做法只适合低风险内部场景,生产环境要用更规范的鉴权和 HTTPS,不要把生产密码明文提交到代码仓库。
注意:部署前先把鉴权、端口、日志目录确认清楚。直接暴露公网且没有任何访问控制的 Demo,很容易被人顺手扫掉。
7.3 后台运行与日志查看
在服务器上直接跑streamlit run app.py,终端关闭服务就会停。可以用 nohup 或系统服务方式后台运行,但重点不是命令,而是日志。
部署后第一件事不是看页面,而是看启动日志。Streamlit 和 Gradio 都会在终端输出访问地址和报错信息。把日志落盘后,再去处理页面问题。这样遇到卡住、无输出、崩溃时,至少有据可查。
8. 遇到问题别乱改:按这个顺序排查
8.1 报错内容其实给出了七成信息
很多人报错后第一反应是重新安装所有依赖,或者把参数调成网上搜到的组合。这样做容易把原本正常的环境弄坏。
更稳妥的做法是先把终端里的完整报错复制下来,看懂第一行错误类型和最后一行原因。常见报错大致分几类:
- 模块缺失:
ModuleNotFoundError,说明依赖没装对或环境不对。 - 端口冲突:
Address already in use,说明端口被占用。 - 模型路径错误:
FileNotFoundError,说明找不到模型文件。 - 输入输出类型不匹配:Gradio 的返回类型和组件类型不一致。
- 显存或内存不足:任务跑到一半被系统杀掉,或者明显变慢。
8.2 各环节排查顺序
我一般按从下往上、从外到内的顺序排查。先看现象,再看输入,最后才怀疑算法和代码:
| 排查环节 | 先确认什么 |
|---|---|
| 现象 | 是直接报错、页面卡住、结果为空,还是网络超时 |
| 输入 | 文件路径、格式、编码、尺寸、大小是否符合预期 |
| 环境 | Python 版本、pip 环境、依赖版本、端口占用 |
| 参数 | 监听地址、端口、并发数、超时时间 |
| 代码 | 模型加载、输入预处理、输出类型是否一致 |
| 模型本身 | 模型是否在原生环境下能单独跑通 |
在这些都没问题之前,不建议直接调大并发、升级硬件或重写页面。
8.3 页面出来了但结果一直为空
这类问题要分成两端看。如果页面能打开,但点击预测没反应,先看终端有没有报错。如果终端也没有日志,多半是按钮事件没有触发,或者前端输入没有传给 Python 函数。
Gradio 里可以先打印函数参数:
def classify(text: str): print("收到输入:", repr(text)) return predict(text)看到终端输出后,就能判断是参数没传进来,还是模型推理逻辑的问题。Streamlit 里也可以先用st.write临时输出中间变量,确认输入内容到了哪一步。
很多看起来像功能不支持的问题,实际是输入格式不对。所以排查第一步永远是复现并打印输入。
9. 落地之前先想清楚边界
9.1 什么场景建议用,什么场景不建议
Streamlit 和 Gradio 适合模型演示、内部工具、快速原型、课程作业、算法验证。它们最大的优点是快,最大的缺点也很明确:不适合做复杂的生产级业务系统。
如果产品需要精细的权限体系、复杂的前端动效、像 SaaS 一样的管理后台,它们并不是最佳选择。更合适的路线是用 Flask 或 FastAPI 提供接口,再用前端框架搭页面。这样开发周期会长一些,但灵活性和工程边界更清晰。
如果是把大模型接进现有系统,也不一定每个场景都要写 Web 界面。先判断用户是真的需要一个可视页面,还是只需要一个 API 端点。很多企业场景只是内部系统调用模型,封装一个 FastAPI 接口比套 Streamlit 页面更省事。
9.2 最终取舍建议
对于大多数人来说,第一次接触这个选题,最有价值的动作不是对比完所有框架再选,而是先做两个小 Demo:用 Streamlit 写一个带表格和按钮的页面,用 Gradio 写一个能接收文本或图片并返回结果的接口。
做完之后,你自然会感受到两个工具各自的边界。之后再决定正式项目到底用哪个,或者干脆两个都不用,回到 Flask + 前端方案里。这个判断能力,比记住某一个库的具体 API 更有用。
我个人更建议先把单任务跑稳,再考虑批量、并发和部署。模型文件、输入格式、推理函数、页面调用这四层都稳定之后,Web 应用才算是真正立住了。
这个方案真正落地时,最该盯住的不是功能列表,而是输入格式、资源占用和失败重试。只要这三件事处理到位,把 AI 模型变成 Web 应用这件事,就真的能在几小时内从零跑到可用。