Gradio——用Python快速搭出 AI 应用界面




2023-03-18

blog_main_img

做模型或数据工具时,最容易卡住的地方经常不是算法本身,而是“怎么让别人顺手试一下”。 如果为了一个模型 demo 还要写前端、写接口、调样式、部署页面,节奏很容易被打断。

Gradio 的定位就很直接:把一个 Python 函数、模型推理函数、数据处理函数,快速包装成可交互的 Web 应用。
根据 Gradio GitHub README 的介绍,它是一个开源 Python 包,可以为机器学习模型、API 或任意 Python 函数快速构建 demo 或 Web 应用,并且可以通过内置分享能力把应用交给别人试用。

这篇文章不只写“Hello World”,而是从 Python 开发者的角度,把 Gradio 常用能力串起来:

  • Interface:几行代码把函数变成界面
  • Blocks:自定义布局和多步骤交互
  • ChatInterface:快速做聊天类应用
  • queue:给耗时推理排队
  • flagging:收集有价值的样本
  • gradio_client:把 Gradio 应用当 API 调用

Gradio 的核心感觉:函数就是界面的入口

先看一个非常小的例子。你写一个 Python 函数,然后告诉 Gradio 输入组件和输出组件,它就能生成一个可以在浏览器里操作的界面。

import gradio as gr


def greet(name: str, style: str):
    if style == "活力一点":
        return f"你好,{name}!很适合继续写点 Python。"
    return f"你好,{name}。"


demo = gr.Interface(
    fn=greet,
    inputs=[
        gr.Textbox(label="名字", placeholder="输入一个名字"),
        gr.Radio(["活力一点", "稳重一点"], label="语气", value="活力一点"),
    ],
    outputs=gr.Textbox(label="输出"),
    title="Gradio 小试牛刀",
    api_name="greet",
)

demo.launch()

这个例子里,真正关键的是三件事:

  • fn:你自己的 Python 函数
  • inputs:函数参数对应的输入组件
  • outputs:函数返回值对应的输出组件

Gradio 的门槛低,是因为它没有要求你先理解前端工程。
你先把 Python 函数写好,界面可以顺着函数签名自然长出来。

Gradio 函数到界面

Interface 适合快速 demo,但别什么都塞进去

Interface 很适合这种场景:

  • 文本分类
  • 图片识别
  • 音频转文字
  • 表格清洗
  • 一个函数输入,几个结果输出

比如我们写一个简单的文本分析器:

import gradio as gr


def analyze_text(text: str):
    length = len(text)
    keywords = [word for word in ["Python", "Gradio", "AI"] if word.lower() in text.lower()]

    score = min(length / 120, 1.0)
    label = {
        "信息量": round(score, 2),
        "轻量内容": round(1 - score, 2),
    }

    return {
        "字符数": length,
        "命中关键词": ", ".join(keywords) or "暂无",
    }, label


demo = gr.Interface(
    fn=analyze_text,
    inputs=gr.Textbox(lines=6, label="输入文本"),
    outputs=[
        gr.JSON(label="结构化结果"),
        gr.Label(label="粗略判断"),
    ],
    examples=[
        ["Gradio 可以用 Python 快速搭建 AI 应用。"],
        ["我想给模型做一个可以交互测试的小工具。"],
    ],
    api_name="analyze",
)

demo.launch()

这里可以看到,输出不一定是纯文本。
Gradio 内置了很多组件,比如 TextboxImageAudioDataframeJSONLabelPlotFile 等,能够覆盖不少机器学习和数据应用场景。

Interface 也有边界:如果你的界面开始出现多个区域、多条交互链路、组件状态联动,那就该换到 Blocks

Blocks:真正做应用时,布局和事件都交给它

Blocks 可以理解成 Gradio 的“自定义应用模式”。
你可以自己组织行、列、标签页、按钮、状态组件,也可以让一个组件的输出继续作为另一个步骤的输入。

Blocks 交互工作台

下面这个例子做了一个“小型文案工坊”:

  • 左侧输入主题和风格
  • 点击按钮生成文案
  • 再点击另一个按钮压缩成摘要
  • gr.State 保存最近一次生成结果
import gradio as gr


