Gradio 月活用户突破 100 万!
阅读更多Gradio 月活用户突破 100 万!
阅读更多这是构建自定义多模态聊天机器人组件的两部分系列文章的第一部分。在第一部分中,我们将修改 Gradio Chatbot 组件以在同一消息中显示文本和媒体文件(视频、音频、图像)。在第二部分中,我们将构建一个自定义 Textbox 组件,该组件将能够向聊天机器人发送多模态消息(文本和媒体文件)。
您可以观看此帖子的作者在以下 YouTube 视频中实现聊天机器人组件的过程!
这是我们的多模态聊天机器人组件外观的预览
对于此演示,我们将调整现有的 Gradio Chatbot
组件,以在同一消息中显示文本和媒体文件。让我们通过模板化 Chatbot
组件源代码来创建一个新的自定义组件目录。
gradio cc create MultimodalChatbot --template Chatbot
我们准备开始了!
提示: 确保修改 `pyproject.toml` 文件中的 `Author` 键。
在您最喜欢的代码编辑器中打开 multimodalchatbot.py
文件,让我们开始修改组件的后端。
我们要做的第一件事是创建组件的 data_model
。 data_model
是您的 python 组件将接收并发送到运行 UI 的 javascript 客户端的数据格式。您可以在后端指南中阅读更多关于 data_model
的信息。
对于我们的组件,每个聊天机器人消息将包含两个键:一个 text
键,用于显示文本消息,以及一个可选的媒体文件列表,可以显示在文本下方。
从 gradio.data_classes
导入 FileData
和 GradioModel
类,并将现有的 ChatbotData
类修改为如下所示
class FileMessage(GradioModel):
file: FileData
alt_text: Optional[str] = None
class MultimodalMessage(GradioModel):
text: Optional[str] = None
files: Optional[List[FileMessage]] = None
class ChatbotData(GradioRootModel):
root: List[Tuple[Optional[MultimodalMessage], Optional[MultimodalMessage]]]
class MultimodalChatbot(Component):
...
data_model = ChatbotData
提示: data_model
是使用 `Pydantic V2` 实现的。请阅读此处的文档。
我们已经完成了最难的部分!
对于 preprocess
方法,我们将保持简单,并将 MultimodalMessage
列表传递给使用此组件作为输入的 python 函数。这将让我们的组件用户可以使用 .text
和 .files
属性访问聊天机器人数据。这是一个您可以在实现中修改的设计选择!我们可以像这样返回带有 ChatbotData
的 root
属性的消息列表
def preprocess(
self,
payload: ChatbotData | None,
) -> List[MultimodalMessage] | None:
if payload is None:
return payload
return payload.root
提示: 在[关键概念指南](./key-component-concepts)中了解 preprocess
和 postprocess
方法背后的原理
在 postprocess
方法中,我们将强制把 Python 函数返回的每个消息转换为 MultimodalMessage
类。我们还将清理 text
字段中的任何缩进,以便它可以在前端正确显示为 Markdown 格式。
我们可以保持 postprocess
方法不变,并修改 _postprocess_chat_messages
方法
def _postprocess_chat_messages(
self, chat_message: MultimodalMessage | dict | None
) -> MultimodalMessage | None:
if chat_message is None:
return None
if isinstance(chat_message, dict):
chat_message = MultimodalMessage(**chat_message)
chat_message.text = inspect.cleandoc(chat_message.text or "")
for file_ in chat_message.files:
file_.file.mime_type = client_utils.get_mimetype(file_.file.path)
return chat_message
在完成后端代码之前,让我们修改 example_value
和 example_payload
方法,使其返回 ChatbotData
的有效字典表示形式
def example_value(self) -> Any:
return [[{"text": "Hello!", "files": []}, None]]
def example_payload(self) -> Any:
return [[{"text": "Hello!", "files": []}, None]]
恭喜 - 后端已完成!
Chatbot
组件的前端分为两个部分 - Index.svelte
文件和 shared/Chatbot.svelte
文件。 Index.svelte
文件对从服务器接收的数据应用一些处理,然后将对话的渲染委托给 shared/Chatbot.svelte
文件。首先,我们将修改 Index.svelte
文件,以将处理应用于后端将返回的新数据类型。
让我们首先将我们的自定义类型从 Python 的 data_model
移植到 TypeScript。打开 frontend/shared/utils.ts
并在文件顶部添加以下类型定义
export type FileMessage = {
file: FileData;
alt_text?: string;
};
export type MultimodalMessage = {
text: string;
files?: FileMessage[];
}
现在让我们在 Index.svelte
中导入它们,并修改 value
和 _value
的类型注解。
import type { FileMessage, MultimodalMessage } from "./shared/utils";
export let value: [
MultimodalMessage | null,
MultimodalMessage | null
][] = [];
let _value: [
MultimodalMessage | null,
MultimodalMessage | null
][];
我们需要规范化每个消息,以确保每个文件都有一个正确的 URL 来从中获取其内容。我们还需要格式化 text
键中任何嵌入的文件链接。让我们添加一个 process_message
实用函数,并在 value
更改时应用它。
function process_message(msg: MultimodalMessage | null): MultimodalMessage | null {
if (msg === null) {
return msg;
}
msg.text = redirect_src_url(msg.text);
msg.files = msg.files.map(normalize_messages);
return msg;
}
$: _value = value
? value.map(([user_msg, bot_msg]) => [
process_message(user_msg),
process_message(bot_msg)
])
: [];
让我们像 Index.svelte
文件一样开始,首先修改类型注解。在 <script>
部分的顶部导入 Mulimodal
消息,并使用它来为 value
和 old_value
变量键入类型。
import type { MultimodalMessage } from "./utils";
export let value:
| [
MultimodalMessage | null,
MultimodalMessage | null
][]
| null;
let old_value:
| [
MultimodalMessage | null,
MultimodalMessage | null
][]
| null = null;
我们还需要修改 handle_select
和 handle_like
函数
function handle_select(
i: number,
j: number,
message: MultimodalMessage | null
): void {
dispatch("select", {
index: [i, j],
value: message
});
}
function handle_like(
i: number,
j: number,
message: MultimodalMessage | null,
liked: boolean
): void {
dispatch("like", {
index: [i, j],
value: message,
liked: liked
});
}
现在到了有趣的部分,实际渲染同一消息中的文本和文件!
您应该看到如下代码,它根据消息的类型确定应显示文件还是 Markdown 消息
{#if typeof message === "string"}
<Markdown
{message}
{latex_delimiters}
{sanitize_html}
{render_markdown}
{line_breaks}
on:load={scroll}
/>
{:else if message !== null && message.file?.mime_type?.includes("audio")}
<audio
data-testid="chatbot-audio"
controls
preload="metadata"
...
我们将修改此代码,使其始终显示文本消息,然后循环遍历文件并显示所有存在的文件
<Markdown
message={message.text}
{latex_delimiters}
{sanitize_html}
{render_markdown}
{line_breaks}
on:load={scroll}
/>
{#each message.files as file, k}
{#if file !== null && file.file.mime_type?.includes("audio")}
<audio
data-testid="chatbot-audio"
controls
preload="metadata"
src={file.file?.url}
title={file.alt_text}
on:play
on:pause
on:ended
/>
{:else if message !== null && file.file?.mime_type?.includes("video")}
<video
data-testid="chatbot-video"
controls
src={file.file?.url}
title={file.alt_text}
preload="auto"
on:play
on:pause
on:ended
>
<track kind="captions" />
</video>
{:else if message !== null && file.file?.mime_type?.includes("image")}
<img
data-testid="chatbot-image"
src={file.file?.url}
alt={file.alt_text}
/>
{:else if message !== null && file.file?.url !== null}
<a
data-testid="chatbot-file"
href={file.file?.url}
target="_blank"
download={window.__is_colab__
? null
: file.file?.orig_name || file.file?.path}
>
{file.file?.orig_name || file.file?.path}
</a>
{:else if pending_message && j === 1}
<Pending {layout} />
{/if}
{/each}
我们成功了! 🎉
对于本教程,让我们保持演示简单,仅显示假设用户和机器人之间的静态对话。此演示将展示用户和机器人如何发送文件。在本教程系列的第 2 部分中,我们将构建一个功能齐全的聊天机器人演示!
演示代码将如下所示
import gradio as gr
from gradio_multimodalchatbot import MultimodalChatbot
from gradio.data_classes import FileData
user_msg1 = {"text": "Hello, what is in this image?",
"files": [{"file": FileData(path="https://gradio-builds.s3.amazonaws.com/diffusion_image/cute_dog.jpg")}]
}
bot_msg1 = {"text": "It is a very cute dog",
"files": []}
user_msg2 = {"text": "Describe this audio clip please.",
"files": [{"file": FileData(path="cantina.wav")}]}
bot_msg2 = {"text": "It is the cantina song from Star Wars",
"files": []}
user_msg3 = {"text": "Give me a video clip please.",
"files": []}
bot_msg3 = {"text": "Here is a video clip of the world",
"files": [{"file": FileData(path="world.mp4")},
{"file": FileData(path="cantina.wav")}]}
conversation = [[user_msg1, bot_msg1], [user_msg2, bot_msg2], [user_msg3, bot_msg3]]
with gr.Blocks() as demo:
MultimodalChatbot(value=conversation, height=800)
demo.launch()
提示: 更改文件路径,使其与您机器上的文件对应。此外,如果您在开发模式下运行,请确保文件位于自定义组件目录的顶层。
让我们使用 gradio cc build
和 gradio cc deploy
构建和部署我们的演示!
您可以查看我们部署到 HuggingFace Spaces 的组件,所有源代码都可以在这里找到。
本系列的下一期再见!