跳转至

第二十三章 Gradio构建AI应用

⚠️ 时效性说明:本章涉及前沿模型/价格/榜单等信息,可能随版本快速变化;请以论文原文、官方发布页和 API 文档为准。

用Python快速构建交互式AI应用界面

学习时间: 6-8小时 难度级别: ⭐⭐⭐ 中级 前置知识: Python基础、LLM API调用(第1-2章)、RAG基础(第5章) 学习目标: 掌握Gradio 4.x核心API,能独立构建聊天、RAG问答、图像生成等AI应用界面


📖 章节导读

大模型能力再强,也需要一个直观的界面让用户使用。Gradio让你用几行Python代码就能构建专业级AI应用界面,无需前端知识。本章从基础组件到完整项目,带你掌握Gradio的核心用法。


1. Gradio简介

1.1 为什么选择Gradio

构建AI应用界面有多种选择,以下是主流方案对比:

特性 Gradio Streamlit Flask/FastAPI
学习曲线 ⭐ 极低 ⭐⭐ 低 ⭐⭐⭐ 中等
AI组件支持 ✅ 原生ChatInterface ⚠️ 需配合st.chat ❌ 需自建
分享功能 ✅ 一键share链接 ❌ 需部署 ❌ 需部署
HF Spaces ✅ 原生支持 ✅ 支持 ⚠️ 需Docker
API自动生成 ✅ 自动 ❌ 无 ✅ 手动定义
流式输出 ✅ 原生 ✅ 支持 ⚠️ 需SSE
自定义布局 ✅ Blocks API ⚠️ 有限 ✅ 完全自由
适用场景 ML Demo、AI应用 数据仪表盘 生产级API

Gradio核心优势: - 专为AI/ML场景设计,内置聊天、图像、音频等组件 - 零前端知识即可构建专业界面 - 自动生成REST API,方便集成 - 一行代码生成公网分享链接

1.2 安装与快速上手

Bash
# 安装Gradio 4.x(需要Python 3.10+)
pip install "gradio>=4.0"

# 验证安装
python -c "import gradio as gr; print(gr.__version__)"

Hello World示例

Python
import gradio as gr

def greet(name: str) -> str:
    return f"你好 {name}!欢迎使用Gradio构建AI应用 🎉"

# 最简单的Interface:一个输入,一个输出
demo = gr.Interface(
    fn=greet,
    inputs=gr.Textbox(label="你的名字", placeholder="请输入名字..."),
    outputs=gr.Textbox(label="问候语"),
    title="Gradio Hello World",
    description="输入你的名字,获取个性化问候!",
)

demo.launch()
# 默认运行在 http://127.0.0.1:7860

运行后浏览器自动打开,你将看到一个带输入框、提交按钮和输出区域的完整界面。

1.3 核心概念:Interface、Blocks、Components

Gradio有三层抽象:

Text Only
Components(组件)   → 单个UI元素:Textbox, Image, Slider...
Interface(接口)    → 快速搭建:输入 → 函数 → 输出
Blocks(区块)      → 完全自定义布局与交互逻辑
  • Interface:高级API,适合简单的 输入→处理→输出 场景
  • Blocks:底层API,支持任意布局、多步交互、状态管理
  • Components:构成界面的基本元素,既可作为输入也可作为输出

💡 经验法则:原型阶段用Interface快速验证,产品阶段用Blocks精细控制。


2. 基础组件

2.1 输入组件

Python
import gradio as gr

def process_inputs(
    text: str,
    image,          # PIL.Image
    audio,          # tuple(sample_rate, numpy_array)
    file,           # tempfile path
    slider_val: float,
    dropdown_val: str,
    checkbox_val: bool,
):
    return f"""
    文本: {text}
    图像尺寸: {image.size if image else 'None'}
    音频采样率: {audio[0] if audio else 'None'}
    文件: {file.name if file else 'None'}
    滑块值: {slider_val}
    下拉选择: {dropdown_val}
    复选框: {checkbox_val}
    """

demo = gr.Interface(
    fn=process_inputs,
    inputs=[
        gr.Textbox(label="文本输入", placeholder="输入任意文本...", lines=3),
        gr.Image(label="上传图片", type="pil"),
        gr.Audio(label="上传音频", type="numpy"),
        gr.File(label="上传文件", file_types=[".pdf", ".txt", ".csv"]),
        gr.Slider(minimum=0, maximum=100, value=50, step=1, label="参数调节"),
        gr.Dropdown(
            choices=["GPT-4o", "Claude-3.5", "Gemini-2.0"],
            value="GPT-4o",
            label="模型选择",
        ),
        gr.Checkbox(label="启用流式输出", value=True),
    ],
    outputs=gr.Textbox(label="处理结果", lines=10),
    title="Gradio组件展示",
)

demo.launch()

2.2 输出组件

Python
import gradio as gr
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np

def generate_outputs(text: str):
    # 文本输出
    result_text = f"处理结果:{text.upper()}"

    # DataFrame输出
    df = pd.DataFrame({
        "模型": ["GPT-4o", "Claude-3.5", "Gemini-2.0"],
        "准确率": [0.92, 0.89, 0.91],
        "延迟(ms)": [320, 280, 250],
    })

    # 图表输出
    fig, ax = plt.subplots(figsize=(8, 4))
    x = np.linspace(0, 10, 100)
    ax.plot(x, np.sin(x), label="sin(x)")
    ax.plot(x, np.cos(x), label="cos(x)")
    ax.set_title("示例图表")
    ax.legend()
    plt.tight_layout()

    return result_text, df, fig

