以前接到一个活儿:给模型写个demo,第二天老板要在会上看效果。最开始我想老老实实用Flask写个页面,表单、上传、回调、加载态……又得写HTML又得写JS,还没开始调模型,两小时就没了。后来同事甩过来一个Gradio,十几行代码把页面搭出来了,还能现场拖参数。从那以后,只要不是特别复杂的前端需求,我都会先问自己一句——这个界面用Gradio是不是就够了。
Gradio是Hugging Face开源的Python工具库,核心功能只有一个:把你的Python函数,自动变成一套可交互的Web页面。它不要求你会HTML、CSS、JavaScript,也不需要理解HTTP请求和WebSocket协议,写普通函数就行。这篇文章不适合纯做前端的同学,更适合模型开发者、数据工程师,以及那些想快速给同事/老板交付可操作界面的“工具人”。我会从最简单用法讲到身份验证、挂载FastAPI和部署避坑,尽量一次讲透。
1. Gradio到底帮你省掉了哪些工作
先说清楚Gradio的定位:它是一个“函数转界面”的框架,不是万能Web框架。传统方式里,你写一个Flask后端,要先处理路由、接收请求、解析JSON,再写HTML模板、JavaScript回调、loading状态、错误提示。如果模型有多个输入输出,前端和后端要对字段名、传参格式,光联调就够喝一壶。Gradio的全部意义,就是把这个环节压缩掉。
你可以把Gradio理解成一个中间层:你定义输入组件(文本框、图片、滑块),再写一个普通Python函数,Gradio自动生成页面,并在页面和函数之间完成数据转换。前端长什么样、请求怎么发、结果怎么渲染,这些都不需要你操心。
我做了个简单对比,方便你选型:
| 维度 | Gradio | Streamlit | Flask + 手写前端 |
|---|---|---|---|
| 上手成本 | 非常低,函数即界面 | 低,但脚本式刷新逻辑 | 高,前后端都要写 |
| 界面定制力 | 中等,可改主题和CSS | 中等 | 最高,完全可控 |
| API能力 | 自带HTTP接口,可挂载FastAPI | 较弱 | 完全自己定义 |
| 身份验证 | 内置Basic Auth,可自定义 | 需要自己处理 | 完全自己实现 |
| 适用场景 | 模型Demo、内部小工具、快速原型 | 数据分析面板 | 正式对外Web服务 |
看完这个表你应该明白:Gradio和Streamlit一样,解决的都是“快速出活”的问题;但Gradio更偏向模型推理场景,组件类型对图像、音频、Chatbot支持得特别全。而Flask那套则适合真正要长期迭代、严格控制前端体验的项目。
在这一步你只需要记住:如果你是给模型做演示、给团队做内部工具,或者想快速验证一个想法,直接上Gradio,别犹豫。真正遇到复杂业务了,后续还能挂到FastAPI上,后面有一章专门讲这个。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 先跑起来:三个能直接用的小例子
2.1 文本问答:最短可用代码
Gradio最基础的用法就是 gr.Interface。它像一个“函数包装器”,你把函数和输入输出组件传进去,剩下的交给它。
python复制import gradio as gr
def answer(question: str) -> str:
return f"你问的是:{question},这个问题可以换成任何模型逻辑。"
demo = gr.Interface(
fn=answer,
inputs=gr.Textbox(lines=3, placeholder="在这里输入问题"),
outputs=gr.Textbox(label="回答"),
title="问答演示",
examples=[["怎么做西红柿炒鸡蛋?"], ["Gradio是什么?"]],
)
demo.launch()
跑起来后浏览器会打开一个本地页面。这里面最值得说的是 examples 参数,演示时特别好用,页面下方会出现可点击的示例,对方一点自动填充输入框,不用现场敲字。
实际项目中,你完全可以把 answer 换成ChatGPT风格的推理函数,Gradio不会限制你的函数是同步还是异步。做了流式输出的话,在launch前调用 demo.queue(),页面就会一截一截地显示生成内容,而不是等全部算完再返回。
2.2 图片处理:type参数是最容易踩的坑
图片组件是Gradio里最常用的组件之一,但90%的新手第一次都在 type 参数上翻车。
python复制import numpy as np
import gradio as gr
def flip(img):
print(type(img)) # 看真实类型,调试神器
return np.flipud(img)
demo = gr.Interface(
fn=flip,
inputs=gr.Image(type="numpy"),
outputs=gr.Image(type="numpy"),
)
demo.launch()
type 决定了你的函数收到什么格式:
numpy:收到(高, 宽, 通道)的数组,适合直接做OpenCV风格处理。pil:收到PIL.Image,适合图像分类、图片生成类任务。filepath:收到临时文件路径字符串,适合传给原生处理工具。
最崩溃的情况是:你明明写了 type="numpy",函数里又用了 cv2.imread 去读字符串路径,运行后大概率报一个“无法调用数组的imread”错。所以拿到图片第一件事就是 print(type(img)),别猜。
2.3 多输入多输出,以及一个计数器例子
gr.Interface 天然支持多个输入和多个输出。参数顺序按你在 inputs 和 outputs 列表里的顺序对应。
python复制def greet(name, age):
return f"你好,{name}", age + 1
demo = gr.Interface(
fn=greet,
inputs=["text", "number"],
outputs=["text", "number"],
)
但如果你想做一个“点击按钮加一”这种有状态的交互,Interface 就有点吃力了。这时候要用 Blocks,它才是Gradio更核心的东西。
python复制import gradio as gr
with gr.Blocks() as demo:
gr.Markdown("## 计数器")
count = gr.State(0)
number = gr.Number(label="当前数值")
btn_add = gr.Button("加一")
def add_one(n):
n = n + 1
return n, n
btn_add.click(add_one, inputs=count, outputs=[number, count])
demo.launch()
gr.State(0) 在服务端存了一个会话级变量,按钮点击时,函数会拿到当前值,返回新值后再存回去。这个模式在对话机器人中非常常见,本质上就是维护历史记录。
3. Blocks才是掌控界面细节的入口
gr.Interface 适合“一个函数、一个页面”的简单场景。当你的工具需要多个模块、多种交互时,就该转到 Blocks 了。
Blocks 的核心逻辑和写代码差不多:用 with gr.Blocks() as demo 建立上下文,通过 gr.Row、gr.Column 做布局,再通过事件方法把组件和函数串起来。
python复制with gr.Blocks(theme=gr.themes.Soft()) as demo:
with gr.Row():
with gr.Column(scale=1):
inp = gr.Textbox(label="输入")
with gr.Column(scale=2):
out = gr.Textbox(label="输出")
def upper(s):
return s.upper()
inp.change(upper, inputs=inp, outputs=out)
这里有两个重点:scale 控制列宽比例,change 表示输入框内容一变化就触发函数。事件类型不止 change,还有 click(按钮点击)、upload(上传文件)、submit(回车提交)。你可以在界面里同时放输入框、滑块、按钮、图表,自由组合,这在 Interface 里很难做到。
Blocks还支持把多个 Interface 放进同一个页面,也支持 gr.Accordion 做折叠面板。4.x版本之后还加入了 gr.render,可以按数据动态生成组件。比如用户上传了10张图,你希望在界面上按列表展示,用 gr.render 就能动态循环渲染,不用提前写死。不过这个属于进阶内容,实际用到再翻文档也不迟。
说个经验:Interface 是Blocks的封装,所以你可以先写 Interface 快速验证函数逻辑,等界面交互复杂了再迁到Blocks。不要一上来就追求复杂布局,先跑通数据流比好看重要。
4. 身份验证:demo上线前先加把锁
很多人开发完直接 demo.launch(),一关机完事。如果是内部放在内网,问题不大;可一旦要让外部同事、合作伙伴访问,裸奔的Gradio端口等于把模型接口直接暴露给别人。加身份验证是第一步。
4.1 内置HTTP Basic Auth,两行代码搞定
Gradio的 launch() 支持 auth 参数。最简单的方式是传一个用户名和密码的元组:
python复制demo.launch(auth=("admin", "password123"))
也可以传一个校验函数:
python复制def verify(username, password):
return username == "admin" and password == "secret"
demo.launch(auth=verify)
校验函数只需要返回布尔值。新版本里如果你返回一个字符串,它会被当作登录失败时展示的提示信息。这里的底层机制其实是HTTP Basic Auth,浏览器会弹一个原生的登录对话框,验证通过后才允许加载界面。
这种方式的优点是真的省事,缺点也比较明显:弹窗样式不可控,没有“退出登录”按钮(浏览器会缓存凭据),并且密码在传输过程中是Base64编码而非加密,所以一定要走HTTPS,不能用裸HTTP。
4.2 接数据库校验账户,别再把密码写死在代码里
临时demo里写死账号密码可以理解,但稍微正式一点的服务,账户应该放在数据库里,密码要以哈希形式保存。
python复制import hmac
import sqlite3
from hashlib import pbkdf2_hmac
def get_user(username):
conn = sqlite3.connect("users.db")
cur = conn.execute(
"SELECT username, salt, password_hash FROM users WHERE username = ?",
(username,),
)
row = cur.fetchone()
conn.close()
return row
def verify(username, password):
row = get_user(username)
if not row:
return False
_, salt, stored_hash = row
computed = pbkdf2_hmac(
"sha256", password.encode(), salt.encode(), 100_000
)
return hmac.compare_digest(computed, stored_hash)
demo.launch(auth=verify)
这里有两个细节:第一,用户不存在和密码错误都要返回 False,不要区分提示,避免被用来探测用户名;第二,用 hmac.compare_digest 做哈希比较,而不是直接 ==,能防一点时序侧信道攻击。密码不要存明文,加盐做PBKDF2或bcrypt都行。
4.3 Gradio auth的边界:它只解决“谁能进”,不解决“谁能干什么”
用上 auth 之后,界面确实被锁住了,但你要清楚它的边界。Gradio的auth只做入口拦截,它是全局限的,不能给不同用户分配不同权限。所有登录成功的人看到的内容、能调用的接口都是一样的。
如果你的场景需要区分用户角色、限制某个接口只能特定账号调用,光靠Gradio内置auth不够。常见做法是这样:对外统一走Nginx或网关层的认证(比如公司SSO),Gradio侧保留或关闭 auth 都行;或者把Gradio挂到FastAPI下面,用FastAPI中间件同时拦截 /api 和 /gradio 两条路径。这也是我为什么一直强调,Gradio适合做界面层,真正的权限控制要在更高的基础设施层做。
5. 和FastAPI组合:把Gradio挂到真实工程里
Gradio独立跑一个端口当然省事,但生产环境往往不是这样:你可能已经有一个FastAPI服务在提供业务API,想让Gradio界面和现有服务共用同一域名、同一端口,怎么办?官方提供了一个很顺手的接口:gr.mount_gradio_app。
5.1 为什么非要把Gradio挂到FastAPI
先说不挂的坏处:独立launch意味着又开一个端口,部署时要多管理一个进程,还要在Nginx里单独配一套反向代理;如果FastAPI那边已经有统一的鉴权、日志、监控中间件,Gradio是绕开的,安全策略就出现了缺口。所以当你发现“FastAPI + Gradio要同时在线”时,优先考虑挂载。
5.2 mount_gradio_app基本用法
代码非常简单:
python复制from fastapi import FastAPI
import gradio as gr
app = FastAPI()
def predict(text: str):
return f"收到:{text}"
demo = gr.Interface(
fn=predict,
inputs=gr.Textbox(lines=2, placeholder="输入文本"),
outputs=gr.Textbox(),
)
app = gr.mount_gradio_app(app, demo, path="/gradio")
挂载之后,FastAPI原来的路由照常可用,Gradio界面则通过 http://你的地址/gradio 访问。这里有一个非常容易犯的错:mount_gradio_app 返回的是新的app对象,你必须重新赋值给 app,让Uvicorn启动的是挂载后的应用,而不是原来的那个。
挂载的美妙之处在于,Gradio请求会经过FastAPI中间件。你如果已经在FastAPI里写了统一鉴权、访问日志、限流,那么Gradio也会被覆盖到,不需要再做第二套认证逻辑。我实际用的基本都是这个方式,真的很省心。
5.3 反向代理和WebSocket:最容易出现404的环节
Gradio页面虽然看起来是普通网页,但内部很多功能依赖WebSocket,尤其是排队、流式输出、实时进度。如果前面架了Nginx,没有正确转发升级请求,最常见的现象就是:界面能打开,但一点提交按钮就转圈,控制台里报 /gradio/queue/join 404。
这是我亲测可用的一段Nginx配置:
nginx复制location /gradio {
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_read_timeout 3600s;
}
注意几个点:proxy_pass 后面不要轻易加路径,否则会把 /gradio 二级路径弄丢,静态资源和WebSocket全部404;Upgrade 和 Connection 必须设置,这是WebSocket能否握手成功的关键;proxy_read_timeout 要调大,长对话场景下默认60秒很容易超时中断。
如果你不是在挂载模式,而是让Gradio独立部署在一个子路径下,比如 /demo/gradio,通常还需要在launch时配置 root_path,否则页面里的资源路径会一直指到根路径,怎么配代理都白搭。这个参数名字就叫 root_path,Gradio在文档里写得比较隐晦,我当年也在这个上面卡了好久。
5.4 挂载后能直接调HTTP接口吗
能。Gradio不只是页面,它还会自动暴露一个后端调用接口。3.x时代接口路径是 /api/predict,4.x之后改成 /call/predict 这样的结构。挂载到 /gradio 之后,对应的接口就在 /gradio/call/predict 下。你可以用脚本直接POST。
python复制import requests
r = requests.post(
"http://127.0.0.1:8000/gradio/call/predict",
json={"data": ["你好"]},
)
print(r.json())
不过我要提醒一句:Gradio的HTTP接口路径和数据结构在不同版本之间变过好几次,你按网上老教程写很容易过时。最稳的调试方式是打开浏览器开发者工具,看页面点击提交时实际发的是什么请求、什么结构,然后照着改。这一点对于所有“拿Gradio当接口服务”的用法都适用。
6. launch参数和踩坑记录:少走几次弯路
6.1 常用启动参数速查
demo.launch() 里的参数看着不多,但每一个都对应一类实际场景。我把最常用的整理成一个表:
| 参数 | 作用 | 我的推荐 |
|---|---|---|
server_name |
绑定地址 | 本地 127.0.0.1;容器/内网用 0.0.0.0 |
server_port |
监听端口 | 默认7860,冲突就换 |
share |
生成临时公网链接 | 快速联调用True,生产别用 |
auth |
身份验证 | 元组或callable |
show_error |
是否把异常堆栈显示到页面 | 生产环境设为False |
prevent_thread_lock |
在Jupyter里不阻塞当前cell | notebook里设为True |
max_threads |
后端线程并发数 | 按机器配置调整 |
6.2 share=True不是正式通道
share=True 生成的是一个公网临时链接,本质是借用Gradio官方节点做内网穿透,官方说明是有时效的,印象中大约72小时。这个功能适合什么场景?你和一个远程同事联调,两边都不在一个内网,手头又没有可用的公网服务器,临时开一个链接给人家看效果。
但它不适合正式使用:隧道稳定性受制于官方节点,数据要经过第三方服务中转,延迟也更高,很多公司网络安全策略还会直接封掉这类外联请求。我遇到过一次,时间紧又打不开share链接,最后是拿ngrok类似的方案顶过去的。所以正规部署,还是用Nginx + 域名 + FastAPI这套链路。
6.3 Queue、State和多实例部署
如果你做的是聊天机器人或流式输出,不要忘记调用 demo.queue()。它一方面负责排队,避免并发太高把模型实例压垮,另一方面也是流式输出的基础。你可以通过 default_concurrency_limit 控制同一个函数同时执行的最大线程数,max_size 控制排队长度。
再说一个生产环境经常踩的坑:gr.State 默认存的是服务端内存。单机部署没问题,一旦你为了并发上了多副本,负载均衡会把不同请求分发到不同进程,用户上一次会话存的State可能在下一个请求里就读不到了。对话历史这类数据,正式环境还是要放到Redis或数据库里,不要指望 gr.State 帮你扛多实例。
6.4 版本迁移:3.x到4.x再到5.x
Gradio近几年版本变化挺大。3.x到4.x是一次底层重构,4.x直接基于FastAPI,所以 mount_gradio_app 才变得这么好用;接口路径也从 /api 系列调整成了 /call 系列。4.x到5.x则更偏前端样式和组件参数收敛,基本逻辑没有颠覆性变化。
我的建议是:新项目直接用当前最新稳定版,查资料时注意看文档版本标签,很多老教程里的写法在4.x之后就报错了。特别是 auth 返回字符串提示、mount_gradio_app 参数这些,都是版本演进过程中出现的,看旧文章容易被带偏。
我自己现在基本把Gradio定位成模型的调试器和内部工具面板,而不是把整个业务逻辑塞进去。遇到非技术同事要看效果,share=True 顶一下;要上线,就挂到FastAPI后面,再加一层认证。真正复杂的前端交互,再考虑用Vue或React单独写。这个边界想清楚之后,Gradio的“简单使用”其实一点都不糙。
