大模型WebUI:Gradio全解11——Chatbots:融合大模型的聊天机器人(1)
- 前言
- 本篇摘要
- 11. Chatbot:融合大模型的多模态聊天机器人
- 11.1 gr.ChatInterface()快速创建Chatbot
- 11.1.1 定义聊天函数
- 1. 随机回答“是”或“否”的聊天机器人
- 2. 交替同意和反对的聊天机器人
- 3. 流式聊天机器人
- 11.1.2 自定义聊天界面
- 1. 自定义观感
- 2. 多模态聊天界面
- 11.1.3 附加输入输出
- 1. additional_inputs:附加输入
- 2. additional_outputs:附加输出
- 11.1.4 返回复杂响应
- 1. 返回Gradio组件
- 2. 提供预设回复
- 3. 返回多条消息
- 11.1.5 通过API加载聊天机器人
- 1. load_chat:从OpenAI API端点
- 2. /chat:通过页面API端点
- 11.1.6 聊天历史和用户反馈
- 1. 聊天历史记录
- 2. 收集用户反馈
- 参考文献
前言
本系列文章主要介绍WEB界面工具Gradio。Gradio是Hugging Face发布的简易webui开发框架,它基于FastAPI和svelte,便于开发多功能界面和部署人工智能模型,是当前热门的非常易于开发和展示机器学习大语言模型LLM及扩散模型DM的UI框架。本系列文章分为前置概念和实战演练两部分。前置概念先介绍Gradio的详细技术架构、历史、应用场景、与其他框架Gradio/NiceGui/StreamLit/Dash/PyWebIO的区别,然后详细介绍了著名的资源网站Hugging Face,因为Gradio演示中经常用到Hugging Face的models及某些场景需要部署在spaces,这里包括三类资源models/datasets/spaces的使用、六类工具库transformers/diffusers/datasets/PEFT/accelerate/optimum实战。实战演练部分先讲解了多种不同的安装、运行和部署方式,安装包括Linux/Win/Mac三类系统安装,运行包括普通方式和热重载方式,部署包括本地部署、HuggingFace托管、FastAPI挂载和Gradio-Lite浏览器集成;然后按照先整体再细节的逻辑,讲解Gradio的多种高级特性,包括三种Gradio Clients(python/javascript/curl)、Gradio Tools、Gradio的模块架构和环境变量等,方便读者对Gradio整体把握;最后深入细节,也是本系列文章的核心,先实践基础功能Interface、Blocks和Additional Features,再详解高级功能Chatbots、Data Science And Plots和Streaming。本系列文章讲解细致,涵盖Gradio大部分组件和功能,代码均可运行并附有大量运行截图,方便读者理解,Gradio一定会成为每个技术人员实现奇思妙想的最称手工具。
本系列文章目录如下:
- 《Gradio全解1——Gradio简介》
- 《Gradio全解1——Gradio的安装与运行》
- 《Gradio全解2——剖析Hugging Face:详解三类资源models/datasets/spaces》
- 《Gradio全解3——剖析Hugging Face:实战六类工具库transformers/diffusers/datasets/PEFT/accelerate/optimum》
- 《Gradio全解4——Gradio的3+1种部署方式实践》
- 《Gradio全解4——浏览器集成Gradio-Lite》
- 《Gradio全解5——Gradio Client:python客户端》
- 《Gradio全解5——Gradio Client:javascript客户端》
- 《Gradio全解5——Gradio Client:curl客户端》
- 《Gradio全解6——Gradio Tools:将Gradio用于LLM Agents》
- 《Gradio全解7——Gradio库的模块架构和环境变量》
- 《Gradio全解8——Interface:高级抽象界面类(上)》
- 《Gradio全解8——Interface:高级抽象界面类(下)》
- 《Gradio全解9——Blocks:底层区块类(上)》
- 《Gradio全解9——Blocks:底层区块类(下)》
- 《Gradio全解10——Additional Features:补充特性(上)》
- 《Gradio全解10——Additional Features:补充特性(下)》
- 《Gradio全解11——Chatbot:融合大模型的多模态聊天机器人(上)》
- 《Gradio全解11——Chatbot:融合大模型的多模态聊天机器人(下)》
- 《Gradio全解系列12——Data Science And Plots:数据科学与绘图》
- 《Gradio全解13——Streaming:数据流(上)》
- 《Gradio全解13——Streaming:数据流(下)》
本篇摘要
本篇介绍如何使用Gradio创建聊天机器人,主要内容包括gr.ChatInterface快速创建Chatbot、与流行LLM库及API结合、使用Agents和Tools智能代理工具、使用Blocks创建Chatbot、Chatbot的特殊Events、通过Gradio应用创建Discord Bot/Slack Bot/Website Widget。
11. Chatbot:融合大模型的多模态聊天机器人
本章介绍如何使用Gradio创建聊天机器人。聊天机器人是大型语言模型(LLMs)的一个流行应用,通过Gradio,我们可以轻松构建LLM演示并与其它用户分享,或者自己使用直观的聊天机器人界面进行开发尝试。本章主要内容包括gr.ChatInterface快速创建Chatbot、与流行LLM库及API结合、使用Agents和Tools智能代理工具、使用Blocks创建Chatbot、Chatbot的特殊Events、通过Gradio应用创建Discord Bot/Slack Bot/Website Widget。
11.1 gr.ChatInterface()快速创建Chatbot
本教程使用gr.ChatInterface(),这是一个高级抽象类,通常只需一行Python代码就可以快速创建聊天机器人界面。它可以轻松支持多模态聊天机器人或需要进一步定制的聊天机器人。本节内容主要包括定义聊天函数、自定义聊天界面、附加输入输出、返回复杂响应、通过API加载聊天机器人、聊天历史和用户反馈。
前提条件:请确保使用的是最新版Gradio,升级命令:$ pip install --upgrade gradio
。
11.1.1 定义聊天函数
在使用gr.ChatInterface()时,首先要做的是定义聊天函数。在最简单的情况下,聊天函数应接受两个参数:message和history(参数可以任意命名,但必须按此顺序):
- message:一个字符串,表示用户的最新消息;
- history:一个包含role和content键的openai-style的字典列表,表示之前的对话历史,还可能包含表示消息元数据的其他键。例如,history可能如下所示:
[
{"role": "user", "content": "What is the capital of France?"},
{"role": "assistant", "content": "Paris"}
]
而聊天函数只需返回一个字符串值,表示聊天机器人基于聊天历史和最新消息的响应。下面让我们看几个由不同聊天函数实现不同功能的聊天机器人示例。
1. 随机回答“是”或“否”的聊天机器人
让我们编写一个随机回答“是”或“否”的聊天函数,将其插入gr.ChatInterface()并调用.launch()方法来创建Web界面,示例如下:
import random
def random_response(message, history):
return random.choice(["Yes", "No"])
import gradio as gr
gr.ChatInterface(
fn=random_response,
type="messages"
).launch()
提示:始终在gr.ChatInterface中设置type=“messages”,而默认值(type=“tuples”)已被弃用,并将在未来的Gradio版本中移除。
以下是运行演示截图,可以自己试试看:
2. 交替同意和反对的聊天机器人
当然,前面的示例非常简单,它没有考虑用户输入或之前的对话历史。以下是另一个简单的示例,展示了如何结合用户输入和历史记录:
import gradio as gr
def alternatingly_agree(message, history):
if len([h for h in history if h['role'] == "assistant"]) % 2 == 0:
return f"Yes, I do think that: {message}"
else:
return "I don't think so"
gr.ChatInterface(
fn=alternatingly_agree,
type="messages"
).launch()
这里不再截图。我们将在下一节中看到更真实的聊天函数示例,它展示了如何使用gr.ChatInterface与流行的LLM结合。
3. 流式聊天机器人
在聊天函数中,我们可以使用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(
fn=slow_echo,
type="messages"
).launch()
运行演示如下:
当响应正在流式传输时,“Submit”按钮会变成一个“Stop”按钮,可用于停止生成器函数。
提示:尽管每次迭代时都会生成最新的消息,但Gradio只会将每条消息的“差异”从服务器发送到前端,从而减少了网络上的延迟和数据消耗。
11.1.2 自定义聊天界面
除了聊天函数,我们最常用的就是自定义聊天界面,比如自定义示例、Chatbot或Textbox,或实现多模态聊天界面。
1. 自定义观感
如果熟悉Gradio的gr.Interface类,可以发现gr.ChatInterface包含许多相同的参数,我们可以使用这些参数来自定义聊天机器人的观感。例如:
- 使用title和description参数在聊天机器人上方添加标题和描述;
- 分别使用theme和css参数添加主题或自定义CSS;
- 添加示例,甚至可以启用cache_examples,使用户更易体验我们的Chatbot;
- 自定义Chatbot(例如更改高度或添加占位符)或文本框(例如设置最大字符数或添加占位符)。
下面以添加示例和自定义chatbot或文本框为例进行说明。我们可以使用examples参数向gr.ChatInterface添加预设示例,该参数接受一个字符串示例列表,所有示例在发送消息之前都以“按钮”形式出现在聊天机器人中。如果我们希望将图像或其他文件作为示例的一部分,可以使用以下字典格式而不是字符串:{"text": "What's in this image?", "files": ["cheetah.jpg"]}
,其中格式为文件的消息将作为单独的消息添加到聊天机器人历史记录中。
我们还可以使用example_labels参数更改每个示例的显示文本,使用example_icons参数为每个示例添加图标。这两个参数都接受一个字符串列表,其长度应与示例列表相同。如果我们希望缓存示例以便预先计算并立即显示结果,请设置cache_examples=True。
如果想自定义组成ChatInterface的gr.Chatbot或gr.Textbox,则可以传入自己的Chatbot或Textbox组件。以下是我们如何应用本节讨论的参数的示例:
import gradio as gr
def yes_man(message, history):
if message.endswith("?"):
return "Yes"
else:
return "Ask me anything!"
gr.ChatInterface(
yes_man,
type="messages",
chatbot=gr.Chatbot(height=300, type="messages", placeholder="<strong>Your Personal Yes-Man</strong><br>Ask Me Anything</br>"),
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="ocean",
examples=["Hello", "Am I cool?", "Are tomatoes vegetables?"],
cache_examples=True,
).launch()
运行演示如下:
请注意:(1)聊天界面添加的gr.Chatbot的“placeholder”,该占位符在用户开始聊天之前显示,接受 Markdown或HTML格式文本,并会在聊天机器人中垂直且水平居中显示;(2)在gr.Chatbot组件中仍需设置type="messages"
,否则会报错:“Data incompatible with the messages format”。
2. 多模态聊天界面
我们可能希望为聊天界面添加多模态功能,比如希望用户能够上传图像或文件到聊天机器人并询问相关问题。这时可以向gr.ChatInterface类传递一个参数(multimodal=True),使聊天机器人具备“多模态”功能。当multimodal=True时,聊天函数的特征会略有变化,如下所述:
- 函数的第一个参数(之前称为message)应接受一个字典,该字典由提交的文本和上传的文件组成,格式如下:
{
"text": "user input",
"files": [
"updated_file_1_path.ext",
"updated_file_2_path.ext",
...
]
}
- 聊天函数的第二个参数history将保持与之前相同的openai-style的字典格式。然而,如果历史记录中包含上传的文件,content键的值将不再是一个字符串,而是一个包含文件路径的单元素元组。每个文件将作为历史记录中的一条单独消息。因此,在上传两个文件并提问后,历史记录可能如下所示:
[
{"role": "user", "content": ("cat1.png")},
{"role": "user", "content": ("cat2.png")},
{"role": "user", "content": "What's the difference between these two images?"},
]
设置multimodal=True时,聊天函数的返回类型不会改变(即在最简单的情况下,仍应返回一个字符串值)。我们将在后续返回复杂响应中讨论更复杂的情况,例如返回文件。
当自定义多模态聊天界面时,textbox参数应该传递gr.MultimodalTextbox的实例。以下是一个示例,展示了如何设置和自定义多模态聊天界面:
import gradio as gr
def count_images(message, history):
num_images = len(message["files"])
total_images = 0
for message in history:
if isinstance(message["content"], tuple):
total_images += 1
return f"You just uploaded {num_images} images, total uploaded: {total_images+num_images}"
demo = gr.ChatInterface(
fn=count_images,
type="messages",
examples=[
{"text": "No files", "files": []}
],
multimodal=True,
textbox=gr.MultimodalTextbox(file_count="multiple", file_types=["image"])
)
demo.launch()
运行截图如下:
从示例中可以看到,通过组件gr.MultimodalTextbox(),我们可以同时上传多个图像、音频、视频类文件并可配有文字说明,其参数说明如下:
- value: 类型为str | dict[str, str | list] | Callable | None,默认为None。显示在MultimodalTextbox中的默认值。可以是一个字符串值,或者一个字典形式的对象,例如:
{“text”: “sample text”, “files”: [{path: “files/file.jpg”, orig_name: “file.jpg”, url: “http://image_url.jpg”, size: 100}]}。如果是一个可调用对象(函数),则每当应用加载时都会调用该函数来设置组件的初始值。 - sources:类型为list[Literal[‘upload’, ‘microphone’]] | Literal[‘upload’, ‘microphone’] | None,默认为None。指定允许的输入来源列表。“upload” 会创建一个按钮,用户可以通过点击上传或拖放文件;“microphone” 会创建一个麦克风输入。如果为None,则默认值为 [“upload”]。
- file_count: 类型为Literal[‘single’, ‘multiple’, ‘directory’],默认为“single”,如果设置为"directory",用户将上传所选目录中的所有文件。在"multiple"或"directory"的情况下,返回类型将是每个文件的列表。
- file_types:类型为list[str] | None,默认为None。"file"允许上传任何文件,"image"仅允许上传图片文件,"audio"仅允许上传音频文件,"video"仅允许上传视频文件,“text” 仅允许上传文本文件。
读者可根据自己需要设置,更多参数请参照官方文档。
11.1.3 附加输入输出
附加输入输出功能为聊天函数添加额外的输入或输出,以便实现更复杂和定制化的功能。
1. additional_inputs:附加输入
我们可能希望在聊天函数中添加额外的输入,并通过聊天界面向用户展示这些输入。例如添加一个用于系统提示的文本框,或者一个设置聊天机器人响应中令牌数量的滑块。gr.ChatInterface类支持 additional_inputs参数,该参数可用于添加额外的输入组件。关于additional inputs,在Interface和Blocks中已讲过,可作为参考,此处稍有不同,请注意区分。
additional_inputs参数接受一个组件或组件列表。我们可以直接传递组件实例,或者使用它们的字符串快捷方式(例如使用 “textbox” 而不是gr.Textbox())。如果传递组件实例,并且它们尚未被渲染,那么这些组件将出现在聊天机器人下方的 gr.Accordion()中。以下是一个完整的示例:
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,
type="messages",
additional_inputs=[
gr.Textbox("You are helpful AI.", label="System Prompt"),
gr.Slider(10, 100),
],
)
demo.launch()
运行截图如下:
如果传递给additional_inputs的组件已经在父级gr.Blocks()中渲染过,那么它们不会在折叠面板中重新渲染,这为设置输入组件的布局提供了灵活性。在下面的示例中,我们将 gr.Textbox()放置在聊天机器人界面的顶部,同时将滑块保留在下方:
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], type="messages"
)
拖动滑条并输入信息,运行截图如下:
我们还可以为额外输入添加示例值。将列表的列表传递给examples参数,其中每个内部列表代表一个样本,每个内部列表的长度应为1 + len(additional_inputs),其第一个元素应为聊天消息的示例值,随后的每个元素应为额外输入的示例值,按顺序排列。当提供额外输入时,示例将以表格形式呈现在聊天界面下方。
如需要创建自定义更高的内容,最好使用低级的gr.Blocks() API构建聊天机器人界面,后续会单独讲述使用Blocks创建聊天机器人界面。
2. additional_outputs:附加输出
与接受额外输入到聊天函数中的方式相同,我们还可以返回额外输出。只需将组件列表传递给gr.ChatInterface中的additional_outputs参数,并从聊天函数中为每个组件返回额外的值。以下是一个提取代码并将其输出到单独的gr.Code组件中的示例:
import gradio as gr
python_code = """
def fib(n):
if n <= 0:
return 0
elif n == 1:
return 1
else:
return fib(n-1) + fib(n-2)
"""
js_code = """
function fib(n) {
if (n <= 0) return 0;
if (n === 1) return 1;
return fib(n - 1) + fib(n - 2);
}
"""
def chat(message, history):
if "python" in message.lower():
return "Type Python or JavaScript to see the code.", gr.Code(language="python", value=python_code)
elif "javascript" in message.lower():
return "Type Python or JavaScript to see the code.", gr.Code(language="javascript", value=js_code)
else:
return "Please ask about Python or JavaScript.", None
with gr.Blocks() as demo:
code = gr.Code(render=False)
with gr.Row():
with gr.Column():
gr.Markdown("<center><h1>Write Python or JavaScript</h1></center>")
gr.ChatInterface(
chat,
examples=["Python", "JavaScript"],
additional_outputs=[code],
type="messages"
)
with gr.Column():
gr.Markdown("<center><h1>Code Artifacts</h1></center>")
code.render()
demo.launch()
运行截图如下:
注意:与额外输入的情况不同,传递给additional_outputs的组件必须在gr.Blocks上下文中预先定义——它们不会自动渲染。如果需要在gr.ChatInterface之后渲染它们,可以在首次定义时设置render=False,然后在gr.Blocks()的适当部分调用.render()进行渲染,如上例所示。
11.1.4 返回复杂响应
我们之前提到过,在最简单的情况下,聊天函数应返回一个字符串响应,该响应将在聊天机器人中呈现为文本。然而也可以返回更复杂的响应,比如返回Gradio组件、提供预设回复或返回多条消息。
1. 返回Gradio组件
目前,以下Gradio组件可以显示在聊天界面中:gr.Image、gr.Plot、gr.Audio、gr.HTML、gr.Video、gr.Gallery和gr.File。只需从函数中返回这些组件之一,即可与gr.ChatInterface一起使用。
以下是一个返回音频文件的示例:
import gradio as gr
def music(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(
music,
type="messages",
textbox=gr.Textbox(placeholder="Which artist's music do you want to listen to?", scale=7),
).launch()
运行截图如下:
类似地,可以使用gr.Image返回图片文件,使用gr.Video返回视频文件,或者使用gr.File组件返回任意文件。
2. 提供预设回复
我们可能希望提供预设回复,供用户在与聊天机器人对话时选择。为此,可以从聊天函数中返回一个完整的OpenAI-style的消息字典,并在返回的字典中添加options键来设置这些回复。字典应包含以下键:
- role:设置为"assistant";
- content:设置为一个字典,其中键为path,值为您要返回的文件路径或URL;
- options键对应的值应该是一个字典列表,每个字典包含一个value(当点击该回复时发送给聊天函数的值,是一个字符串)和一个可选的label(如果提供,则作为预设回复显示的文本,而不是value)。
以下示例展示了如何使用预设回复:
import gradio as gr
import random
example_code = """
Here's an example Python lambda function:
lambda x: x + {}
Is this correct?
"""
def chat(message, history):
if message == "Yes, that's correct.":
return "Great!"
else:
return {
"role": "assistant",
"content": example_code.format(random.randint(1, 100)),
"options": [
{"value": "Yes, that's correct.", "label": "Yes"},
{"value": "No"}
]
}
demo = gr.ChatInterface(
chat,
type="messages",
examples=["Write an example Python lambda function."]
)
demo.launch()
运行截图如下:
点击按钮Yes后,会直接使用预设恢复“Yes, that’s correct.”,然后聊天机器人回复“Great!"结束会话。
3. 返回多条消息
我们还可以通过返回上述任意类型的消息列表(甚至可以混合使用),从而使聊天函数中返回多条助手消息。例如,你可以发送一条带有文件的消息,如下例所示:
import gradio as gr
def echo_multimodal(message, history):
response = []
response.append("You wrote: '" + message["text"] + "' and uploaded:")
if message.get("files"):
for file in message["files"]:
response.append(gr.File(value=file))
return response
demo = gr.ChatInterface(
echo_multimodal,
type="messages",
multimodal=True,
textbox=gr.MultimodalTextbox(file_count="multiple"),
)
demo.launch()
运行截图如下:
注意组件gr.MultimodalTextbox的设置,此时函数返回输入信息和上传的文件。
11.1.5 通过API加载聊天机器人
需要说明的是,除了利用gr.ChatInterface()快速创建Chatbot,还可以从API端点加载聊天机器人,有两种方法实现:gr.load_char()和/chat。
1. load_chat:从OpenAI API端点
通过函数gr.load_chat(),我们可以从Ollama或任何兼容OpenAI API的端点快速加载聊天机器人。假如有一个提供兼容OpenAI API端点的聊天服务器(如果没有,可以跳过这部分),那么可以用一行代码启动一个 ChatInterface。
首先,运行pip install openai
;然后,使用自己的URL、模型和可选的token进行加载,如下所示:
import gradio as gr
gr.load_chat("http://localhost:11434/v1/", model="llama3.2", token=None).launch()
目前还没有关于这部分的详细资料,感兴趣的读者请关注官方后续的gr.load_chat()文档说明。
2. /chat:通过页面API端点
一旦我们构建了Gradio聊天界面并将其托管在Hugging Face Spaces或其他地方,就可以通过简单API的端点:/chat
进行查询。该端点只需要用户的消息(如果使用additional_inputs参数设置了任何额外输入,则可能还需要这些输入),并会返回响应,内部会跟踪已发送的历史消息。API端点截图如下:
要使用该端点,可以通过Gradio Python客户端或Gradio JS客户端,详见之前的Gradio Clients章节。或者,我们也可以将聊天界面部署到其他平台,例如:
- Discord Bot;
- Slack Bot;
- Website widget。
关于这三部分操作将在本章后续讲述,请持续关注进步。
11.1.6 聊天历史和用户反馈
用户聊天过程中,维护聊天历史记录至关重要,同时也可以通过用户反馈与用户互动。
1. 聊天历史记录
我们可以为ChatInterface启用持久化聊天历史记录功能,这允许用户维护多个对话并轻松切换。启用持久化聊天历史记录功能后,对话会使用浏览器的本地存储(local storage)在用户浏览器中本地且私密地保存。因此,如果将ChatInterface部署在Hugging Face Spaces等平台上时,每个用户都将拥有自己独立的聊天历史记录,不会干扰其他用户的对话。这意味着多个用户可以同时与同一个ChatInterface交互,同时保留各自的私密对话历史。
要启用持久化聊天历史记录功能,只需设置gr.ChatInterface(save_history=True)(如下一节示例所示)。用户随后会在侧边栏中看到他们之前的对话,并可以继续之前的聊天或开始新的对话。
2. 收集用户反馈
为了收集关于聊天模型的反馈,可以设置gr.ChatInterface(flagging_mode=“manual”),用户将能够对助手的回复进行点赞或点踩。每个被标记的回复以及整个聊天历史记录都会保存到应用工作目录中的CSV文件中(可以通过flagging_dir参数配置标记目录)。
我们还可以通过flagging_options参数更改反馈选项。默认选项是“喜欢”和“不喜欢”,分别显示为点赞和点踩图标。其他任何选项都会显示在专用的标记图标下。以下示例展示了一个同时启用了聊天历史记录(如前一节所述)和用户反馈功能的ChatInterface:
import time
import gradio as gr
def slow_echo(message, history):
for i in range(len(message)):
time.sleep(0.05)
yield "You typed: " + message[: i + 1]
demo = gr.ChatInterface(
slow_echo,
type="messages",
flagging_mode="manual",
flagging_options=["Like", "Spam", "Inappropriate", "Other"],
save_history=True,
)
demo.launch()
运行截图如下:
参考文献
- Gradio - guides - Additional Features