demo = gr.Interface(
    fn=generate_outputs,
    inputs=gr.Textbox(label="输入文本"),
    outputs=[
        gr.Textbox(label="文本结果"),
        gr.DataFrame(label="数据表格"),
        gr.Plot(label="可视化图表"),
    ],
)

demo.launch()

2.3 组件属性与事件

每个组件都有丰富的属性可配置:

Python
import gradio as gr

# Textbox高级配置
text_input = gr.Textbox(
    label="Prompt",
    placeholder="输入你的问题...",
    lines=5,               # 显示行数
    max_lines=20,           # 最大行数
    show_copy_button=True,  # 显示复制按钮
    autofocus=True,         # 自动聚焦
    elem_id="main-input",   # CSS ID
    elem_classes=["custom-textbox"],  # CSS类
)

# Image高级配置
image_input = gr.Image(
    label="上传图片",
    type="pil",            # "pil", "numpy", "filepath"
    height=300,
    sources=["upload", "clipboard"],  # 图片来源
    show_download_button=True,
)

# 组件事件
with gr.Blocks() as demo:
    text = gr.Textbox(label="输入")
    output = gr.Textbox(label="输出")

    # change事件:输入变化时触发
    text.change(fn=lambda x: x.upper(), inputs=text, outputs=output)  # lambda匿名函数

    # submit事件:按回车时触发
    text.submit(fn=lambda x: f"提交了: {x}", inputs=text, outputs=output)

demo.launch()

3. 构建LLM聊天应用

3.1 gr.ChatInterface快速构建

gr.ChatInterface是Gradio 4.x专为聊天场景设计的高级组件:

Python
import gradio as gr
from openai import OpenAI

client = OpenAI()  # 自动读取OPENAI_API_KEY环境变量

def chat(message: str, history: list[dict]) -> str:
    """非流式聊天函数"""
    messages = history + [{"role": "user", "content": message}]
    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=messages,
    )
    return response.choices[0].message.content

demo = gr.ChatInterface(
    fn=chat,
    type="messages",  # Gradio 4.x推荐使用messages格式
    title="AI聊天助手",
    description="基于GPT-4o-mini的智能助手",
    examples=["你好!", "解释一下什么是RAG", "写一首关于AI的诗"],
    theme=gr.themes.Soft(),
)

demo.launch()

3.2 流式输出(Streaming)

流式输出大幅提升用户体验,让回答"打字机式"逐字显示:

Python
import gradio as gr
from openai import OpenAI

client = OpenAI()

def chat_stream(message: str, history: list[dict]):
    """流式聊天函数——使用yield逐步返回"""
    messages = history + [{"role": "user", "content": message}]

    stream = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=messages,
        stream=True,
    )

    partial_message = ""
    for chunk in stream:
        if chunk.choices[0].delta.content is not None:
            partial_message += chunk.choices[0].delta.content
            yield partial_message  # 每次yield更新界面

demo = gr.ChatInterface(
    fn=chat_stream,
    type="messages",
    title="AI聊天助手(流式输出)",
)

demo.launch()

3.3 系统提示词配置

让用户自定义AI助手的行为:

Python
import gradio as gr
from openai import OpenAI

client = OpenAI()

def chat_with_system_prompt(
    message: str,
    history: list[dict],
    system_prompt: str,
    temperature: float,
    max_tokens: int,
):
    messages = [{"role": "system", "content": system_prompt}]
    messages += history
    messages.append({"role": "user", "content": message})

    stream = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=messages,
        stream=True,
        temperature=temperature,
        max_tokens=max_tokens,
    )

    partial = ""
    for chunk in stream:
        if chunk.choices[0].delta.content:
            partial += chunk.choices[0].delta.content
            yield partial  # yield产出值,函数变为生成器

demo = gr.ChatInterface(
    fn=chat_with_system_prompt,
    type="messages",
    title="可配置AI助手",
    additional_inputs=[
        gr.Textbox(
            value="你是一个有帮助的AI助手,回答简洁准确。",
            label="系统提示词",
            lines=3,
        ),
        gr.Slider(0, 2, value=0.7, step=0.1, label="Temperature"),
        gr.Slider(64, 4096, value=1024, step=64, label="Max Tokens"),
    ],
    additional_inputs_accordion=gr.Accordion("⚙️ 高级设置", open=False),
)

demo.launch()

3.4 多轮对话历史管理

Gradio 4.x使用OpenAI兼容的messages格式管理对话历史:

Python
import gradio as gr
from openai import OpenAI

client = OpenAI()

def chat_with_memory(message: str, history: list[dict]):
    """
    history格式(type="messages"):
    [
        {"role": "user", "content": "你好"},
        {"role": "assistant", "content": "你好!有什么可以帮你的?"},
        ...
    ]
    """
    # 限制历史长度,防止token超限
    max_history_turns = 20
    recent_history = history[-(max_history_turns * 2):]

    messages = [
        {"role": "system", "content": "你是一个友好的AI助手。"},
        *recent_history,
        {"role": "user", "content": message},
    ]

    stream = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=messages,
        stream=True,
    )

    partial = ""
    for chunk in stream:
        if chunk.choices[0].delta.content:
            partial += chunk.choices[0].delta.content
            yield partial

with gr.Blocks(theme=gr.themes.Soft()) as demo:
    gr.Markdown("# 🤖 AI聊天助手")
    gr.Markdown("支持多轮对话,自动管理对话历史")

    chatbot = gr.ChatInterface(
        fn=chat_with_memory,
        type="messages",
        examples=[
            "帮我解释什么是Transformer",
            "用Python实现快速排序",
            "比较RAG和微调的优缺点",
        ],
    )

