1. 聊天机器人
  2. 快速创建聊天机器人

如何使用Gradio创建聊天机器人

介绍

聊天机器人是大语言模型(LLMs)的一个流行应用。使用Gradio,你可以轻松构建你的LLM演示,并与用户分享,或者使用直观的聊天机器人界面自己尝试。

本教程使用gr.ChatInterface(),这是一个高级抽象,允许您快速创建聊天机器人用户界面,通常只需一行Python代码。它可以轻松适应支持多模态聊天机器人,或需要进一步定制的聊天机器人。

先决条件:请确保您使用的是最新版本的Gradio:

$ pip install --upgrade gradio

快速从Ollama或任何OpenAI-API兼容的端点加载

如果你有一个提供与OpenAI API兼容的端点的聊天服务器(如果没有,请跳过),你可以在一行代码中启动一个ChatInterface。首先,还需要运行pip install openai。然后,使用你自己的URL、模型和可选的令牌:

import gradio as gr

gr.load_chat("http://localhost:11434/v1/", model="llama3.2", token=None).launch()

定义一个聊天函数

在使用gr.ChatInterface()时,你应该做的第一件事是定义你的聊天函数。在最简单的情况下,你的聊天函数应该接受两个参数:messagehistory(参数可以任意命名,但必须按此顺序)。

  • message: 一个 str,表示用户最近的消息。
  • history: 一个包含rolecontent键的openai风格字典列表,表示之前的对话历史。可能还包括表示消息元数据的其他键。

例如,history 可能看起来像这样:

[
    {"role": "user", "content": "What is the capital of France?"},
    {"role": "assistant", "content": "Paris"}
]

您的聊天功能只需返回:

  • 一个 str 值,这是聊天机器人基于聊天 history 和最近的 message 的响应。

让我们来看几个示例聊天函数:

示例:一个随机回答是或否的聊天机器人

让我们编写一个随机响应YesNo的聊天函数。

这是我们的聊天功能:

import random

def random_response(message, history):
    return random.choice(["Yes", "No"])

现在,我们可以将其插入gr.ChatInterface()并调用.launch()方法来创建网页界面:

import gradio as gr

gr.ChatInterface(
    fn=random_response, 
    type="messages"
).launch()

提示: 始终在 gr.ChatInterface 中设置 type="messages"。默认值 (type="tuples") 已被弃用,并将在未来的 Gradio 版本中移除。

就是这样!这是我们的运行演示,试试看:

示例:一个在同意和不同意之间交替的聊天机器人

当然,前面的例子非常简单,它没有考虑用户输入或之前的历史记录!这是另一个简单的例子,展示了如何结合用户的输入以及历史记录。

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与流行LLMs的示例

流式聊天机器人

在你的聊天函数中,你可以使用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()

当响应正在流式传输时,“提交”按钮会变成“停止”按钮,可用于停止生成器函数。

提示: 尽管你在每次迭代时都会生成最新的消息,但Gradio只会将每条消息的“差异”从服务器发送到前端,这减少了网络上的延迟和数据消耗。

自定义聊天界面

如果你熟悉Gradio的gr.Interface类,gr.ChatInterface包含许多相同的参数,你可以使用这些参数来自定义你的聊天机器人的外观和感觉。例如,你可以:

  • 使用titledescription参数在您的聊天机器人上方添加标题和描述。
  • 使用themecss参数分别添加主题或自定义CSS。
  • 添加 examples 并启用 cache_examples,这使您的 Chatbot 更容易让用户试用。
  • Customize the chatbot (e.g., change the height or add a placeholder) or text box (e.g., add a maximum character count or add a placeholder).

添加示例

你可以使用examples参数向你的gr.ChatInterface添加预设示例,该参数接受一个字符串示例列表。任何示例都会在发送任何消息之前以“按钮”的形式出现在聊天机器人中。如果你想在示例中包含图片或其他文件,你可以使用这种字典格式而不是字符串来表示每个示例:{"text": "这张图片里有什么?", "files": ["cheetah.jpg"]}。每个文件将作为一条单独的消息添加到你的聊天机器人历史中。

您可以使用example_labels参数更改每个示例的显示文本。您还可以使用example_icons参数为每个示例添加图标。这两个参数都接受一个字符串列表,该列表的长度应与examples列表相同。

如果您希望缓存示例以便它们预先计算并且结果立即显示,请设置cache_examples=True

自定义聊天机器人或文本框组件

如果你想自定义组成ChatInterfacegr.Chatbotgr.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),
    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()

这是另一个示例,它为您的聊天界面添加了一个“占位符”,该占位符在用户开始聊天之前出现。gr.Chatbotplaceholder参数接受Markdown或HTML:

gr.ChatInterface(
    yes_man,
    type="messages",
    chatbot=gr.Chatbot(placeholder="<strong>Your Personal Yes-Man</strong><br>Ask Me Anything"),
...

占位符在聊天机器人中垂直和水平居中显示。

多模态聊天界面

您可能希望为您的聊天界面添加多模态功能。例如,您可能希望用户能够上传图像或文件到您的聊天机器人,并向其提问。您可以通过向gr.ChatInterface类传递一个参数(multimodal=True)来使您的聊天机器人具备“多模态”功能。

multimodal=True时,您的聊天函数的签名会略有变化:函数的第一个参数(我们上面提到的message)应该接受一个由提交的文本和上传的文件组成的字典,如下所示:

{
    "text": "user input", 
    "files": [
        "updated_file_1_path.ext",
        "updated_file_2_path.ext", 
        ...
    ]
}

你的聊天函数的第二个参数,history,将采用与之前相同的openai风格的字典格式。然而,如果历史记录包含上传的文件,文件的content键将不是一个字符串,而是一个由文件路径组成的单元素元组。每个文件将是历史记录中的一条独立消息。因此,在上传两个文件并提问后,你的历史记录可能如下所示:

[
    {"role": "user", "content": ("cat1.png")},
    {"role": "user", "content": ("cat2.png")},
    {"role": "user", "content": "What's the difference between these two images?"},
]

当设置multimodal=True时,您的聊天函数的返回类型不会改变(即在最简单的情况下,您仍然应该返回一个字符串值)。我们讨论了更复杂的情况,例如返回文件如下

如果您正在定制一个多模态聊天界面,您应该将一个gr.MultimodalTextbox的实例传递给textbox参数。您可以通过传递sources参数进一步定制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"], sources=["upload", "microphone"])
)

demo.launch()

额外输入

您可能希望向聊天函数添加额外的输入,并通过聊天UI向用户展示。例如,您可以添加一个用于系统提示的文本框,或者一个设置聊天机器人响应中令牌数量的滑块。gr.ChatInterface类支持一个additional_inputs参数,该参数可用于添加额外的输入组件。

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()放置在聊天机器人界面的顶部,同时将滑块保持在下方。

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], type="messages"
    )

demo.launch()

带有额外输入的示例

您还可以为您的额外输入添加示例值。将列表的列表传递给examples参数,其中每个内部列表代表一个样本,每个内部列表的长度应为1 + len(additional_inputs)。内部列表中的第一个元素应为聊天消息的示例值,随后的每个元素应按顺序为其中一个额外输入的示例值。当提供额外输入时,示例将在聊天界面下方的表格中呈现。

如果您需要创建更定制化的内容,那么最好使用低级的gr.Blocks() API来构建聊天机器人UI。我们在这里有专门的指南

额外输出

就像你可以接受聊天函数中的额外输入一样,你也可以返回额外的输出。只需将组件列表传递给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(),就像我们在上面的示例中所做的那样。

返回复杂响应

我们之前提到过,在最简单的情况下,您的聊天函数应该返回一个str响应,这将在聊天机器人中呈现为文本。然而,您也可以返回更复杂的响应,如下所述:

返回文件或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组件返回任意文件。

提供预设响应

您可能希望提供预设的响应,用户在与您的聊天机器人对话时可以选择这些响应。为此,从您的聊天函数中返回一个完整的openai风格的消息字典,并在从您的聊天函数返回的字典中添加options键来设置这些响应。

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()

返回多条消息

你可以通过返回上述任何类型的消息的list来从你的聊天函数中返回多个助手消息(你甚至可以混合搭配)。这让你可以,例如,发送一条消息以及文件,如下例所示:

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()

通过API使用您的聊天机器人

一旦你构建了你的Gradio聊天界面并将其托管在Hugging Face Spaces或其他地方,你就可以通过/chat端点的简单API进行查询。该端点只需要用户的消息,并会返回响应,内部会跟踪消息历史。

要使用该端点,您应该使用Gradio Python ClientGradio JS client。或者,您可以将您的聊天界面部署到其他平台,例如:

聊天记录

您可以为您的ChatInterface启用持久聊天历史记录,允许用户维护多个对话并轻松在它们之间切换。启用后,对话将使用本地存储在用户的浏览器中本地且私密地保存。因此,如果您在Hugging Face Spaces上部署ChatInterface,每个用户都将拥有自己独立的聊天历史记录,不会干扰其他用户的对话。这意味着多个用户可以同时与同一个ChatInterface交互,同时保持各自的私人对话历史记录。

要启用此功能,只需设置 gr.ChatInterface(save_history=True)(如下一节中的示例所示)。用户随后将在侧面板中看到他们之前的对话,并可以继续任何之前的聊天或开始新的聊天。

收集用户反馈

为了收集关于您的聊天模型的反馈,设置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()

请注意,在这个例子中,我们设置了几个标记选项:"Like"(喜欢)、"Spam"(垃圾)、"Inappropriate"(不适当)、"Other"(其他)。因为区分大小写的字符串"Like"是标记选项之一,用户将在每个助手消息旁边看到一个竖起大拇指的图标。其他三个标记选项将出现在标记图标下的下拉菜单中。

接下来是什么?

现在你已经了解了gr.ChatInterface类以及如何使用它快速创建聊天机器人用户界面,我们建议阅读以下内容之一:

  • Our next Guide 展示了如何使用 gr.ChatInterface 与流行的LLM库的示例。
  • 如果您想从头开始构建非常定制的聊天应用程序,您可以使用低级别的Blocks API来构建它们,如本指南中讨论的
  • 一旦你部署了你的Gradio聊天界面,由于内置的API,在其他应用程序中使用它变得非常容易。这里有一个关于如何将Gradio聊天界面部署为Discord机器人的教程。