
> 作者:李剑锋
3. 基于 gr.Interface() 的大模型应用开发页面构建
前面我们已经学习了 Gradio 的基本概念,知道了 gr.Interface() 可以把一个 Python 函数包装成网页应用。接下来,我们就可以把前面学过的大模型 API 调用代码接入 Gradio 页面中,构建多个问答应用。这一部分的关键,是明确页面输入、函数参数和页面输出之间的对应关系。
3.1 文本问答页面
文本问答页面是最基础的大模型应用页面。它的输入是用户提出的问题,输出是大模型生成的回答。因此,我们只需要使用两个文本组件即可:一个 Textbox 用来接收用户问题,另一个 Textbox 用来展示模型回答。
在代码实现上,可以分为两步:第一步,先构建一个文本对话函数;第二步,再使用 gr.Interface() 将这个函数包装成页面。
3.1.1 文本对话函数构建
在前面的 API 调用示例中,我们已经能够通过 Python 调用百炼平台的大模型。例如:
import os
from openai import OpenAI
client = OpenAI(
api_key=os.getenv("DASHSCOPE_API_KEY"),
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
)
response = client.chat.completions.create(
model="qwen3.6-plus",
messages=[
{"role": "system", "content": "你是一名耐心的人工智能课程助教。"},
{"role": "user", "content": "请用三句话介绍什么是大模型 API。"}
]
)
print(response.choices[0].message.content)
这段代码能够正常调用大模型,但是其中的用户问题是固定写在代码里的:
{"role": "user", "content": "请用三句话介绍什么是大模型 API。"}
如果要把它改造成 Gradio 页面应用,就不能把问题写死在代码中,而应该让用户在页面中输入问题。也就是说,我们需要把用户问题设计成函数参数。
可以先构建一个 chat_with_llm() 函数:
import os
from openai import OpenAI
client = OpenAI(
api_key=os.getenv("DASHSCOPE_API_KEY"),
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
)
def chat_with_llm(question):
response = client.chat.completions.create(
model="qwen3.6-plus",
messages=[
{"role": "system", "content": "你是一名耐心的人工智能课程助教。"},
{"role": "user", "content": question}
]
)
answer = response.choices[0].message.content
return answer
在这个函数中,question 就是用户输入的问题。后续当这个函数接入 Gradio 页面后,用户在页面文本框中输入的内容,就会被传递给 question 参数。
函数内部仍然使用 client.chat.completions.create() 调用大模型。不同的是,原来写死的问题被替换成了变量 question:
{"role": "user", "content": question}
模型返回结果后,通过下面这行代码提取回答内容:
answer = response.choices[0].message.content
最后,使用 return answer 将回答返回。这个返回值后续会交给 Gradio 页面进行展示。
因此,这个函数的作用可以概括为:
接收用户问题 question
→ 调用大模型 API
→ 提取模型回答 answer
→ 返回 answer
这里要特别注意,Gradio 页面本身并不负责生成回答,真正调用大模型的是 chat_with_llm() 这个 Python 函数。Gradio 只是负责把用户输入传给函数,再把函数返回结果展示出来。
3.1.2 文本对话页面搭建
完成文本对话函数后,接下来就可以使用 gr.Interface() 构建对话页面。这里需要注意,本节重点不是重新编写大模型调用代码,而是把上一节已经写好的 chat_with_llm() 函数接入 Gradio 页面中。
由于文本问答应用的输入和输出都是文字,因此页面中可以使用两个 Textbox 组件:一个用于接收用户输入的问题,另一个用于展示大模型返回的回答。
页面构建代码如下:
import gradio as gr
demo = gr.Interface(
fn=chat_with_llm,
inputs=gr.Textbox(
label="请输入你的问题",
placeholder="例如:请用三句话介绍什么是大模型 API。",
lines=3
),
outputs=gr.Textbox(
label="大模型回答",
lines=8
),
title="文本问答大模型应用",
description="在下方输入问题,点击提交后,系统会调用大模型生成回答。"
)
demo.launch()
运行后页面如下所示:

在这段代码中,fn=chat_with_llm 表示页面提交后会调用前面定义好的文本对话函数;inputs=gr.Textbox(...) 表示页面使用文本框接收用户输入的问题;outputs=gr.Textbox(...) 表示函数返回的结果会显示在文本框中。
当用户在页面中输入问题并点击提交按钮后,Gradio 会自动把输入框中的文字传递给 chat_with_llm() 函数。函数处理完成后,返回的大模型回答会显示在页面的输出区域中。
通过这个例子可以看到,gr.Interface() 的作用就是把“函数”和“页面组件”连接起来。只要前面已经写好了能够完成任务的 Python 函数,后面就可以根据输入和输出类型选择合适的 Gradio 组件,把它封装成一个可以直接交互的网页应用。
3.1.3 多组件输入页面搭建
前面的文本问答页面只有一个输入框,用户只能输入问题。实际上,在大模型应用中,页面往往不只有一个输入组件。例如,我们可以让用户同时输入系统提示词和用户问题。
其中,系统提示词用于规定大模型的角色和回答风格,例如“你是一名耐心的人工智能课程助教”;用户问题则是用户真正想让模型回答的内容。这样设计的好处是,系统提示词可以提供一个默认值,但用户也可以根据需要进行修改,从而让页面更加灵活。前面已经讲过,Gradio 会把页面组件中的内容传递给 Python 函数,因此当页面中有多个输入组件时,函数也需要接收多个参数。
可以先将文本对话函数改造成接收两个参数:
def chat_with_llm(system_prompt, user_prompt):
response = client.chat.completions.create(
model="qwen3.6-plus",
messages=[
{"role": "system", "content": system_prompt},
{"role": "user", "content": user_prompt}
]
)
answer = response.choices[0].message.content
return answer
在这个函数中,system_prompt 表示系统提示词,user_prompt 表示用户输入的问题。相比前面的版本,这里不再把系统提示词固定写死,而是也设计成了函数参数。这样一来,页面既可以提供默认系统提示词,又允许用户根据不同任务进行修改。
接下来,就可以在 gr.Interface() 中设置多个输入组件。多个输入组件需要放在一个列表中:
import gradio as gr
demo = gr.Interface(
fn=chat_with_llm,
inputs=[
gr.Textbox(
label="系统提示词",
value="你是一名耐心的人工智能课程助教。",
lines=3
),
gr.Textbox(
label="用户问题",
placeholder="例如:请用三句话介绍什么是大模型 API。",
lines=3
)
],
outputs=gr.Textbox(
label="大模型回答",
lines=8
),
title="多组件文本问答应用",
description="可以同时设置系统提示词和用户问题,然后调用大模型生成回答。"
)
demo.launch()
这段代码中,inputs 不再是一个单独的 gr.Textbox(),而是一个列表。列表中第一个文本框用于输入系统提示词,第二个文本框用于输入用户问题。
需要特别注意的是:输入组件的顺序要和函数参数的顺序保持一致。在上面的代码中,inputs 列表中的第一个组件会对应 chat_with_llm(system_prompt, user_prompt) 中的 system_prompt 参数,第二个组件会对应 user_prompt 参数。
运行页面后,用户会看到两个输入框。第一个输入框中已经默认填写了系统提示词,用户可以直接使用,也可以根据需要修改;第二个输入框用于输入具体问题。点击提交后,程序会同时读取这两个输入框的内容,并传递给大模型完成回答。