demo.launch()

3.5 完整代码:接入OpenAI API的聊天机器人

Python
"""完整的聊天机器人应用 —— 支持流式输出、参数配置、对话管理"""

import gradio as gr
from openai import OpenAI

client = OpenAI()

MODELS = ["gpt-4o-mini", "gpt-4o", "gpt-4-turbo"]
PRESETS = {
    "通用助手": "你是一个有帮助的AI助手,回答准确、简洁。",
    "代码专家": "你是一个编程专家,擅长Python、JavaScript等语言。用代码示例回答问题。",
    "翻译官": "你是一个专业翻译,将用户输入翻译为英文。如果输入已经是英文,则翻译为中文。",
    "写作助手": "你是一个写作助手,帮助用户润色、改写和创作文本。",
}

def respond(
    message: str,
    history: list[dict],
    system_prompt: str,
    model: str,
    temperature: float,
    max_tokens: int,
):
    messages = [{"role": "system", "content": system_prompt}]
    # 保留最近20轮对话
    messages += history[-(20 * 2):]
    messages.append({"role": "user", "content": message})

    try:  # try/except捕获异常,防止程序崩溃
        stream = client.chat.completions.create(
            model=model,
            messages=messages,
            stream=True,
            temperature=temperature,
            max_tokens=max_tokens,
        )
        partial = ""
        for chunk in stream:
            if chunk.choices[0].delta.content:
                partial += chunk.choices[0].delta.content
                yield partial
    except Exception as e:
        yield f"❌ 调用失败: {e}"

def apply_preset(preset_name: str) -> str:
    return PRESETS.get(preset_name, "")

with gr.Blocks(theme=gr.themes.Soft(), title="AI聊天助手") as demo:
    gr.Markdown("# 🤖 AI聊天助手\n支持多模型、流式输出、预设角色")

    with gr.Row():
        with gr.Column(scale=4):
            chat = gr.ChatInterface(
                fn=respond,
                type="messages",
                additional_inputs=[
                    gr.Textbox(
                        value=PRESETS["通用助手"],
                        label="系统提示词",
                        lines=3,
                        key="system_prompt",
                    ),
                    gr.Dropdown(MODELS, value="gpt-4o-mini", label="模型"),
                    gr.Slider(0, 2, value=0.7, step=0.1, label="Temperature"),
                    gr.Slider(64, 4096, value=1024, step=64, label="Max Tokens"),
                ],
                additional_inputs_accordion=gr.Accordion("⚙️ 设置", open=False),
            )

demo.launch()

4. 构建RAG问答应用

4.1 文件上传与处理

Python
import gradio as gr
from pathlib import Path

def load_document(file) -> str:
    """加载上传的文档并提取文本"""
    if file is None:
        return ""

    file_path = Path(file.name)
    suffix = file_path.suffix.lower()

    if suffix == ".txt":
        return file_path.read_text(encoding="utf-8")
    elif suffix == ".pdf":
        # 使用PyMuPDF提取PDF文本
        import pymupdf
        doc = pymupdf.open(file_path)
        text = ""
        for page in doc:
            text += page.get_text() + "\n"
        return text
    elif suffix == ".md":
        return file_path.read_text(encoding="utf-8")
    else:
        return f"不支持的文件类型: {suffix}"

4.2 完整代码:简单RAG应用

Python
"""简单RAG问答应用 —— 上传文档,基于文档内容回答问题"""

import gradio as gr
from pathlib import Path
from openai import OpenAI
import numpy as np

client = OpenAI()

# ---- 文档处理 ----

def chunk_text(text: str, chunk_size: int = 500, overlap: int = 50) -> list[str]:
    """将文本切分为有重叠的块"""
    chunks = []
    start = 0
    while start < len(text):
        end = start + chunk_size
        chunks.append(text[start:end])
        start += chunk_size - overlap
    return chunks

def get_embeddings(texts: list[str]) -> list[list[float]]:
    """调用OpenAI Embedding API"""
    response = client.embeddings.create(
        model="text-embedding-3-small",
        input=texts,
    )
    return [item.embedding for item in response.data]

def cosine_similarity(a: list[float], b: list[float]) -> float:
    a, b = np.array(a), np.array(b)
    return float(np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b)))

# ---- 全局状态 ----
knowledge_base: dict = {"chunks": [], "embeddings": []}

def process_document(file) -> str:
    """处理上传的文档,构建知识库"""
    if file is None:
        return "⚠️ 请先上传文件"

    file_path = Path(file.name)
    if file_path.suffix == ".txt":
        text = file_path.read_text(encoding="utf-8")
    elif file_path.suffix == ".pdf":
        import pymupdf
        doc = pymupdf.open(file_path)
        text = "\n".join(page.get_text() for page in doc)
    else:
        return f"❌ 不支持的格式: {file_path.suffix}"

    chunks = chunk_text(text)
    embeddings = get_embeddings(chunks)

    knowledge_base["chunks"] = chunks
    knowledge_base["embeddings"] = embeddings

    return f"✅ 已加载文档,共 {len(chunks)} 个文本块"

