Gradio 代理与 MCP 黑客马拉松
获奖者Gradio 代理与 MCP 黑客马拉松
获奖者这是构建自定义多模态聊天机器人的两部分系列文章中的第一篇。在第一部分中,我们将修改 Gradio 聊天机器人组件,使其能在同一消息中显示文本和媒体文件(视频、音频、图像)。在第二部分中,我们将构建一个自定义文本框组件,使其能够向聊天机器人发送多模态消息(文本和媒体文件)。
您可以观看以下 YouTube 视频,跟随本文作者一起实现聊天机器人组件!
以下是我们多模态聊天机器人组件的预览效果
在本演示中,我们将调整现有的 Gradio `Chatbot` 组件,使其能在同一消息中显示文本和媒体文件。让我们基于 `Chatbot` 组件的源代码创建新的自定义组件目录。
gradio cc create MultimodalChatbot --template Chatbot
我们准备好了!
提示: 请务必修改 `pyproject.toml` 文件中的 `Author` 键。
在您喜欢的代码编辑器中打开 `multimodalchatbot.py` 文件,让我们开始修改组件的后端。
我们要做的第一件事是创建组件的 `data_model`。`data_model` 是您的 Python 组件将接收并发送给运行 UI 的 JavaScript 客户端的数据格式。您可以在[后端指南](./backend)中阅读有关 `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` 实现的。请在此处阅读文档[https://docs.pydantic.org.cn/latest/](https://docs.pydantic.org.cn/latest/)。
我们已经完成了最难的部分!
对于 `preprocess` 方法,我们将保持其简单性,并向将此组件用作输入的 Python 函数传递 `MultimodalMessage` 列表。这将允许我们组件的用户通过 `.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` 文件一样,首先修改类型注解。在 `