def draft_copy(topic: str, tone: str):
    text = (
        f"主题:{topic}\n"
        f"风格:{tone}\n\n"
        f"这是一段围绕「{topic}」生成的初稿,语气偏「{tone}」。"
    )
    return text, text


def summarize_copy(latest_text: str):
    if not latest_text:
        return "还没有可总结的内容。"
    first_line = latest_text.splitlines()[0]
    return f"摘要:{first_line},适合继续扩写成博客、脚本或产品说明。"


with gr.Blocks(title="文案工坊", theme=gr.themes.Soft()) as demo:
    gr.Markdown("## 文案工坊")

    latest = gr.State("")

    with gr.Row():
        with gr.Column(scale=1):
            topic = gr.Textbox(label="主题", placeholder="例如:Gradio 快速原型")
            tone = gr.Dropdown(
                ["轻松", "专业", "简洁", "热情"],
                label="风格",
                value="轻松",
            )
            build_btn = gr.Button("生成文案", variant="primary")
            summary_btn = gr.Button("提炼摘要")

        with gr.Column(scale=2):
            draft = gr.Textbox(label="生成结果", lines=10)
            summary = gr.Textbox(label="摘要", lines=3)

    build_btn.click(
        fn=draft_copy,
        inputs=[topic, tone],
        outputs=[draft, latest],
        api_name="draft",
    )

    summary_btn.click(
        fn=summarize_copy,
        inputs=latest,
        outputs=summary,
        api_name="summary",
    )

demo.launch()

这类写法比 Interface 更适合真实小工具。
原因不是它“更高级”,而是它能把交互流程拆清楚:哪个按钮触发哪个函数,哪些组件参与输入输出,状态放在哪里,一眼就能看出来。

事件链:.click().change().then() 让流程动起来

Gradio 的事件系统很像把 Python 函数接到组件上。
按钮可以 .click(),输入框可以 .change(),上传组件可以 .upload(),一个事件完成后还可以接 .then()

下面是一个更像模型推理流程的写法:

import gradio as gr


def clean_prompt(prompt: str):
    return prompt.strip()


def run_model(prompt: str, temperature: float):
    return f"模型收到:{prompt}\n采样参数:{temperature}"


def make_report(result: str):
    return {
        "状态": "完成",
        "结果长度": len(result),
    }


with gr.Blocks() as demo:
    prompt = gr.Textbox(label="Prompt", lines=4)
    temperature = gr.Slider(0, 1, value=0.7, label="temperature")
    run = gr.Button("运行")

    cleaned = gr.Textbox(label="清洗后 Prompt")
    output = gr.Textbox(label="模型输出", lines=6)
    report = gr.JSON(label="运行报告")

    run.click(
        clean_prompt,
        inputs=prompt,
        outputs=cleaned,
    ).then(
        run_model,
        inputs=[cleaned, temperature],
        outputs=output,
    ).then(
        make_report,
        inputs=output,
        outputs=report,
    )

demo.launch()

这段代码的好处是:
数据清洗、模型推理、结果统计被拆成三个函数,每一步都能独立测试,也能独立替换。

Gradio 事件链

聊天应用:ChatInterface 负责把聊天框架搭好

聊天类应用如果从 Blocks 开始手写,也能做,但会多写不少样板代码。
Gradio 提供了 ChatInterface,只要你给它一个函数,它就能搭出聊天 UI。

import gradio as gr


def bot(message, history):
    if "gradio" in message.lower():
        return "Gradio 很适合把 Python 函数快速包装成可交互应用。"
    return f"我收到了:{message}"


demo = gr.ChatInterface(
    fn=bot,
    title="Gradio Chat Demo",
    examples=["Gradio 适合做什么?", "帮我解释 Blocks"],
)

demo.launch()

如果要做流式输出,也可以让函数变成生成器:

import gradio as gr


def stream_bot(message, history):
    answer = f"你刚刚说的是:{message}。我会把这句话拆成几段返回。"
    buffer = ""
    for char in answer:
        buffer += char
        yield buffer


demo = gr.ChatInterface(stream_bot, title="流式聊天 Demo")
demo.launch()

这个例子没有绑定具体大模型,但模式是一样的:
你可以在函数里调用本地模型、远程 API、RAG 检索链路或工具函数,然后把结果返回给 Gradio。