def retrieve(query: str, top_k: int = 3) -> list[str]:
    """检索最相关的文本块"""
    if not knowledge_base["chunks"]:
        return []
    query_emb = get_embeddings([query])[0]
    scores = [
        (i, cosine_similarity(query_emb, emb))
        for i, emb in enumerate(knowledge_base["embeddings"])  # enumerate同时获取索引和元素
    ]
    scores.sort(key=lambda x: x[1], reverse=True)  # 按相似度分数(x[1])降序排列
    return [knowledge_base["chunks"][i] for i, _ in scores[:top_k]]  # 取前K个,_丢弃分数

def rag_answer(message: str, history: list[dict]) -> str:
    """基于检索结果回答问题"""
    if not knowledge_base["chunks"]:
        return "⚠️ 请先上传文档再提问"

    # 检索相关上下文
    contexts = retrieve(message, top_k=3)
    context_text = "\n---\n".join(contexts)

    messages = [
        {
            "role": "system",
            "content": (
                "你是一个文档问答助手。根据以下检索到的文档内容回答用户问题。"
                "如果无法从文档中找到答案,请如实告知。\n\n"
                f"【检索到的文档内容】\n{context_text}"
            ),
        },
        *history,
        {"role": "user", "content": message},
    ]

    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=messages,
    )
    return response.choices[0].message.content

# ---- Gradio界面 ----

with gr.Blocks(theme=gr.themes.Soft(), title="RAG问答应用") as demo:
    gr.Markdown("# 📚 RAG文档问答")
    gr.Markdown("上传文档后,基于文档内容回答你的问题")

    with gr.Row():
        with gr.Column(scale=1):
            file_input = gr.File(
                label="上传文档",
                file_types=[".txt", ".pdf", ".md"],
            )
            process_btn = gr.Button("📥 加载文档", variant="primary")
            status = gr.Textbox(label="状态", interactive=False)
            process_btn.click(fn=process_document, inputs=file_input, outputs=status)

        with gr.Column(scale=3):
            gr.ChatInterface(
                fn=rag_answer,
                type="messages",
                examples=["这篇文档的主要内容是什么?", "总结文档的关键要点"],
            )

demo.launch()

5. 构建图像生成应用

5.1 文生图界面

Python
"""图像生成应用 —— 使用DALL-E 3生成图片"""

import gradio as gr
from openai import OpenAI

client = OpenAI()

def generate_image(
    prompt: str,
    size: str,
    quality: str,
    style: str,
) -> str:
    """调用DALL-E 3生成图片"""
    if not prompt.strip():  # 链式调用:strip去除空白
        raise gr.Error("请输入描述文字")

    response = client.images.generate(
        model="dall-e-3",
        prompt=prompt,
        size=size,
        quality=quality,
        style=style,
        n=1,
    )
    return response.data[0].url

with gr.Blocks(theme=gr.themes.Soft()) as demo:
    gr.Markdown("# 🎨 AI图像生成")

    with gr.Row():
        with gr.Column(scale=1):
            prompt = gr.Textbox(
                label="描述",
                placeholder="描述你想生成的图片...",
                lines=3,
            )
            size = gr.Dropdown(
                ["1024x1024", "1024x1792", "1792x1024"],
                value="1024x1024",
                label="尺寸",
            )
            quality = gr.Radio(["standard", "hd"], value="standard", label="质量")
            style = gr.Radio(["vivid", "natural"], value="vivid", label="风格")
            btn = gr.Button("🎨 生成", variant="primary")

        with gr.Column(scale=2):
            output_image = gr.Image(label="生成结果", type="filepath")

    btn.click(
        fn=generate_image,
        inputs=[prompt, size, quality, style],
        outputs=output_image,
    )

    gr.Examples(
        examples=[
            ["一只戴墨镜的猫坐在海滩上看日落,水彩风格"],
            ["未来城市的鸟瞰图,赛博朋克风格,霓虹灯光"],
            ["一个安静的日式花园,春天,樱花飘落"],
        ],
        inputs=prompt,
    )

demo.launch()

5.2 图片画廊展示

使用gr.Gallery展示多张生成的图片:

Python
import gradio as gr
from openai import OpenAI
import requests
from PIL import Image
from io import BytesIO

client = OpenAI()

def generate_variations(prompt: str, num_images: int) -> list:
    """生成多张图片(通过多次调用实现)"""
    images = []
    for i in range(int(num_images)):
        response = client.images.generate(
            model="dall-e-3",
            prompt=f"{prompt} (variation {i+1})",
            size="1024x1024",
            quality="standard",
            n=1,
        )
        url = response.data[0].url
        img = Image.open(BytesIO(requests.get(url).content))
        images.append((img, f"变体 {i+1}"))
    return images

with gr.Blocks() as demo:
    gr.Markdown("# 🖼️ 图片画廊")
    prompt = gr.Textbox(label="描述")
    num = gr.Slider(1, 4, value=2, step=1, label="生成数量")
    btn = gr.Button("生成")
    gallery = gr.Gallery(label="结果", columns=2, height="auto")

    btn.click(fn=generate_variations, inputs=[prompt, num], outputs=gallery)

demo.launch()

6. Blocks高级布局

6.1 Row、Column布局

Python
import gradio as gr

with gr.Blocks() as demo:
    gr.Markdown("# 布局演示")

    # 水平排列
    with gr.Row():
        with gr.Column(scale=1):
            gr.Markdown("### 左侧面板")
            input1 = gr.Textbox(label="输入1")
        with gr.Column(scale=2):
            gr.Markdown("### 右侧面板(2倍宽)")
            output1 = gr.Textbox(label="输出1")

    # 嵌套布局
    with gr.Row():
        with gr.Column():
            with gr.Row():
                btn1 = gr.Button("按钮1")
                btn2 = gr.Button("按钮2")
                btn3 = gr.Button("按钮3")