通过这个例子可以看到,gr.Interface() 不仅可以处理单个输入,也可以处理多个输入。只要函数需要多个参数,就可以在 inputs 中配置多个组件;只要函数返回一个结果,就可以用一个输出组件展示。如果函数返回多个结果,也可以在 outputs 中配置多个输出组件。后续构建图片理解、文件分析、语音识别等应用时,也会经常用到这种多组件组合的方式。
3.2 视觉问答页面:图片与视频
前面我们已经完成了文本问答页面的搭建,用户可以在页面中输入问题,然后由后端函数调用大模型生成回答。接下来,我们可以进一步构建一个图片问答页面,让用户上传图片,并输入与图片相关的问题,由多模态大模型结合图片内容进行回答。
图片问答页面和文本问答页面的整体思路是一样的,仍然是先构建一个 Python 函数,再使用 gr.Interface() 将函数包装成页面。不同之处在于,文本问答只需要接收文字,而图片问答需要同时接收文字内容和图片内容。
3.2.1 视觉调用函数构建
在调用图片理解模型时,用户输入的内容不再只是一个普通字符串,而是由文字和图片共同组成。根据百炼 OpenAI 兼容接口的说明,用户消息的 content 在包含图像等多模态数据时,可以使用数组形式,其中输入文本使用 type="text",输入图片使用 type="image_url"。
由于 Gradio 上传图片后通常会得到一个本地图片路径,因此我们可以先写一个辅助函数,把本地图片文件转换成 Base64 格式的 data URL,方便后续传入模型接口。具体代码如下:
import os
import base64
import mimetypes
from openai import OpenAI
client = OpenAI(
api_key=os.getenv("DASHSCOPE_API_KEY"),
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
)
def image_to_data_url(image_path):
mime_type, _ = mimetypes.guess_type(image_path)
if mime_type is None:
mime_type = "image/png"
with open(image_path, "rb") as image_file:
base64_data = base64.b64encode(image_file.read()).decode("utf-8")
return f"data:{mime_type};base64,{base64_data}"
其中,image_to_data_url() 函数的作用是把本地图片路径转换成接口可以识别的图片数据格式。这样,用户通过 Gradio 上传的图片,就可以作为多模态输入传递给大模型。
接下来,可以构建图片问答函数:
def image_chat(system_prompt, user_prompt, image_path):
if image_path is not None:
image_url = image_to_data_url(image_path)
messages = [
{
"role": "system",
"content": system_prompt
},
{
"role": "user",
"content": [
{
"type": "image_url",
"image_url": {
"url": image_url
}
},
{
"type": "text",
"text": user_prompt
}
]
}
]
else:
messages = [
{
"role": "system",
"content": system_prompt
},
{
"role": "user",
"content": user_prompt
}
]
response = client.chat.completions.create(
model="qwen3.6-plus",
messages=messages
)
answer = response.choices[0].message.content
return answer
这个函数一共接收三个参数:system_prompt、user_prompt 和 image_path。其中,system_prompt 表示系统提示词,用来控制模型的角色和回答风格;user_prompt 表示用户提出的问题;image_path 表示用户上传图片后得到的图片路径。
函数内部先判断 image_path 是否为空。如果用户上传了图片,image_path 就会有图片路径,程序会先通过 image_to_data_url(image_path) 将图片转换成接口可以识别的格式,然后把图片和文字一起放入 messages 中,让模型进行图文理解。
如果用户没有上传图片,image_path 的值就是 None,程序就会进入 else 分支,只把系统提示词和用户问题发送给模型。此时这个函数就相当于普通文本问答函数。
因此,这个函数同时支持两种使用方式:
上传图片:
系统提示词 + 用户问题 + 图片 → 图文问答
不上传图片:
系统提示词 + 用户问题 → 文本问答
这样设计后,页面使用起来会更加灵活。用户想分析图片时可以上传图片,不想使用图片时也可以直接输入文字问题。
3.2.2 视觉问答页面搭建
完成图片调用函数后,就可以使用 gr.Interface() 构建图片问答页面。与前面的文本问答页面相比,这里的输入组件更多,包括系统提示词、用户问题和图片上传组件。
页面构建代码如下:
import gradio as gr
demo = gr.Interface(
fn=image_chat,
inputs=[
gr.Textbox(
label="系统提示词",
value="你是一名耐心的多模态人工智能课程助教,能够根据图片内容回答问题。",
lines=3
),
gr.Textbox(
label="用户问题",
placeholder="例如:请描述这张图片的主要内容。",
lines=3
),
gr.Image(
label="上传图片",
type="filepath"
)
],
outputs=gr.Textbox(
label="模型回答",
lines=10
),
title="图片问答大模型应用",
description="上传一张图片,并输入问题,系统会调用多模态大模型进行分析。"
)
demo.launch()
在这段代码中,inputs 中一共有三个组件。第一个 Textbox 用于输入系统提示词,并设置了默认内容;第二个 Textbox 用于输入用户问题;第三个 Image 用于上传图片。由于 Image 组件设置了 type="filepath",因此它会把上传后的图片路径传递给后端函数。
此时页面如下所示:

我们可以输入一个问题并上传一张图片从而获取对应的文本回复信息:

至此,一个基础的图文对话页面就搭建完成了。
3.2.3 视觉问答页面进阶
前面我们已经完成了图片问答页面的构建,用户可以上传图片,并输入问题,让多模态大模型结合图片内容进行回答。实际上,部分多模态模型除了支持图片理解外,也可以进一步支持视频理解。也就是说,用户不仅可以上传图片,还可以上传一段视频,让模型对视频内容进行概括、描述或分析。
在实现思路上,视频问答和图片问答非常相似。区别在于图片内容使用 image_url 传入,视频内容可以使用 video_url 传入。由于 Gradio 上传视频后通常也会得到一个本地文件路径,因此我们可以把前面只处理图片的辅助函数改成一个更通用的文件转换函数,用来同时处理图片和视频。具体代码如下:
import os
import base64
import mimetypes
from openai import OpenAI
client = OpenAI(
api_key=os.getenv("DASHSCOPE_API_KEY"),
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
)
def file_to_data_url(file_path):
mime_type, _ = mimetypes.guess_type(file_path)
if mime_type is None:
mime_type = "application/octet-stream"
with open(file_path, "rb") as file:
base64_data = base64.b64encode(file.read()).decode("utf-8")
return f"data:{mime_type};base64,{base64_data}"
这里将函数命名为 file_to_data_url(),而不是 image_to_data_url(),是因为它不再只服务于图片,也可以用于视频文件。函数会根据文件路径自动判断文件类型,并把本地文件转换成接口可以接收的 Base64 data URL。
接下来,可以在原有图片问答函数的基础上增加一个 video_path 参数:
def visual_chat(system_prompt, user_prompt, image_path, video_path):
content = []
if image_path is not None:
image_url = file_to_data_url(image_path)
content.append({
"type": "image_url",
"image_url": {
"url": image_url
}
})
if video_path is not None:
video_url = file_to_data_url(video_path)
content.append({
"type": "video_url",
"video_url": {
"url": video_url
},
"fps": 2
})
content.append({
"type": "text",
"text": user_prompt
})
response = client.chat.completions.create(
model="qwen3.6-plus",
messages=[
{
"role": "system",
"content": system_prompt
},
{
"role": "user",
"content": content
}
]
)
answer = response.choices[0].message.content
return answer
这个函数相比前面的图片问答函数多了一个 video_path 参数。函数内部会分别判断用户是否上传了图片和视频:如果上传了图片,就把图片加入 content;如果上传了视频,就把视频加入 content;最后再把用户输入的问题加入 content。
其中,视频部分的格式如下:
{
"type": "video_url",
"video_url": {
"url": video_url
},
"fps": 2
}
这里的 fps 可以简单理解为视频抽帧频率。模型在理解视频时,并不是把视频的每一帧都完整读取,而是按照一定频率从视频中抽取画面进行分析。对于课堂演示来说,可以先设置为 2,表示每秒抽取一定数量的画面供模型理解。
这样改造后,这个函数就可以支持多种输入方式:
只输入文字:
系统提示词 + 用户问题 → 文本问答
上传图片:
系统提示词 + 用户问题 + 图片 → 图片问答
上传视频:
系统提示词 + 用户问题 + 视频 → 视频问答
同时上传图片和视频:
系统提示词 + 用户问题 + 图片 + 视频 → 综合视觉问答
函数改造完成后,页面也需要增加一个视频上传组件。页面构建代码如下:
import gradio as gr
demo = gr.Interface(
fn=visual_chat,
inputs=[
gr.Textbox(
label="系统提示词",
value="你是一名耐心的多模态人工智能课程助教,能够根据文字、图片或视频内容回答问题。",
lines=3
),
gr.Textbox(
label="用户问题",
placeholder="例如:请描述图片内容,或概括视频中发生了什么。",
lines=3
),
gr.Image(
label="上传图片(可选)",
type="filepath"
),
gr.Video(
label="上传视频(可选)"
)
],
outputs=gr.Textbox(
label="模型回答",
lines=10
),
title="视觉问答大模型应用",
description="可以只输入文字,也可以上传图片或视频,让多模态大模型结合内容进行回答。"
)
demo.launch()
运行页面后,用户可以根据实际需求选择不同的使用方式。如果只想进行普通文本问答,可以不上传图片和视频,只输入问题;如果想分析图片,可以上传图片并输入“请描述这张图片的主要内容”;如果想分析视频,可以上传一段较短的视频,并输入“这视频说了什么?”。模型就会根据视频内容进行回复。

