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

如何用 Gradio 创建聊天机器人

介绍

聊天机器人是大型语言模型 (LLM) 的一个热门应用。使用 Gradio,您可以轻松构建一个聊天应用程序,并与您的用户分享,或者通过直观的用户界面自己尝试。

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

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

$ pip install --upgrade gradio

关于 OpenAI API 兼容端点的说明

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

import gradio as gr

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

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

定义聊天函数

要使用 gr.ChatInterface() 创建一个聊天应用程序,您首先需要定义您的聊天函数。在最简单的情况下,您的聊天函数应该接受两个参数:messagehistory(参数名称可以随意,但必须按此顺序)。

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

history 会像这样

[
    {"role": "user", "content": [{"type": "text", "text": "What is the capital of France?"}]},
    {"role": "assistant", "content": [{"type": "text", "text": "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, 
).launch()

就是这样!这是我们正在运行的演示,快来试试吧

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

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

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, 
).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, 
).launch()

在响应流式传输时,“提交”按钮会变成一个“停止”按钮,可以用来停止生成器函数。

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

自定义聊天 UI

如果您熟悉 Gradio 的 gr.Interface 类,gr.ChatInterface 包含了许多相同的参数,您可以使用它们来定制聊天机器人的外观和感觉。例如,您可以

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

添加示例

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

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

如果您想缓存示例,以便它们预先计算好并且结果立即出现,请将 cache_examples=True 设置为 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,
    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",
    examples=["Hello", "Am I cool?", "Are tomatoes vegetables?"],
    cache_examples=True,
).launch(theme="ocean")

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

gr.ChatInterface(
    yes_man,
    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 键将是一个字典,其中包含一个名为“type”的键,其值为“file”,文件将表示为一个字典。所有文件都将被分组在历史消息中。因此,上传两个文件并提出问题后,您的历史记录可能看起来像这样

[
    {"role": "user", "content": [{"type": "file", "file": {"path": "cat1.png"}},
                                 {"type": "file", "file": {"path": "cat1.png"}},
                                 {"type": "text", "text": "What's the difference between these two images?"}]}
]

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

如果您正在自定义多模态聊天界面,您应该将 gr.MultimodalTextbox 的实例传递给 textbox 参数。您可以通过传递 sources 参数进一步自定义 MultimodalTextboxsources 是要启用的源列表。下面是一个示例,说明如何设置和自定义多模态聊天界面

import gradio as gr

def count_images(message, history):
    num_images = len(message["files"])
    total_images = 0
    for message in history:
        for content in message["content"]:
            if content["type"] == "file":
                total_images += 1
    return f"You just uploaded {num_images} images, total uploaded: {total_images+num_images}"

demo = gr.ChatInterface(
    fn=count_images, 
    examples=[
        {"text": "No files", "files": []}
    ], 
    multimodal=True,
    textbox=gr.MultimodalTextbox(file_count="multiple", file_types=["image"], sources=["upload", "microphone"])
)

demo.launch()

附加输入

您可能希望将额外的输入添加到您的聊天函数中,并通过聊天 UI 将它们暴露给用户。例如,您可以添加一个用于系统提示的文本框,或者一个用于设置聊天机器人响应 token 数量的滑块。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,
    additional_inputs=[
        gr.Textbox("You are helpful AI.", label="System Prompt"),
        gr.Slider(10, 100),
    ],
)

demo.launch()

如果您传递到 additional_inputs 的组件已经在父级 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()

附加输入的示例

您还可以为您的附加输入添加示例值。将列表的列表传递给 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],
                api_name="chat",
            )
        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/test/test_files/audio_sample.wav")
    else:
        return "Please provide the name of an artist"

gr.ChatInterface(
    music,
    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,
    multimodal=True,
    textbox=gr.MultimodalTextbox(file_count="multiple"),
    api_name="chat",
)

demo.launch()

显示中间思考或工具使用

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

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

MessageContent = Union[str, FileDataDict, FileData, Component]

@dataclass
class ChatMessage:
   content: MessageContent | list[MessageContent]
   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 🤔",
)

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,
    examples=["Write an example Python lambda function."],
    api_name="chat",
)

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, api_name="chat")
    radio.change(prefill_chatbot, radio, chat.chatbot_value)

demo.launch()

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

一旦您构建了 Gradio 聊天界面并在 Hugging Face Spaces 或其他地方托管了它,您就可以通过一个简单的 API 来查询它。API 路由将是您传递给 ChatInterface 的函数的名称。因此,如果 gr.ChatInterface(respond),则 API 路由是 /respond。该端点仅接收用户的消息并返回响应,在内部跟踪消息历史记录。

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

聊天历史记录

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

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

收集用户反馈

为了收集关于您的聊天模型的反馈,请设置 gr.ChatInterface(flagging_mode="manual"),用户将能够点赞或点踩助手响应。每个被标记的响应以及整个聊天历史记录将被保存在应用程序工作目录的 CSV 文件中(这可以通过 flagging_dir 参数进行配置)。

您还可以通过 flagging_options 参数更改反馈选项。默认选项是“喜欢”和“不喜欢”,它们显示为点赞和点踩图标。任何其他选项都显示在一个专用的旗帜图标下。本例展示了一个同时启用了聊天历史记录(在前一节中提到)和用户反馈的聊天界面

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,
    flagging_mode="manual",
    flagging_options=["Like", "Spam", "Inappropriate", "Other"],
    save_history=True,
)

demo.launch()

请注意,在此示例中,我们设置了几个标记选项:“Like”、“Spam”、“Inappropriate”、“Other”。由于区分大小写的字符串“Like”是标记选项之一,用户将在每个助手消息旁边看到一个点赞图标。其他三个标记选项将显示在旗帜图标下的下拉菜单中。

下一步?

现在您已经了解了 gr.ChatInterface 类及其如何用于快速创建聊天机器人 UI,我们建议阅读以下内容之一

gradio