demo.launch()

6.2 Tab分页

Python
import gradio as gr

with gr.Blocks() as demo:
    gr.Markdown("# 🧩 多功能工具")

    with gr.Tabs():
        with gr.Tab("💬 聊天"):
            chatbot = gr.Chatbot(type="messages")
            msg = gr.Textbox(label="输入消息")

        with gr.Tab("📝 文本处理"):
            text_input = gr.Textbox(label="输入文本", lines=5)
            with gr.Row():
                summarize_btn = gr.Button("摘要")
                translate_btn = gr.Button("翻译")
            text_output = gr.Textbox(label="结果", lines=5)

        with gr.Tab("🎨 图像"):
            img_input = gr.Image(label="上传图片")
            img_output = gr.Textbox(label="图片描述")

demo.launch()

6.3 Accordion与Group

Python
import gradio as gr

with gr.Blocks() as demo:
    gr.Markdown("# 高级布局组件")

    # Accordion折叠面板
    with gr.Accordion("🔧 高级设置", open=False):
        temperature = gr.Slider(0, 2, value=0.7, label="Temperature")
        max_tokens = gr.Slider(64, 4096, value=1024, label="Max Tokens")
        top_p = gr.Slider(0, 1, value=0.9, label="Top P")

    # Group组件组
    with gr.Group():
        gr.Markdown("### 模型配置")
        model = gr.Dropdown(["gpt-4o", "gpt-4o-mini"], label="模型")
        api_key = gr.Textbox(label="API Key", type="password")

demo.launch()

6.4 自定义CSS

Python
import gradio as gr

custom_css = """
#main-title {
    text-align: center;
    background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
    font-size: 2.5em;
}
.custom-container {
    border: 2px solid #667eea;
    border-radius: 12px;
    padding: 20px;
    margin: 10px 0;
}
"""

with gr.Blocks(css=custom_css) as demo:
    gr.Markdown("# AI应用平台", elem_id="main-title")

    with gr.Column(elem_classes=["custom-container"]):
        gr.Textbox(label="输入")
        gr.Button("提交", variant="primary")

demo.launch()

6.5 事件链(.then())

Python
import gradio as gr
import time

def step1(text: str) -> str:
    time.sleep(1)
    return f"步骤1完成: {text.upper()}"

def step2(text: str) -> str:
    time.sleep(1)
    return f"步骤2完成: {text}(已翻转: {text[::-1]})"

def step3(text: str) -> str:
    time.sleep(1)
    return f"步骤3完成: 最终结果长度={len(text)}"

with gr.Blocks() as demo:
    gr.Markdown("# 事件链演示")
    input_text = gr.Textbox(label="输入")
    btn = gr.Button("开始处理")

    out1 = gr.Textbox(label="步骤1")
    out2 = gr.Textbox(label="步骤2")
    out3 = gr.Textbox(label="步骤3")

    # 链式调用:step1 -> step2 -> step3
    btn.click(fn=step1, inputs=input_text, outputs=out1) \
       .then(fn=step2, inputs=out1, outputs=out2) \
       .then(fn=step3, inputs=out2, outputs=out3)

demo.launch()

7. 状态管理与交互

7.1 gr.State状态保持

gr.State用于在多次交互间保持数据,每个用户独立:

Python
import gradio as gr

def add_item(item: str, items: list) -> tuple[str, list, str]:
    """添加项目到列表"""
    if item.strip():
        items.append(item.strip())
    display = "\n".join(f"  {i+1}. {x}" for i, x in enumerate(items))  # enumerate提供索引,f-string格式化为编号列表,join用换行符拼接
    return "", items, display  # 清空输入, 更新状态, 显示列表

def clear_items(items: list) -> tuple[list, str]:
    items.clear()
    return items, "(列表已清空)"

with gr.Blocks() as demo:
    gr.Markdown("# 📋 待办列表(State演示)")

    state = gr.State(value=[])  # 每个用户独立的状态

    item_input = gr.Textbox(label="新项目", placeholder="输入待办事项...")
    with gr.Row():
        add_btn = gr.Button("➕ 添加", variant="primary")
        clear_btn = gr.Button("🗑️ 清空")
    display = gr.Textbox(label="当前列表", lines=10, interactive=False)

    add_btn.click(fn=add_item, inputs=[item_input, state], outputs=[item_input, state, display])
    item_input.submit(fn=add_item, inputs=[item_input, state], outputs=[item_input, state, display])
    clear_btn.click(fn=clear_items, inputs=state, outputs=[state, display])

demo.launch()

7.2 组件间联动

Python
import gradio as gr

def update_options(category: str) -> gr.Dropdown:
    """根据类别动态更新下拉选项(Gradio 4.x用组件构造器替代gr.update)"""
    options_map = {
        "文本模型": ["gpt-4o", "gpt-4o-mini", "claude-3.5-sonnet"],
        "图像模型": ["dall-e-3", "stable-diffusion-xl", "midjourney"],
        "语音模型": ["whisper-large-v3", "tts-1", "tts-1-hd"],
    }
    options = options_map.get(category, [])
    return gr.Dropdown(choices=options, value=options[0] if options else None)

with gr.Blocks() as demo:
    gr.Markdown("# 组件联动演示")
    category = gr.Dropdown(
        ["文本模型", "图像模型", "语音模型"],
        label="模型类别",
        value="文本模型",
    )
    model = gr.Dropdown(
        ["gpt-4o", "gpt-4o-mini", "claude-3.5-sonnet"],
        label="具体模型",
        value="gpt-4o",
    )
    category.change(fn=update_options, inputs=category, outputs=model)