通过这个进阶案例可以看到,文本问答、图片问答和视频问答的核心思路是一致的:Gradio 负责接收页面输入,Python 函数负责整理消息并调用模型,模型返回结果后再由 Gradio 展示到页面上。 区别只在于,输入内容从单一文本逐步扩展为了文本、图片和视频等多模态数据。
3.3 全模态问答页面:文字、图片、音频、视频与语音输出
前面我们已经完成了文本问答、图片问答以及视觉问答页面的构建。到这里,页面已经可以接收文字、图片和视频等内容。接下来,我们继续在这个基础上扩展功能,加入音频输入和语音输出,构建一个更完整的全模态模型应用页面。
所谓全模态模型,可以简单理解为:模型不只处理文字,也可以理解图片、音频、视频等多种类型的数据,并且输出结果也不一定只有文字,还可以生成语音。比如,用户可以输入一个普通问题,也可以上传图片让模型描述画面内容,上传音频让模型分析语音内容,上传视频让模型概括视频中发生的事情;模型处理完成后,可以返回文字回答,也可以同时返回语音回答。
本节使用 qwen3.5-omni-flash 模型进行演示。相比前面的文本模型和视觉模型,全模态模型的特点在于:输入模态更加丰富,输出形式也更加灵活。因此,页面中除了 Textbox、Image、Video 外,还需要加入 Audio 音频组件,并增加一个 Radio 单选框,让用户选择输出格式。
为了能够实现全模态模型,需要额外安装以下库:
pip install soundfile
3.3.1 全模态问答函数构建
在前面的视觉问答函数中,我们已经学习过如何把图片或视频加入到 messages 中。现在要加入音频,本质上也是类似的思路:先判断用户是否上传了对应文件,如果上传了,就把文件转换成接口可以识别的格式,再加入到用户消息中。
根据百炼 Qwen-Omni 的调用方式,图片可以使用 image_url,音频可以使用 input_audio,视频可以使用 video_url。同时,Omni 模型当前需要使用流式输出,并可以通过 modalities=["text"] 或 modalities=["text", "audio"] 控制输出文本或文本加音频。
由于图片、音频和视频都可能来自 Gradio 上传后的本地文件路径,因此可以先写一个通用的文件转换函数:
import os
import base64
import mimetypes
import tempfile
import numpy as np
import soundfile as sf
from openai import OpenAI
client = OpenAI(
api_key=os.getenv("DASHSCOPE_API_KEY"),
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1"
)
def file_to_data_url(file_path):
mime_type, _ = mimetypes.guess_type(file_path)
if mime_type is None:
mime_type = "application/octet-stream"
with open(file_path, "rb") as file:
base64_data = base64.b64encode(file.read()).decode("utf-8")
return f"data:{mime_type};base64,{base64_data}"
def get_file_format(file_path, default_format="wav"):
file_format = os.path.splitext(file_path)[1].replace(".", "").lower()
if file_format == "":
file_format = default_format
return file_format
其中,file_to_data_url() 用于把本地文件转换成 Base64 格式的 data URL。图片、音频和视频都可以通过这个函数进行处理。get_file_format() 用来获取音频文件格式,例如 wav、mp3 等,后面传入音频时会用到。
接下来构建全模态模型调用函数:
def omni_chat(system_prompt, user_prompt, image_path, audio_path, video_path, output_format):
content = []
# 1. 如果上传了图片,就加入图片内容
if image_path is not None:
image_url = file_to_data_url(image_path)
content.append({
"type": "image_url",
"image_url": {
"url": image_url
}
})
# 2. 如果上传了音频,就加入音频内容
if audio_path is not None:
audio_data = file_to_data_url(audio_path)
audio_format = get_file_format(audio_path, default_format="wav")
content.append({
"type": "input_audio",
"input_audio": {
"data": audio_data,
"format": audio_format
}
})
# 3. 如果上传了视频,就加入视频内容
if video_path is not None:
video_url = file_to_data_url(video_path)
content.append({
"type": "video_url",
"video_url": {
"url": video_url
}
})
# 4. 最后加入用户文本问题
content.append({
"type": "text",
"text": user_prompt
})
messages = [
{
"role": "system",
"content": system_prompt
},
{
"role": "user",
"content": content
}
]
# 根据用户选择设置输出模态
if output_format == "输出文字和语音":
modalities = ["text", "audio"]
audio_config = {
"voice": "Tina",
"format": "wav"
}
else:
modalities = ["text"]
audio_config = None
request_params = {
"model": "qwen3.5-omni-flash",
"messages": messages,
"modalities": modalities,
"stream": True,
"stream_options": {
"include_usage": True
}
}
if audio_config is not None:
request_params["audio"] = audio_config
completion = client.chat.completions.create(**request_params)
answer = ""
audio_base64_string = ""
for chunk in completion:
if chunk.choices and chunk.choices[0].delta.content:
answer += chunk.choices[0].delta.content
if (
chunk.choices
and hasattr(chunk.choices[0].delta, "audio")
and chunk.choices[0].delta.audio
):
audio_base64_string += chunk.choices[0].delta.audio.get("data", "")
audio_path_result = None
if audio_base64_string:
wav_bytes = base64.b64decode(audio_base64_string)
audio_np = np.frombuffer(wav_bytes, dtype=np.int16)
audio_path_result = tempfile.NamedTemporaryFile(
delete=False,
suffix=".wav"
).name
sf.write(audio_path_result, audio_np, samplerate=24000)
return answer, audio_path_result
这个函数一共接收六个参数:system_prompt、user_prompt、image_path、audio_path、video_path 和 output_format。其中,前两个参数对应系统提示词和用户问题;中间三个参数对应用户上传的图片、音频和视频;最后一个参数来自页面上的单选框,用来决定模型输出文字,还是同时输出文字和语音。
函数内部首先创建一个 content 列表,然后依次判断用户是否上传了图片、音频和视频。如果上传了图片,就加入 image_url;如果上传了音频,就加入 input_audio;如果上传了视频,就加入 video_url。最后,再把用户输入的问题以 text 形式加入进去。
例如,音频输入部分使用的是:
{
"type": "input_audio",
"input_audio": {
"data": audio_data,
"format": audio_format
}
}
这里的 data 表示音频数据,format 表示音频格式,例如 wav 或 mp3。这样模型才能正确解析上传的音频内容。
接着,函数会根据用户选择的输出格式设置 modalities。如果用户选择“只输出文字”,则使用:
modalities = ["text"]
如果用户选择“输出文字和语音”,则使用:
modalities = ["text", "audio"]
audio_config = {
"voice": "Tina",
"format": "wav"
}
由于 Omni 模型采用流式返回,所以代码中使用 for chunk in completion 不断接收模型返回的内容。文本片段会被拼接到 answer 中,音频片段会被拼接到 audio_base64_string 中。最后,如果模型返回了音频,就将音频数据保存成一个临时 .wav 文件,并把文件路径返回给 Gradio 的音频组件播放。这个函数整体可以概括为:
接收文字、图片、音频、视频和输出格式选择
→ 判断用户上传了哪些文件
→ 将不同模态内容加入 content
→ 根据用户选择设置输出模态
→ 调用 qwen3.5-omni-flash
→ 返回文字回答和可选语音回答
3.3.2 全模态问答页面搭建
完成全模态模型调用函数后,就可以使用 gr.Interface() 构建页面。与前面的文本问答和图片问答相比,这里的页面输入组件更多,包括系统提示词、用户问题、图片上传、音频上传、视频上传以及输出格式选择。
页面构建代码如下:
import gradio as gr
demo = gr.Interface(
fn=omni_chat,
inputs=[
gr.Textbox(
label="系统提示词",
value="你是一名耐心的全模态人工智能课程助教,能够根据文字、图片、音频或视频内容回答问题。",
lines=3
),
gr.Textbox(
label="用户问题",
placeholder="例如:请描述上传内容的主要信息,或回答我提出的问题。",
lines=3
),
gr.Image(
label="上传图片(可选)",
type="filepath"
),
gr.Audio(
label="上传音频(可选)",
type="filepath"
),
gr.Video(
label="上传视频(可选)"
),
gr.Radio(
choices=["只输出文字", "输出文字和语音"],
value="只输出文字",
label="输出格式"
)
],
outputs=[
gr.Textbox(
label="文字回答",
lines=12
),
gr.Audio(
label="语音回答"
)
],
title="全模态大模型应用",
description="可以输入文字,也可以上传图片、音频或视频,并选择模型输出文字或文字加语音。"
)
demo.launch()
在这段代码中,页面一共包含六个输入组件。第一个 Textbox 用于输入系统提示词,第二个 Textbox 用于输入用户问题,Image 用于上传图片,Audio 用于上传音频,Video 用于上传视频,Radio 用于选择输出格式。
这里新增的单选框代码如下:
gr.Radio(
choices=["只输出文字", "输出文字和语音"],
value="只输出文字",
label="输出格式"
)
其中,choices 用来设置可选项,value 用来设置默认选项,label 用来设置组件标题。由于这里的输出格式只有两个选项,所以使用单选框比下拉框更加直观。
同时,输出组件也从一个文本框扩展成了两个组件:
outputs=[
gr.Textbox(
label="文字回答",
lines=12
),
gr.Audio(
label="语音回答"
)
]
这也意味着后端函数需要返回两个结果:
return answer, audio_path_result
其中,answer 会显示在 Textbox 中,audio_path_result 会传给 Audio 组件。如果用户选择“只输出文字”,audio_path_result 就是 None,页面不会播放语音;如果用户选择“输出文字和语音”,模型返回的音频会被保存成 .wav 文件,并在页面中显示为可播放音频。
由于函数定义为:
def omni_chat(system_prompt, user_prompt, image_path, audio_path, video_path, output_format):
所以 inputs 中组件的顺序也要与函数参数保持一致:
第一个 Textbox → system_prompt
第二个 Textbox → user_prompt
Image 组件 → image_path
Audio 组件 → audio_path
Video 组件 → video_path
Radio 组件 → output_format
运行页面后,用户可以根据需要选择不同的输入方式。例如,不上传任何文件时,可以作为普通文本问答页面使用;上传图片时,可以让模型描述图片内容;上传音频时,可以让模型识别音频中说了什么;上传视频时,可以让模型概括视频内容。如果选择“输出文字和语音”,模型还会同时生成一段语音回答,并在页面中播放。

