Gradio 的月活跃用户突破 100 万!

阅读更多
Gradio logo
  1. 聊天机器人
  2. 快速创建聊天机器人

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

简介

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

本教程使用 gr.ChatInterface(),这是一个高级抽象,可以让你快速创建聊天机器人 UI,通常只需几行 Python 代码。它可以轻松地进行调整以支持多模态聊天机器人,或需要进一步自定义的聊天机器人。

先决条件:请确保你正在使用最新版本的 Gradio

$ pip install --upgrade gradio

关于 OpenAI-API 兼容端点的说明

如果你有一个聊天服务器提供 OpenAI-API 兼容的端点(例如 Ollama),你可以用一行 Python 代码启动 ChatInterface。首先,也运行 pip install openai。然后,使用你自己的 URL、模型和可选令牌

import gradio as gr

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

文档中阅读关于 gr.load_chat 的信息。如果你有自己的模型,请继续阅读以了解如何围绕 Python 中的任何聊天模型创建应用程序!

定义聊天函数

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

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

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

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

而下一个 message 将会是

"And what is its largest city?"

你的聊天函数只需要返回

  • 一个 str 值,即聊天机器人基于聊天 history 和最新 message 的响应,例如,在本例中
Paris is also the largest city.

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

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

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

这是我们的聊天函数

import random

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

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

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 与流行的 LLM 一起使用的示例

流式聊天机器人

在你的聊天函数中,你可以使用 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 也只会将每条消息的“差异”从服务器发送到前端,这减少了延迟和网络上的数据消耗。

自定义聊天 UI

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

  • 使用 titledescription 参数在您的聊天机器人上方添加标题和描述。
  • 分别使用 themecss 参数添加主题或自定义 CSS。
  • 添加 examples 甚至启用 cache_examples,这将使您的聊天机器人更容易让用户试用。
  • 自定义聊天机器人(例如,更改高度或添加占位符)或文本框(例如,添加最大字符数或添加占位符)。

添加示例

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

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

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

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

如果您想自定义构成 ChatInterfacegr.Chatbotgr.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),
    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"),
...

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

多模态聊天界面

您可能希望为您的聊天界面添加多模态功能。例如,您可能希望用户能够将图像或文件上传到您的聊天机器人并询问有关它们的问题。您可以通过将单个参数 (multimodal=True) 传递给 gr.ChatInterface 类,使您的聊天机器人成为“多模态”。

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() 放置在 Chatbot 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], 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 响应,该响应将在聊天机器人中呈现为 Markdown。但是,您也可以返回更复杂的响应,我们将在下面讨论

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

返回多条消息

您可以通过简单地返回消息的 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()

显示中间思考过程或工具使用情况

gr.ChatInterface 类支持直接在聊天机器人中显示中间思考过程或工具使用情况。

为此,您需要从您的聊天函数返回一个 gr.ChatMessage 对象。以下是 gr.ChatMessage 数据类以及两个内部类型化字典的架构

@dataclass
class ChatMessage:
   content: str | Component
   metadata: MetadataDict = None
   options: list[OptionDict] = None

class MetadataDict(TypedDict):
   title: NotRequired[str]
   id: NotRequired[int | str]
   parent_id: NotRequired[int | str]
   log: NotRequired[str]
   duration: NotRequired[float]
   status: NotRequired[Literal["pending", "done"]]

class OptionDict(TypedDict):
   label: NotRequired[str]
   value: str

如您所见,gr.ChatMessage 数据类类似于 openai 风格的消息格式,例如,它有一个“content”键,指的是聊天消息内容。但它也包含一个“metadata”键,其值是一个字典。如果此字典包含“title”键,则生成的消息将显示为中间思考过程,标题显示在思考过程的顶部。这是一个显示用法的示例

import gradio as gr
from gradio import ChatMessage
import time

sleep_time = 0.5

def simulate_thinking_chat(message, history):
    start_time = time.time()
    response = ChatMessage(
        content="",
        metadata={"title": "_Thinking_ step-by-step", "id": 0, "status": "pending"}
    )
    yield response

    thoughts = [
        "First, I need to understand the core aspects of the query...",
        "Now, considering the broader context and implications...",
        "Analyzing potential approaches to formulate a comprehensive answer...",
        "Finally, structuring the response for clarity and completeness..."
    ]

    accumulated_thoughts = ""
    for thought in thoughts:
        time.sleep(sleep_time)
        accumulated_thoughts += f"- {thought}\n\n"
        response.content = accumulated_thoughts.strip()
        yield response

    response.metadata["status"] = "done"
    response.metadata["duration"] = time.time() - start_time
    yield response

    response = [
        response,
        ChatMessage(
            content="Based on my thoughts and analysis above, my response is: This dummy repro shows how thoughts of a thinking LLM can be progressively shown before providing its final answer."
        )
    ]
    yield response


demo = gr.ChatInterface(
    simulate_thinking_chat,
    title="Thinking LLM Chat Interface 🤔",
    type="messages",
)

demo.launch()

您甚至可以显示嵌套的思考过程,这对于一个工具可能调用其他工具的代理演示非常有用。要显示嵌套的思考过程,请在“metadata”字典中包含“id”和“parent_id”键。阅读我们的关于显示中间思考过程和工具使用情况的专门指南,以获取更真实的示例。

提供预设响应

当返回助手消息时,您可能希望提供用户可以选择的预设选项以进行响应。为此,您将再次从您的聊天函数返回一个 gr.ChatMessage 实例。这一次,请确保设置 options 键,指定预设响应。

如上面的 gr.ChatMessage 架构所示,与 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 gr.ChatMessage(
            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()

直接修改聊天机器人值

您可能希望使用您自己的事件(而不是 gr.ChatInterface 中预构建的事件)来修改聊天机器人的值。例如,您可以创建一个下拉列表,用某些对话预填充聊天历史记录,或者添加一个单独的按钮来清除对话历史记录。 gr.ChatInterface 支持这些事件,但您需要使用 gr.ChatInterface.chatbot_value 作为此类事件中的输入或输出组件。在此示例中,我们使用 gr.Radio 组件来使用某些对话预填充聊天机器人

import gradio as gr
import random

def prefill_chatbot(choice):
    if choice == "Greeting":
        return [
            {"role": "user", "content": "Hi there!"},
            {"role": "assistant", "content": "Hello! How can I assist you today?"}
        ]
    elif choice == "Complaint":
        return [
            {"role": "user", "content": "I'm not happy with the service."},
            {"role": "assistant", "content": "I'm sorry to hear that. Can you please tell me more about the issue?"}
        ]
    else:
        return []

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

with gr.Blocks() as demo:
    radio = gr.Radio(["Greeting", "Complaint", "Blank"])
    chat = gr.ChatInterface(random_response, type="messages")
    radio.change(prefill_chatbot, radio, chat.chatbot_value)

demo.launch()

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

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

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

聊天记录

您可以为您的 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()

请注意,在此示例中,我们设置了多个标记选项:“赞”、“垃圾邮件”、“不当内容”、“其他”。由于区分大小写的字符串“赞”是标记选项之一,因此用户将在每条助手消息旁边看到一个点赞图标。其他三个标记选项将显示在标志图标下的下拉列表中。

下一步是什么?

既然您已经了解了 gr.ChatInterface 类以及如何使用它来快速创建聊天机器人 UI,我们建议您阅读以下内容之一