demo.launch()

7.3 进度条与加载状态

Python
import gradio as gr
import time

def long_task(text: str, progress=gr.Progress()) -> str:
    """带进度条的长时间任务"""
    progress(0, desc="初始化...")
    time.sleep(1)

    results = []
    steps = text.split()
    for i, word in enumerate(progress.tqdm(steps, desc="处理中")):
        time.sleep(0.5)
        results.append(word.upper())

    progress(1, desc="完成!")
    return " ".join(results)

with gr.Blocks() as demo:
    text = gr.Textbox(label="输入多个单词", value="hello world gradio python ai")
    btn = gr.Button("处理")
    output = gr.Textbox(label="结果")

    btn.click(fn=long_task, inputs=text, outputs=output)

demo.launch()

7.4 错误处理

Python
import gradio as gr

def safe_divide(a: float, b: float) -> float:
    if b == 0:
        raise gr.Error("除数不能为零!")  # 在UI上显示错误提示
    return a / b

def with_warning(text: str) -> str:
    if len(text) > 100:
        gr.Warning("文本过长,将只处理前100个字符")
        text = text[:100]
    if not text.strip():
        gr.Info("提示:输入为空,将返回默认值")
        return "默认输出"
    return text.upper()

with gr.Blocks() as demo:
    gr.Markdown("# 错误处理演示")

    with gr.Row():
        a = gr.Number(label="被除数", value=10)
        b = gr.Number(label="除数", value=0)
    result = gr.Number(label="结果")
    btn = gr.Button("计算")
    btn.click(fn=safe_divide, inputs=[a, b], outputs=result)

    text = gr.Textbox(label="文本输入")
    text_out = gr.Textbox(label="处理结果")
    text.submit(fn=with_warning, inputs=text, outputs=text_out)

demo.launch()

8. 部署与分享

8.1 Gradio Share链接

最快捷的分享方式——一行代码生成72小时有效的公网链接:

Python
demo.launch(share=True)
# 输出:Running on public URL: https://xxxxx.gradio.live

⚠️ 注意:share链接通过Gradio代理服务器转发,不适合生产环境。72小时后自动失效。

8.2 Hugging Face Spaces部署

推荐用于演示和轻量级部署:

Bash
# 1. 安装huggingface_hub
pip install huggingface_hub

# 2. 登录
huggingface-cli login

# 3. 创建Space
huggingface-cli repo create my-ai-app --type space --space-sdk gradio

项目结构:

Text Only
my-ai-app/
├── app.py          # Gradio应用主文件
├── requirements.txt # 依赖
└── README.md        # HF Space配置

README.md(HF Space配置):

YAML
---
title: My AI App
emoji: 🤖
colorFrom: blue
colorTo: purple
sdk: gradio
sdk_version: "4.44.0"
app_file: app.py
pinned: false
---
Bash
# 4. 推送代码
cd my-ai-app
git init
git remote add origin https://huggingface.co/spaces/YOUR_USERNAME/my-ai-app
git add .
git commit -m "Initial commit"
git push origin main

8.3 Docker容器化部署

Docker
FROM python:3.12-slim

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

# Gradio默认端口7860
EXPOSE 7860

# 设置环境变量避免Gradio尝试分析
ENV GRADIO_SERVER_NAME="0.0.0.0"
ENV GRADIO_SERVER_PORT="7860"

CMD ["python", "app.py"]
Bash
# 构建并运行
docker build -t my-gradio-app .
docker run -p 7860:7860 -e OPENAI_API_KEY=$OPENAI_API_KEY my-gradio-app

8.4 生产部署建议

Python
import gradio as gr

demo = gr.Blocks()

# 生产环境配置
demo.launch(
    server_name="0.0.0.0",    # 监听所有网卡
    server_port=7860,          # 指定端口
    share=False,               # 生产环境不开share
    auth=("admin", "password"),# 基础认证(可选)
    ssl_keyfile="key.pem",     # HTTPS(可选)
    ssl_certfile="cert.pem",
    max_threads=40,            # 并发线程数
    show_error=False,          # 不暴露错误详情
)

也可使用Nginx反向代理:

Nginx Configuration File
server {
    listen 443 ssl;
    server_name ai-app.example.com;

    ssl_certificate /etc/ssl/certs/cert.pem;
    ssl_certificate_key /etc/ssl/private/key.pem;

    location / {
        proxy_pass http://127.0.0.1:7860;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";  # WebSocket支持
    }
}

9. 实战项目:AI多功能助手

Python
"""
AI多功能助手 —— 集成聊天、文档问答、图像理解
运行: python app.py
依赖: pip install gradio openai pymupdf numpy
"""

import gradio as gr
from openai import OpenAI
from pathlib import Path
import numpy as np
import base64

client = OpenAI()

# ==================== 工具函数 ====================

def get_embeddings(texts: list[str]) -> list[list[float]]:
    resp = client.embeddings.create(model="text-embedding-3-small", input=texts)
    return [item.embedding for item in resp.data]

def cosine_sim(a, b):
    a, b = np.array(a), np.array(b)
    return float(np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b)))

def encode_image_base64(image) -> str:
    """将PIL Image编码为base64"""
    from io import BytesIO
    buffer = BytesIO()
    image.save(buffer, format="PNG")
    return base64.b64encode(buffer.getvalue()).decode("utf-8")

# ==================== Tab 1: 聊天 ====================