通过这个案例可以看到,所谓全模态页面,本质上不是重新学习一套完全不同的开发方式,而是在前面文本问答、图片问答和视频问答的基础上,继续增加音频输入和语音输出能力。Gradio 负责接收不同模态的数据,Python 函数负责把这些数据整理成模型接口需要的格式,最后由全模态大模型完成理解和回答。
3.3.3 全模态问答页面进阶
前面我们已经完成了全模态问答页面,用户可以输入文字,也可以上传图片、音频或视频,并选择只输出文字,还是同时输出文字和语音。这个页面已经具备了全模态应用的基本形态。
不过,在实际应用中,还可以继续增加一些进阶功能。例如,当模型需要输出语音时,用户可能希望选择不同的音色;当用户询问新闻、天气、价格、政策、活动安排等实时信息时,模型也需要具备联网搜索能力。下面就在前面页面的基础上继续扩展这两个功能。
首先是音色选择。前面代码中,语音输出的音色是固定写死的:
audio={
"voice": "Tina",
"format": "wav"
}
这样虽然可以正常生成语音,但用户无法选择不同的声音。根据百炼音色列表文档,Qwen3.5-Omni 和 Qwen3.5-Omni-Realtime 系列支持多种音色,默认音色为 Tina,使用时可以将请求参数中的 voice 设置为对应的音色参数值。因此,可以在页面中增加一个音色下拉框。例如:
gr.Dropdown(
choices=["Tina", "Serena", "Ethan", "Cherry"],
value="Tina",
label="语音音色"
)
这里的 choices 表示用户可以选择的音色,value 表示默认音色。课堂演示时不需要一次性放入全部音色,可以先选几个常见音
色即可,例如 Tina、Serena、Ethan、Cherry。后续如果需要更丰富的声音效果,可以再根据官方音色列表继续补充。
其次是联网搜索。大模型本身有知识截止时间,对于“今天的天气”“最近发布的模型”“最新政策”“当前价格”等实时问题,普通模型可能无法准确回答。启用联网搜索后,模型可以从网络获取实时信息,再结合自身能力进行整理和回答。百炼联网搜索文档也说明,启用联网搜索后,模型可以回答股票价格、天气预报、最新新闻等时效性问题。
在 OpenAI 兼容的 Chat Completions API 中,可以通过 extra_body 传入 enable_search=True 开启联网搜索。对于 Qwen3.5-Omni 系列,联网搜索仅支持 agent 搜索策略,因此这里可以在页面上增加一个复选框,让用户决定是否启用联网搜索。
页面组件可以增加为:
gr.Checkbox(
label="是否启用联网搜索",
value=False
)
接下来,需要在函数中增加两个参数:voice 和 enable_search。其中,voice 用来接收用户选择的音色,enable_search 用来判断是否开启联网搜索。
函数可以修改为:
def omni_chat(
system_prompt,
user_prompt,
image_path,
audio_path,
video_path,
output_format,
voice,
enable_search
):
content = []
# 1. 如果上传了图片,就加入图片内容
if image_path is not None:
image_url = file_to_data_url(image_path)
content.append({
"type": "image_url",
"image_url": {
"url": image_url
}
})
# 2. 如果上传了音频,就加入音频内容
if audio_path is not None:
audio_data = file_to_data_url(audio_path)
audio_format = get_file_format(audio_path, default_format="wav")
content.append({
"type": "input_audio",
"input_audio": {
"data": audio_data,
"format": audio_format
}
})
# 3. 如果上传了视频,就加入视频内容
if video_path is not None:
video_url = file_to_data_url(video_path)
content.append({
"type": "video_url",
"video_url": {
"url": video_url
}
})
# 4. 最后加入用户文本问题
content.append({
"type": "text",
"text": user_prompt
})
messages = [
{
"role": "system",
"content": system_prompt
},
{
"role": "user",
"content": content
}
]
extra_body = {}
if enable_search:
extra_body = {
"enable_search": True,
"search_options": {
"search_strategy": "agent"
}
}
# 情况一:只输出文字,可以使用非流式输出
if output_format == "只输出文字":
response = client.chat.completions.create(
model="qwen3.5-omni-flash",
messages=messages,
modalities=["text"],
extra_body=extra_body
)
answer = response.choices[0].message.content
audio_path_result = None
return answer, audio_path_result
# 情况二:输出文字和语音,需要使用流式输出
completion = client.chat.completions.create(
model="qwen3.5-omni-flash",
messages=messages,
modalities=["text", "audio"],
audio={
"voice": voice,
"format": "wav"
},
stream=True,
stream_options={
"include_usage": True
},
extra_body=extra_body
)
answer = ""
audio_base64_string = ""
for chunk in completion:
if chunk.choices and chunk.choices[0].delta.content:
answer += chunk.choices[0].delta.content
if (
chunk.choices
and hasattr(chunk.choices[0].delta, "audio")
and chunk.choices[0].delta.audio
):
audio_base64_string += chunk.choices[0].delta.audio.get("data", "")
audio_path_result = None
if audio_base64_string:
wav_bytes = base64.b64decode(audio_base64_string)
audio_np = np.frombuffer(wav_bytes, dtype=np.int16)
audio_path_result = tempfile.NamedTemporaryFile(
delete=False,
suffix=".wav"
).name
sf.write(audio_path_result, audio_np, samplerate=24000)
return answer, audio_path_result
在这段代码中,voice 不再固定写成 "Tina",而是由页面下拉框传入:
audio={
"voice": voice,
"format": "wav"
}
这样用户选择不同音色时,模型生成的语音回答就会使用对应音色。
另外,enable_search 用来控制是否开启联网搜索:
if enable_search:
extra_body = {
"enable_search": True,
"search_options": {
"search_strategy": "agent"
}
}
如果用户勾选“是否启用联网搜索”,程序就会在请求中加入 enable_search=True。如果用户没有勾选,extra_body 就保持为空,模型仍然按普通方式回答。
对应的页面代码可以修改为:
import gradio as gr
demo = gr.Interface(
fn=omni_chat,
inputs=[
gr.Textbox(
label="系统提示词",
value="你是一名耐心的全模态人工智能课程助教,能够根据文字、图片、音频或视频内容回答问题。",
lines=3
),
gr.Textbox(
label="用户问题",
placeholder="例如:请描述上传内容的主要信息,或回答我提出的问题。",
lines=3
),
gr.Image(
label="上传图片(可选)",
type="filepath"
),
gr.Audio(
label="上传音频(可选)",
type="filepath"
),
gr.Video(
label="上传视频(可选)"
),
gr.Radio(
choices=["只输出文字", "输出文字和语音"],
value="只输出文字",
label="输出格式"
),
gr.Dropdown(
choices=["Tina", "Serena", "Ethan", "Cherry"],
value="Tina",
label="语音音色"
),
gr.Checkbox(
label="是否启用联网搜索",
value=False
)
],
outputs=[
gr.Textbox(
label="文字回答",
lines=12
),
gr.Audio(
label="语音回答"
)
],
title="全模态大模型应用进阶版",
description="可以输入文字,上传图片、音频或视频,并选择输出格式、语音音色和是否启用联网搜索。"
)
demo.launch()
最后,我们还需要注意联网搜索的成本。联网搜索费用主要由两部分组成:第一部分是模型调用费用,因为搜索到的网页内容会拼接到提示词中,从而增加输入 Token;第二部分是搜索策略费用。根据百炼联网搜索计费说明,agent 策略每调用 1000 次,中国内地和全球部署范围为 4 元,国际部署范围为 73.392381 元。
由于价格并不便宜,因此不建议每次请求都默认开启。更合理的做法是让用户通过复选框手动决定是否启用联网搜索:当问题涉及时效性信息时再开启,例如天气、新闻、政策、价格、比赛结果等;如果只是普通概念解释、文本总结、图片描述或音频理解,一般不需要开启联网搜索。
4. 总结
本节内容围绕 Gradio 页面开发与大模型应用构建 展开,从最基础的 gr.Interface() 入手,逐步完成了文本问答、图文问答、视频问答以及全模态问答页面的搭建。
首先,我们学习了 Gradio 的基本作用。Gradio 并不是大模型本身,而是一个帮助开发者快速构建交互式网页页面的工具。它可以把 Python 函数和页面组件连接起来,让用户通过网页输入文字、上传图片、上传音频或上传视频,再由后端函数调用大模型 API,并将模型返回结果展示到页面中。这样一来,原本只能在代码或终端中运行的大模型调用程序,就可以变成普通用户也能直接操作的应用页面。
接着,我们重点学习了 gr.Interface() 的基本使用方式。Interface 的核心在于三个参数:fn、inputs 和 outputs。其中,fn 用来绑定后端 Python 函数,inputs 用来设置页面输入组件,outputs 用来设置结果展示组件。通过这些参数,Gradio 可以自动完成页面输入、函数参数、函数返回值和页面输出之间的数据传递。
在理解 Interface 工作机制的基础上,我们进一步认识了常见组件,例如 Textbox、Image、File、Video 和 Audio。这些组件决定了用户可以通过什么方式提交数据,也决定了程序结果以什么形式展示。对于初学者来说,现阶段不需要记住所有组件,只需要理解一个核心思路:根据任务需要选择合适的组件,再把组件和处理函数连接起来。
随后,我们进入大模型应用开发实践。文本问答页面展示了最基础的应用形态:用户输入问题,后端函数调用文本大模型,模型返回文字回答。在此基础上,我们又加入系统提示词输入框,让用户可以自行调整模型角色和回答风格,从而理解多组件输入页面的构建方式。
在视觉问答部分,我们将文本问答扩展为图文问答和视频问答。通过 Image 和 Video 组件,用户可以上传图片或视频;后端函数则负责把这些文件转换成模型接口可以识别的格式,并组织成多模态消息发送给模型。这个过程说明,多模态应用并不是完全不同的新开发方式,而是在文本问答流程基础上增加不同类型的数据输入。
最后,我们进一步构建了全模态问答页面。通过 qwen3.5-omni-flash 模型,页面可以同时支持文字、图片、音频和视频输入,并允许用户选择只输出文字,或同时输出文字和语音。进阶部分还加入了音色选择和联网搜索能力,使页面更加接近真实的大模型应用场景。
通过本节学习,我们不仅掌握了 Gradio 的基础用法,更重要的是理解了大模型应用开发的一种基本思路:先写好后端处理函数,再选择合适的页面组件,最后使用 Gradio 将函数和页面连接起来。 后续无论是构建文本助手、图片分析工具、语音交互应用,还是更复杂的全模态智能应用,本质上都可以沿着这条思路继续扩展。
-- 完 --
关注机智流并加入 AI 技术交流群,不仅能和来自大厂名校的 AI 开发者、爱好者一起进行技术交流,同时还有与、、、、等。
cc | 大模型技术交流群 hf | HuggingFace 高赞论文分享群 lc|LangChain 技术交流群 code | AI Coding 交流群 具身 | 具身智能交流群 硬件 | AI 硬件交流群 推理 | AI 推理框架交流群 智能体 | Agent 技术交流群