传送门:
- 《Gradio官方教程一:Gradio生态系统、主要组件及Interface class简介》
- 《Gradio 4.37.1官方教程二:Blocks》
- 《Gradio 4.37.1官方教程三:Chatbot》
- 《Gradio 教程四:Building Generative AI Applications with Gradio》
一、使用ChatInterface创建聊天机器人
聊天机器人是大型语言模型的一种流行应用。使用 Gradio,可以轻松构建聊天机器人模型的演示。gr.ChatInterface()
是一个高级抽象,通常只需一行代码即可快速创建聊天机器人 UI,还可以与几个流行的 API 和库(包括 langchain、openai 和 Hugging Face)中的实际语言模型结合使用。
1.1 定义聊天函数
在使用 gr.ChatInterface()
时,首先需要定义聊天函数。你的聊天函数应接受两个参数:message
和 history
(参数名称可以随意,但顺序必须如此)。
message
:表示用户输入的字符串。history
:表示到目前为止的对话历史记录的列表。每个内嵌列表由两个字符串组成,表示一个对话对:[user input, bot response]
。
最终返回返回一个字符串响应(bot response
)。下面是一个简单的示例:
import random
import gradio as gr
def alternatingly_agree(message, history):
if len(history) % 2 == 0:
return f"Yes, I do think that '{
message}'"
else:
return "I don't think so"
gr.ChatInterface(alternatingly_agree).launch()
1.2 流式聊天机器人(Streaming chatbots)
流式聊天机器人的响应是逐步生成并显示的,这样用户在等待完整回复的过程中,可以逐步看到部分回复(实时反馈),提高了互动的流畅度和用户体验。在聊天函数中,使用 yield
逐步生成响应,就能实现一个流式聊天机器人。
import time
import gradio as gr
def slow_echo(message, history):
for i in range(len(message)):
time.sleep(0.3)
yield "You typed: " + message[: i+1]
gr.ChatInterface(slow_echo).launch()
slow_echo
:在每次迭代中,函数都会暂停 0.3 秒,然后将用户输入的信息一个个字母的生成出来。另外,在响应流式传输时,“Submit”
按钮会变成“Stop”
按钮,用来停止生成器函数。你可以使用 stop_btn
参数自定义“Stop”
按钮的外观和行为。
1.3 自定义聊天机器人
和Gradio的Interface类一样,gr.ChatInterface类也包括许多相同的参数,可以用来自定义聊天机器人的外观和感觉,比如:
- 使用
title
和description
参数在聊天机器人上方添加标题和描述。 - 使用
theme
和css
参数添加主题或自定义CSS。 - 使用
placeholder
参数用于设置聊天界面的“占位符”(用户未输入时的起始显示,用于提示用户输入),该参数接受Markdown或HTML。占位符会在聊天机器人中居中显示。 - 使用
examples
添加示例,并设置cache_examples=True
缓存示例(启动demo时自动运行示例,缓存结果)。 - 更改或禁用聊天界面中的各个按钮:
submit_btn
、retry_btn
、undo_btn
、clear_btn
。 - 自定义ChatInterface中的
gr.Chatbot
或gr.Textbox
等组件
import gradio as gr
def yes_man(message, history):
if message.endswith("?"):
return "Yes"
else:
return "Ask me anything!"
gr.ChatInterface(
yes_man,
chatbot=gr.Chatbot(height=300,placeholder="<strong>Your Personal Yes-Man</strong><br>Ask Me Anything"),
textbox=gr.Textbox(placeholder="Ask me a yes or no question", container=False, scale=7),
title="Yes Man",
description="Ask Yes Man any question",
theme="soft",
examples=["Hello", "Am I cool?", "Are tomatoes vegetables?"],
cache_examples=True,
retry_btn=None,
undo_btn="Delete Previous",
clear_btn="Clear",
).launch()
1.4 添加多模态功能
你可以向聊天机器人添加多模态功能。例如,你可能希望用户能够轻松上传图片或文件,并向聊天机器人提问。在gr.ChatInterface
中设置multimodal=True
可以使聊天机器人“多模态化”。
当multimodal=True
时,函数fn
的签名会稍微变化。你的函数的第一个参数应该接受一个包含提交文本和上传文件的字典,比如{"text": "user input", "file": ["file_path1", "file_path2", ...]}
。同样,你提供的任何示例也应该是这种形式的字典。函数返回值不变,仍旧返回一个单一的字符串消息。
import gradio as gr
import time
def count_files(message, history):
num_files = len(message["files"])
return f"You uploaded {
num_files} files"
demo = gr.ChatInterface(fn=count_files, examples=[{
"text": "Hello", "files": []}], title="Echo Bot", multimodal=True)
demo.launch()
✍️ 提示:如果你想自定义多模态聊天机器人的文本框的UI/UX,你应该传递一个
gr.MultimodalTextbox
实例到ChatInterface的textbox
参数,而不是gr.Textbox
实例。
1.5 通过additional_inputs
添加额外组件
通过additional_inputs
参数可以添加额外的输入组件,例如,添加一个系统提示的文本框,或设置聊天机器人响应中token数量的滑块。以下是更具体的说明:
additional_inputs
参数接受一个组件或组件列表,你可以直接传递组件实例,或使用它们的字符串快捷方式(比如用“textbox”
代替gr.Textbox()
)- 如果传递的组件尚未渲染,会出现在聊天机器人(和任何示例)下面的折叠面板中(
gr.Accordion()
,可以打开/折叠)。可以使用additional_inputs_accordion_name
参数设置此折叠面板的标签
下面创建一个带有额外输入参数的聊天机器人界面,用户可以在折叠面板的系统提示语文本框中输入自定义提示语,使用滑块设置响应的最大token数量。聊天机器人根据用户输入的消息和系统提示语生成响应,并逐字符显示响应内容,模拟打字效果。
import gradio as gr
import time
def echo(message, history, system_prompt, tokens):
response = f"System prompt: {
system_prompt}\n Message: {
message}."
for i in range(min(len(response), int(tokens))):
time.sleep(0.05)
yield response[: i + 1]
demo = gr.ChatInterface(
echo,chatbot=gr.Chatbot(height=150),
additional_inputs=[
gr.Textbox("You are helpful AI.", label="System Prompt"),
gr.Slider(10, 100),
],
)
if __name__ == "__main__":
demo.queue().launch()
ChatInterface的
input
参数通常用于主要的用户输入。而代码中的system_prompt
和tokens
是辅助配置,把这些参数放在additional_inputs
中,可以与用户输入明确区分,让UI界面更清晰。
如果组件已经在父gr.Blocks()
中被渲染,则不会在折叠面板中重新渲染。这提供了灵活性,可以决定输入组件的布局位置。例如,我们可以将gr.Textbox()
放在聊天机器人UI的顶部,同时将滑块放在下面:
import gradio as gr
import time
def echo(message, history, system_prompt, tokens):
response = f"System prompt: {
system_prompt}\n Message: {
message}."
for i in range(min(len(response), int(tokens))):
time.sleep(0.05)
yield response[: i+1]
with gr.Blocks() as demo:
system_prompt = gr.Textbox("You are helpful AI.", label="System Prompt")
slider = gr.Slider(10, 100, render=False) # 设置滑块组件不渲染
gr.ChatInterface(
echo, additional_inputs=[system_prompt, slider],
)
demo.launch()
如果需要创建更自定义的内容,最好使用底层的gr.Blocks()
API构建聊天机器人UI(见下一章节)
1.6 直接添加 Gradio 组件
在Gradio的Chatbot
组件中,可以使用许多核心的Gradio组件(如gr.Image
、gr.Plot
、gr.Audio
和gr.HTML
),使聊天机器人可以返回更丰富的多媒体内容。只需从你的函数中返回这些组件之一,就可以在gr.ChatInterface
中使用它们。
下面是一个示例,用户可以通过输入框请求播放特定艺术家的音乐。如果用户输入有效信息,聊天机器人将返回一个音频文件的链接并播放;如果输入无效,聊天机器人会提示用户提供艺术家名称。
import gradio as gr
def fake(message, history):
if message.strip():
return gr.Audio("https://github.com/gradio-app/gradio/raw/main/test/test_files/audio_sample.wav")
else:
return "Please provide the name of an artist"
gr.ChatInterface(
fake,
textbox=gr.Textbox(placeholder="Which artist's music do you want to listen to?", scale=7),
chatbot=gr.Chatbot(placeholder="Play music by any artist!"),
).launch()
textbox
组件用于指定用户消息输入框,而chatbot
组件用于指定聊天机器人的显示区域,都是主要的聊天功能组件。
1.7 通过 API 使用聊天机器人
1.7.1 调用托管的聊天机器人
即使你在Hugging Face Spaces或其他地方托管了你的Gradio聊天机器人,可以通过一个简单的API在/chat
端点与其进行交互。端点只需要用户的消息(以及可能的额外输入,如果你在创建聊天机器人时使用了additional_inputs
参数),API内部会自动跟踪到目前为止发送的所有消息,以便生成上下文相关的响应。要调用这个API端点,可以使用 Gradio Python Client 或Gradio JS client。
Gradio Python client
可以让我们非常容易地将任何Gradio应用当作API来使用。以托管在Hugging Face Spaces的abidlabs/whisper为例,这个应用可以从麦克风录制音频文件并进行转录。通过Gradio Python client
,可以编程方式地使用这个Gradio应用来转录音频文件。
# pip install --upgrade gradio_client
from gradio_client import Client, file
# 创建客户端实例,指向托管的Gradio应用
client = Client("abidlabs/whisper")
# 使用Gradio应用的API来转录音频文件
client.predict(
audio=file("audio_sample.wav")
)
>> "This is a test of the whisper speech recognition model."
1.7.2 使用Langchain构建流式聊天机器人
from langchain.chat_models import ChatOpenAI
from langchain.schema import AIMessage, HumanMessage
import openai
import gradio as gr
os.environ["OPENAI_API_KEY"] = "sk-..." # Replace with your key
llm = ChatOpenAI(temperature=1.0, model='gpt-3.5-turbo-0613')
def predict(message, history):
history_langchain_format = []
for human, ai in history:
history_langchain_format.append(HumanMessage(content=human))
history_langchain_format.append(AIMessage(content=ai))
history_langchain_format.append(HumanMessage(content=message))
gpt_response = llm(history_langchain_format)
return gpt_response.content
gr.ChatInterface(predict).launch()
这段代码的逻辑与之前的类似,只是在predict
函数中,通过LangChain加载了OpenAI的大语言模型来生成用户响应。
1.7.3 使用openai 构建流式聊天机器人
我们也可以直接使用openai来进行构建:
from openai import OpenAI
import gradio as gr
api_key = "sk-..." # Replace with your key
client = OpenAI(api_key=api_key)
def predict(message, history):
history_openai_format = []
for human, assistant in history:
history_openai_format.append({
"role": "user", "content": human })
history_openai_format.append({
"role": "assistant", "content":assistant})
history_openai_format.append({
"role": "user", "content": message})
response = client.chat.completions.create(model='gpt-3.5-turbo',
messages= history_openai_format,
temperature=1.0,
stream=True)
partial_message = ""
for chunk in response:
if chunk.choices[0].delta.content is not None:
partial_message = partial_message + chunk.choices[0].delta.content
yield partial_message
gr.ChatInterface(predict).launch()
1.7.4 使用本地开源LLM进行构建
在更多情况下,你可能希望在本地运行聊天机器人。下面是一个使用Hugging Face上的
RedePajama model的示例,这需要GPU。
import gradio as gr
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer, StoppingCriteria, StoppingCriteriaList, TextIteratorStreamer
from threading import Thread
tokenizer = AutoTokenizer.from_pretrained("togethercomputer/RedPajama-INCITE-Chat-3B-v1")
model = AutoModelForCausalLM.from_pretrained("togethercomputer/RedPajama-INCITE-Chat-3B-v1", torch_dtype=torch.float16)
model = model.to('cuda:0')
class StopOnTokens(StoppingCriteria):
def __call__(self, input_ids: torch.LongTensor, scores: torch.FloatTensor, **kwargs) -> bool:
stop_ids = [29, 0]
for stop_id in stop_ids:
if input_ids[0][-1] == stop_id:
return True
return False
def predict(message, history): # 接受用户的输入消息和聊天历史记录
history_transformer_format = history + [[message, ""]] # 将聊天历史转换为 Transformer 模型所需的格式
stop = StopOnTokens()
messages = "".join(["".join(["\n<human>:"+item[0], "\n<bot>:"+item[1]])
for item in history_transformer_format]) # 将新消息附加到历史记录中
model_inputs = tokenizer([messages], return_tensors="pt").to("cuda")
# 创建 TextIteratorStreamer 对象用于逐步生成文本
streamer = TextIteratorStreamer(tokenizer, timeout=10., skip_prompt=True, skip_special_tokens=True)
generate_kwargs = dict(
model_inputs,
streamer=streamer,
max_new_tokens=1024,
do_sample=True,
top_p=0.95,
top_k=1000,
temperature=1.0,
num_beams=1,
stopping_criteria=StoppingCriteriaList([stop])
)
# 通过多线程方式调用 model.generate 生成文本,并将结果逐步传给 streamer
t = Thread(target=model.generate, kwargs=generate_kwargs)
t.start()
# 生成过程中逐步产出部分消息 (partial_message),实现流式输出
partial_message = ""
for new_token in streamer:
if new_token != '<':
partial_message += new_token
yield partial_message
gr.ChatInterface(predict).launch()
代码中, StopOnTokens
:继承自 StoppingCriteria
,用于实现自定义的停止条件。在生成文本时,如果生成的 token 序列中包含 ID 为 29 或 0 的标记,生成过程将终止( <pad>
或结束标记<eos>
)。__call__
方法会在生成每个新 token 时被调用,以检查是否应该停止生成。
二、 使用Blocks 创建自定义聊天机器人
2.1 快速入门
Gradio 提供了两种不同的方式来创建聊天机器人:Gradio Blocks
和 gr.ChatInterface
。Gradio Blocks是更底层的API,允许开发者完全自定义聊天机器人的界面和行为。开发者可以自定义各种组件的排列、样式和交互方式,可以自定义事件处理逻辑,适合需要复杂布局或特殊功能的应用。
下面我们先使用 gradio.Blocks
类来构建一个简单的聊天机器人示例,这个机器人会随机响应"How are you?", "I love you", "I'm very hungry"
。
import gradio as gr
import random
import time
with gr.Blocks() as demo:
chatbot = gr.Chatbot()
msg = gr.Textbox()
clear = gr.ClearButton([msg, chatbot])
def respond(message, chat_history):
# chat_history是一个包含(message, bot_message)二元组的列表,表示历史对话记录
bot_message = random.choice(["How are you?", "I love you", "I'm very hungry"])
chat_history.append((message, bot_message))
time.sleep(2) # 模拟机器人响应的延迟
return "", chat_history # 返回一个二元组,第一个元素是一个空字符串,用于清空输入框
msg.submit(respond, [msg, chatbot], [msg, chatbot])
demo.launch()
这里有三个 Gradio 组件:
Chatbot
:存储了整个对话历史,作为用户和机器人响应对的列表。Textbox
:用户输入框,按回车或提交按钮以触发聊天机器人响应。ClearButton
:清除Textbox
和Chatbot
。
respond
函数接收整个聊天机器人的历史记录,并追加一个随机消息。在等待 2 秒后返回更新后的聊天历史。函数在返回时也会清除文本框。在实际应用中,你可以用更复杂的函数替换 respond
,比如调用预训练模型或 API 来生成响应。
2.2 为聊天机器人添加流式响应
我们可以通过几种方式改进上面的聊天机器人用户体验。首先,我们可以流式传输响应,这样用户可以看到实时反馈。其次,我们可以在生成机器人的响应时也显示用户的消息。
import gradio as gr
import random
import time
with gr.Blocks() as demo:
chatbot = gr.Chatbot()
msg = gr.Textbox()
clear = gr.Button("Clear")
def user(user_message, history):
return "", history + [[user_message, None]]
def bot(history):
bot_message = random.choice(["How are you?", "I love you", "I'm very hungry"])
history[-1][1] = ""
for character in bot_message:
history[-1][1] += character
time.sleep(0.05)
yield history
msg.submit(user, [msg, chatbot], [msg, chatbot], queue=False).then(
bot, chatbot, chatbot
)
clear.click(lambda: None, None, chatbot, queue=False)
demo.queue()
demo.launch()
-
user
函数 :处理用户输入,将用户消息(user_message, None)
添加到聊天历史中(其中None
表示机器人尚未响应),同时清空输入框。此方法还使输入字段非交互式,以便用户在聊天机器人响应时无法发送另一条消息。 -
bot
函数:生成机器人的回复,并逐个字母地显示出来,每次暂停0.05s,模拟打字效果。这里没有创建新消息,只是用机器人的响应替换之前创建的None
消息。Gradio 会自动将任何包含yield
关键字的函数转换为流式输出界面。
.then()
方法:主要功能是链式连接多个事件,实现复杂的交互逻辑。
- 顺序执行:使得一个事件在另一个事件完成后执行
- 数据传递:一个事件的输出可以作为下一个事件的输入,使得数据流在整个链条中保持连贯。
- 异步处理:可以处理异步操作,例如等待某个操作完成后再执行下一个操作。这对于处理API调用或复杂计算非常有用。
- 提高可读性和维护性:通过链式调用,使代码更具可读性和结构化,方便理解和维护。
.then()
方法主要参数为(fn,input,output,queue)
,其中默认queue=True
。通过设置queue=False
可以禁用队列,以便立即执行。本例中,当用户提交消息时,通过.then()
方法将三个事件链式连接:
msg.submit(user, [msg, chatbot], [msg, chatbot], queue=False)
:第一个事件,调用user
函数,处理用户输入,更新聊天记录并清空输入框。.then(bot, chatbot, chatbot)
:第二个事件,调用bot
函数,更新聊天历史,用机器人的回复替换之前创建的None
消息,并逐字显示回复内容。clear.click(lambda: None, None, chatbot, queue=False)
:第三个事件,清空聊天窗口。输入框重新变为可交互状态,允许用户继续输入消息。- 触发回调函数
lambda: None
- 回调函数返回
None
,不进行任何操作。 - 输出目标是
chatbot
,因此Gradio清除chatbot
组件的内容 queue=False
确保这个操作立即执行,不会被排队。
- 触发回调函数
最后,我们通过运行 demo.queue()
启用队列功能,以便实现中间流式输出的功能。
2.3 添加点赞和点踩功能
可以为聊天机器人添加点赞和点踩功能,以便用户可以对每条机器人的回复进行评价。通过附加.like()
事件,就可以在每条机器人消息旁边自动显示点赞和点踩图标。
.like()
方法需要传入一个函数,当用户点击图标时调用该函数。该函数应有一个gr.LikeData
类型的参数,Gradio会自动传入包含被点赞或点踩消息信息的对象。下面是一个简单的示例:
import gradio as gr
def greet(history, input):
return history + [(input, "Hello, " + input)]
def vote(data: gr.LikeData):
if data.liked:
print("You upvoted this response: " + data.value)
else:
print("You downvoted this response: " + data.value)
with gr.Blocks() as demo:
chatbot = gr.Chatbot()
textbox = gr.Textbox()
textbox.submit(greet, [chatbot, textbox], [chatbot]) # 调用greet函数更新聊天记录,显示在聊天机器人组件中
chatbot.like(vote, None, None) # 绑定vote函数。当用户点击点赞或点踩图标时,调用vote函数处理投票数据。
demo.launch()
vote
:投票函数,接收一个gr.LikeData
类型的参数data,包含关于被点赞或点踩消息的信息。根据data.liked
的值,打印用户是否点赞或点踩了该消息。chatbot.like(vote, None, None)
:inputs: None
表示回调函数 vote 不需要从其他组件接收输入。Gradio 会自动传递 gr.LikeData 对象给 vote 函数。outputs: None
表示回调函数 vote 不需要更新任何组件的输出。因为点赞或点踩操作只是记录用户的反馈,没有必要更新界面上的其他组件。
2.4 添加 Markdown、图像、音频或视频
gr.Chatbot
组件支持一部分Markdown格式,包括加粗、斜体和代码。例如,可以用加粗的文本回复用户消息,如“That’s cool!”。
def bot(history):
response = "**That's cool!**"
history[-1][1] = response
return history
gr.Chatbot
可以处理媒体文件,如图片、音频和视频。使用MultimodalTextbox
组件,可以方便地上传各种类型的媒体文件到聊天机器人。传递媒体文件的格式是一个包含两个字符串的元组,如(filepath, alt_text)
。后者是可选的,所以也可以只传一个包含单个元素的元组(filepath,)
。
def add_message(history, message):
for x in message["files"]:
history.append(((x["path"],), None))
if message["text"] is not None:
history.append((message["text"], None))
return history, gr.MultimodalTextbox(value=None, interactive=False, file_types=["image"])
下面是完整的示例代码,我们使用MultimodalTextbox
组件创建了一个带有多模态文本框的多模态聊天机器人。
import gradio as gr
import os
import plotly.express as px
# Chatbot demo with multimodal input (text, markdown, LaTeX, code blocks, image, audio, & video). Plus shows support for streaming text.
def random_plot():
df = px.data.iris()
fig = px.scatter(df, x="sepal_width", y="sepal_length", color="species",
size='petal_length', hover_data=['petal_width'])
return fig
def print_like_dislike(x: gr.LikeData):
print(x.index, x.value, x.liked)
def add_message(history, message):
for x in message["files"]:
history.append(((x,), None))
if message["text"] is not None:
history.append((message["text"], None))
return history, gr.MultimodalTextbox(value=None, interactive=False)
def bot(history):
history[-1][1] = "Cool!"
return history
fig = random_plot()
with gr.Blocks(fill_height=True) as demo:
chatbot = gr.Chatbot(
elem_id="chatbot",
bubble_full_width=False,
scale=1,
)
chat_input = gr.MultimodalTextbox(interactive=True,
file_count="multiple",
placeholder="Enter message or upload file...", show_label=False)
chat_msg = chat_input.submit(add_message, [chatbot, chat_input], [chatbot, chat_input])
bot_msg = chat_msg.then(bot, chatbot, chatbot, api_name="bot_response")
bot_msg.then(lambda: gr.MultimodalTextbox(interactive=True), None, [chat_input])
chatbot.like(print_like_dislike, None, None)
demo.queue()
demo.launch()