排队和并发:让推理请求更可控

模型推理、图片生成、音频处理这类任务通常比较重。
如果多个用户同时操作,直接并发打到模型函数上,容易让机器压力突然升高。

Gradio 提供了 queue(),可以把请求放进队列中处理,也可以给事件设置并发限制。

import gradio as gr


def heavy_predict(text: str):
    return text.upper()


with gr.Blocks() as demo:
    text = gr.Textbox(label="输入")
    out = gr.Textbox(label="输出")
    btn = gr.Button("开始处理")

    btn.click(
        heavy_predict,
        inputs=text,
        outputs=out,
        concurrency_limit=2,
        api_name="heavy_predict",
    )

demo.queue(default_concurrency_limit=1)
demo.launch()

简单理解:

  • demo.queue() 控制应用整体排队行为
  • concurrency_limit 控制单个事件的并发上限
  • 对模型类任务来说,这比“来一个请求就立刻执行一个”更稳

Flagging:把用户反馈变成样本资产

模型 demo 做出来之后,真正有价值的数据往往来自使用者反馈:
哪些输入让模型答得不好?哪些输出值得二次标注?哪些样本应该进入下一轮评测?

Gradio 的 Interface 默认提供 flagging 能力,也可以自定义回调。
比如使用 CSV 记录用户标记样本:

import gradio as gr


def classify(text: str):
    if "bug" in text.lower():
        return {"问题反馈": 0.85, "普通内容": 0.15}
    return {"普通内容": 0.78, "问题反馈": 0.22}


demo = gr.Interface(
    fn=classify,
    inputs=gr.Textbox(label="输入文本"),
    outputs=gr.Label(label="分类结果"),
    flagging_callback=gr.CSVLogger(),
    flagging_options=["误判", "有价值样本", "需要人工复核"],
)

demo.launch()

对模型团队来说,flagging 不只是一个按钮。
它可以成为“模型评估样本池”的入口,让 demo 不只是展示工具,也能反向帮助模型改进。

gradio_client:把 Gradio 应用当 API 调用

Gradio 应用不只能给人点按钮,也能被 Python 程序调用。
官方提供了 gradio_client,可以像调用远程函数一样调用一个 Gradio app。

服务端代码可以这样暴露接口:

import gradio as gr


def normalize_text(text: str):
    return " ".join(text.split())


demo = gr.Interface(
    fn=normalize_text,
    inputs=gr.Textbox(label="原始文本"),
    outputs=gr.Textbox(label="清洗后文本"),
    api_name="normalize",
)

demo.launch()

客户端这样调用:

from gradio_client import Client


client = Client("http://127.0.0.1:7860")

result = client.predict(
    text="  Gradio   makes   Python apps easy.  ",
    api_name="/normalize",
)

print(result)

如果任务比较重,还可以用 submit() 拿到 job 对象:

job = client.submit(
    text="hello gradio",
    api_name="/normalize",
)

print(job.result())

这就让 Gradio 不只是“网页 demo”,也可以作为自动化流程中的一个服务节点。

Gradio Client 调用

实战建议:Gradio 适合快速,也要写得干净

Gradio 很适合快速出效果,但代码结构还是要收住。
建议把项目拆成这样:

project/
├── app.py              # Gradio 入口
├── services/
│   ├── model.py        # 模型加载和推理
│   └── postprocess.py  # 结果处理
├── examples_data/
│   └── samples/        # 示例文件
└── tests/
    └── test_model.py   # 核心函数测试

app.py 只负责界面和事件绑定,真正的业务函数放到 services/
这样 Gradio 页面可以改,模型逻辑也可以单独测试,不会混成一大坨。

import gradio as gr
from services.model import predict


def build_demo():
    with gr.Blocks(theme=gr.themes.Soft(), title="AI 工具台") as demo:
        gr.Markdown("## AI 工具台")

        with gr.Row():
            inp = gr.Textbox(label="输入", lines=5)
            out = gr.Textbox(label="输出", lines=5)

        run = gr.Button("运行", variant="primary")
        run.click(predict, inputs=inp, outputs=out, api_name="predict")

    return demo


if __name__ == "__main__":
    build_demo().queue().launch()