def chat_fn(message: str, history: list[dict], system_prompt: str, temperature: float):
    messages = [{"role": "system", "content": system_prompt}]
    messages += history[-(20 * 2):]
    messages.append({"role": "user", "content": message})

    stream = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=messages,
        stream=True,
        temperature=temperature,
    )
    partial = ""
    for chunk in stream:
        if chunk.choices[0].delta.content:
            partial += chunk.choices[0].delta.content
            yield partial

# ==================== Tab 2: 文档问答 ====================

doc_store: dict = {"chunks": [], "embeddings": []}

def load_doc(file) -> str:
    if file is None:
        return "⚠️ 请上传文件"
    path = Path(file.name)
    if path.suffix == ".txt":
        text = path.read_text(encoding="utf-8")
    elif path.suffix == ".pdf":
        import pymupdf
        doc = pymupdf.open(path)
        text = "\n".join(page.get_text() for page in doc)
    elif path.suffix == ".md":
        text = path.read_text(encoding="utf-8")
    else:
        return f"❌ 不支持: {path.suffix}"

    # 切分(500字符块,50字符重叠)
    chunk_size, overlap = 500, 50
    chunks = []
    for i in range(0, len(text), chunk_size - overlap):
        chunks.append(text[i:i + chunk_size])

    doc_store["chunks"] = chunks
    doc_store["embeddings"] = get_embeddings(chunks)
    return f"✅ 已加载 {path.name},共 {len(chunks)} 个文本块"

def doc_qa(message: str, history: list[dict]) -> str:
    if not doc_store["chunks"]:
        return "⚠️ 请先在左侧上传文档"

    q_emb = get_embeddings([message])[0]
    scores = [(i, cosine_sim(q_emb, e)) for i, e in enumerate(doc_store["embeddings"])]
    scores.sort(key=lambda x: x[1], reverse=True)
    top_chunks = [doc_store["chunks"][i] for i, _ in scores[:3]]
    context = "\n---\n".join(top_chunks)

    messages = [
        {
            "role": "system",
            "content": f"根据以下文档内容回答问题。如果无法回答,请说明。\n\n{context}",
        },
        *history,
        {"role": "user", "content": message},
    ]

    resp = client.chat.completions.create(model="gpt-4o-mini", messages=messages)
    return resp.choices[0].message.content

# ==================== Tab 3: 图像理解 ====================

def analyze_image(image, question: str) -> str:
    if image is None:
        return "⚠️ 请上传图片"
    if not question.strip():
        question = "请描述这张图片的内容"

    b64 = encode_image_base64(image)

    response = client.chat.completions.create(
        model="gpt-4o-mini",
        messages=[
            {
                "role": "user",
                "content": [
                    {"type": "text", "text": question},
                    {
                        "type": "image_url",
                        "image_url": {"url": f"data:image/png;base64,{b64}"},
                    },
                ],
            }
        ],
        max_tokens=1024,
    )
    return response.choices[0].message.content

# ==================== 组装界面 ====================

custom_css = """
#app-title {
    text-align: center;
    margin-bottom: 0.5em;
}
"""

with gr.Blocks(theme=gr.themes.Soft(), css=custom_css, title="AI多功能助手") as demo:
    gr.Markdown("# 🚀 AI多功能助手", elem_id="app-title")
    gr.Markdown("集成聊天、文档问答、图像理解三大功能", elem_id="app-title")

    with gr.Tabs():
        # ---- Tab 1: 智能聊天 ----
        with gr.Tab("💬 智能聊天"):
            gr.ChatInterface(
                fn=chat_fn,
                type="messages",
                additional_inputs=[
                    gr.Textbox(
                        value="你是一个有帮助的AI助手。",
                        label="系统提示词",
                        lines=2,
                    ),
                    gr.Slider(0, 2, value=0.7, step=0.1, label="Temperature"),
                ],
                additional_inputs_accordion=gr.Accordion("⚙️ 设置", open=False),
                examples=["你好!", "用Python实现快速排序", "什么是RAG?"],
            )

        # ---- Tab 2: 文档问答 ----
        with gr.Tab("📚 文档问答"):
            with gr.Row():
                with gr.Column(scale=1):
                    doc_file = gr.File(
                        label="上传文档",
                        file_types=[".txt", ".pdf", ".md"],
                    )
                    doc_btn = gr.Button("📥 加载文档", variant="primary")
                    doc_status = gr.Textbox(label="状态", interactive=False)
                    doc_btn.click(fn=load_doc, inputs=doc_file, outputs=doc_status)

                with gr.Column(scale=3):
                    gr.ChatInterface(
                        fn=doc_qa,
                        type="messages",
                        examples=["文档的主要内容是什么?", "总结关键要点"],
                    )

        # ---- Tab 3: 图像理解 ----
        with gr.Tab("🖼️ 图像理解"):
            with gr.Row():
                with gr.Column(scale=1):
                    img_input = gr.Image(label="上传图片", type="pil")
                    img_question = gr.Textbox(
                        label="问题",
                        placeholder="关于这张图片你想问什么?",
                        value="请描述这张图片的内容",
                    )
                    img_btn = gr.Button("🔍 分析", variant="primary")
                with gr.Column(scale=1):
                    img_output = gr.Textbox(label="分析结果", lines=10)

            img_btn.click(
                fn=analyze_image,
                inputs=[img_input, img_question],
                outputs=img_output,
            )

    gr.Markdown("---")
    gr.Markdown("💡 Powered by OpenAI GPT-4o-mini | Built with Gradio")

if __name__ == "__main__":
    demo.launch()

10. 最佳实践与FAQ

10.1 最佳实践

实践 说明
使用type="messages" Gradio 4.x推荐的聊天历史格式,与OpenAI API兼容
流式输出用yield 用generator函数实现流式,大幅提升体验
设置show_error=False 生产环境不暴露错误堆栈
使用gr.Progress() 长任务显示进度,避免用户以为卡死
gr.State管理会话数据 每个用户独立,不会串数据
限制对话历史长度 防止token超限,保留最近N轮即可
使用Blocks而非Interface 复杂应用用Blocks更灵活
合理使用gr.update() 动态更新组件属性(选项、可见性等)

10.2 性能优化

Python
import gradio as gr

# 1. 开启队列(高并发必需)
demo = gr.Blocks()
# queue()默认已启用(Gradio 4.x);可调整参数:
# demo.queue(max_size=20, default_concurrency_limit=5)

# 2. 缓存昂贵计算
import functools

@functools.lru_cache(maxsize=128)
def expensive_embedding(text: str) -> tuple:
    """缓存embedding计算结果"""
    emb = get_embeddings([text])[0]
    return tuple(emb)  # lru_cache需要hashable参数

# 3. 异步支持
import asyncio  # Python标准异步库

async def async_chat(message: str, history: list[dict]):  # async def定义协程函数
    """Gradio 4.x原生支持async函数"""
    # 使用异步客户端
    from openai import AsyncOpenAI
    aclient = AsyncOpenAI()

    stream = await aclient.chat.completions.create(  # await等待异步操作完成
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": message}],
        stream=True,
    )
    partial = ""
    async for chunk in stream:
        if chunk.choices[0].delta.content:
            partial += chunk.choices[0].delta.content
            yield partial

10.3 常见问题

Q: 如何在Gradio中使用环境变量?

Python
import os

# 方法1:直接读取
api_key = os.environ.get("OPENAI_API_KEY", "")

# 方法2:用.env文件
from dotenv import load_dotenv
load_dotenv()

Q: 如何限制上传文件大小?

Python
# 在launch时设置(单位:MB)
demo.launch(max_file_size="10mb")

Q: 如何添加用户认证?

Python
# 简单认证
demo.launch(auth=("username", "password"))

# 多用户认证
def auth_fn(username: str, password: str) -> bool:
    users = {"admin": "admin123", "user": "user123"}
    return users.get(username) == password

demo.launch(auth=auth_fn)

Q: 组件不更新/事件不触发?

Python
# 常见原因1:忘记在outputs中声明要更新的组件
btn.click(fn=my_fn, inputs=[a], outputs=[b, c])  # b和c都会更新

# 常见原因2:函数返回值数量与outputs不匹配
def my_fn(a):
    return "result_b", "result_c"  # 必须返回与outputs数量一致的值

Q: 如何在Blocks中使用Examples?

Python
with gr.Blocks() as demo:
    inp = gr.Textbox(label="输入")
    out = gr.Textbox(label="输出")
    btn = gr.Button("提交")

    # Examples需要在Blocks内部使用
    gr.Examples(
        examples=["例子1", "例子2", "例子3"],
        inputs=inp,
    )

    btn.click(fn=my_fn, inputs=inp, outputs=out)

10.4 调试技巧

Python
# 1. 启用详细日志
import logging
logging.basicConfig(level=logging.DEBUG)

# 2. 使用 gr.Warning/gr.Info 在UI上输出调试信息
def debug_fn(x):
    gr.Info(f"收到输入: {x}")  # 右上角toast提示
    result = process(x)
    gr.Info(f"处理完成: {result[:50]}")
    return result

# 3. 热重载开发(命令行方式更好用)
# gradio app.py  ← 自动检测代码变更并重载

📝 本章小结

知识点 掌握程度
Gradio Interface快速原型 ⭐⭐⭐ 必须掌握
Blocks自定义布局 ⭐⭐⭐ 必须掌握
ChatInterface聊天应用 ⭐⭐⭐ 必须掌握
流式输出(yield) ⭐⭐⭐ 必须掌握
状态管理(gr.State) ⭐⭐ 重要
组件联动与事件链 ⭐⭐ 重要
HF Spaces / Docker部署 ⭐⭐ 重要
高级CSS自定义 ⭐ 了解即可

📝 练习与面试

练习1:多轮对话Bot(⭐)

gr.ChatInterface 接入OpenAI API,实现一个支持流式输出、可调整temperature的聊天机器人。

练习2:文档问答UI(⭐⭐)

gr.Blocks 构建一个带文件上传、检索结果展示、回答生成的RAG问答界面。

练习3:多模态助手(⭐⭐⭐)

构建一个多Tab应用:文本聊天 + 图像理解 + 语音转写,包含状态管理和错误处理。

面试题

Q1: Gradio与Streamlit的核心区别是什么?各自适合什么场景?

Gradio更擅长ML模型的快速交互Demo,支持一键分享链接和HF Spaces部署,组件以I/O对为核心。Streamlit更适合数据展示和Dashboard,支持更复杂的页面布局和多页应用。简单模型Demo选Gradio,复杂数据应用选Streamlit。

Q2: Gradio 4.x中如何实现流式输出?

两种方式:① 在普通函数中用 yield 逐步返回结果。② 在 gr.ChatInterface 中,将 fn 设为generator函数,通过 yield 流式输出每个token。Gradio会自动处理前端渲染。


导航:上一章 22-结构化输出与函数调用 | 下一章 24-多模态RAG与向量数据库进阶 | 返回目录