这是我们构建自定义多模态聊天机器人组件两部分系列的第一部分。在第一部分中,我们将修改 Gradio 聊天机器人组件,使其能够在同一消息中显示文本和媒体文件(视频、音频、图片)。在第二部分中,我们将构建一个自定义的文本框组件,该组件能够向聊天机器人发送多模态消息(文本和媒体文件)。
您可以跟随这篇文章的作者,通过以下 YouTube 视频实现聊天机器人组件!
这里是我们的多模态聊天机器人组件将会是什么样子的预览:
第 1 部分 - 创建我们的项目
对于这个演示,我们将调整现有的 Gradio Chatbot
组件,以便在同一消息中显示文本和媒体文件。让我们通过模板化 Chatbot
组件源代码来创建一个新的自定义组件目录。
gradio cc create MultimodalChatbot --template Chatbot
我们准备好出发了!
✍️ 提示:确保在 pyproject.toml
文件中修改 Author
键。
第 2a 部分 - 后端数据模型
在您最喜欢的代码编辑器中打开 multimodalchatbot.py
文件,让我们开始修改组件的后端。
我们将要做的第一件事是创建我们组件的 data_model
。 data_model
是您的 Python 组件将接收并发送给运行 UI 的 JavaScript 客户端的数据格式。您可以在后端指南中了解更多关于 data_model
的信息。
对于我们的组件,每个聊天机器人消息将包含两个键:一个 text
键用于显示文本消息,以及一个可选的媒体文件列表,可以显示在文本下方。
从 gradio.data_classes
导入 FileData
和 GradioModel
类,并修改现有的 ChatbotData
类,使其看起来如下:
# 定义文件消息类,继承自 GradioModel
class FileMessage(GradioModel):
# 文件数据,类型为 FileData
file: FileData
# 可选的替代文本,类型为可选的字符串
alt_text: Optional[str] = None
# 定义多模态消息类,继承自 GradioModel
class MultimodalMessage(GradioModel):
# 文本消息,类型为可选的字符串
text: Optional[str] = None
# 文件消息,类型为 FileMessage 类的列表,是可选的
files: Optional[List[FileMessage]] = None
# 定义聊天机器人数据类,继承自 GradioRootModel
class ChatbotData(GradioRootModel):
# 聊天数据,类型为包含多模态消息类的元组的列表
root: List[Tuple[Optional[MultimodalMessage], Optional[MultimodalMessage]]]
# 定义多模态聊天机器人组件,继承自 Component
class MultimodalChatbot(Component):
...
# 多模态聊天机器人组件的数据模型为聊天机器人数据类
data_model = ChatbotData
✍️ 提示:`data_model`是使用`Pydantic V2`实现的。请阅读此处的文档。
我们已经完成了最困难的部分!
第 2b 部分 - 预处理和后处理方法
对于 preprocess
方法,我们将保持简单,将一个 MultimodalMessage
列表传递给使用此组件作为输入的 python 函数。这将允许我们组件的用户使用 .text
和 .files
属性访问聊天机器人数据。这是您可以在实现中修改的设计选择!我们可以像这样返回带有 root
属性的 ChatbotData
的消息列表:
# 定义预处理方法,输入参数为聊天数据,类型为 ChatbotData 或 None
def preprocess(
self,
payload: ChatbotData | None,
) -> List[MultimodalMessage] | None:
# 如果输入参数为空,则直接返回
if payload is None:
return payload
# 如果存在聊天数据,则返回其中的 root 属性,即所有的聊天消息
return payload.root
✍️ 提示:了解`preprocess`和`postprocess`方法背后的原理,请参阅关键概念指南
在 postprocess
方法中,我们将强制 python 函数返回的每条消息都是一个 MultimodalMessage
类。我们还将清理 text
字段中的任何缩进,以便它可以在前端正确显示为 markdown。
我们可以保留 postprocess
方法并修改 _postprocess_chat_messages
# 定义后处理聊天消息的方法,输入参数为多模态消息,类型为 MultimodalMessage 或 字典 或 None
def _postprocess_chat_messages(
self, chat_message: MultimodalMessage | dict | None
) -> MultimodalMessage | None:
# 如果聊天消息为空,则直接返回 None
if chat_message is None:
return None
# 如果聊天消息是一个字典,则将其转化为 MultimodalMessage 类型
if isinstance(chat_message, dict):
chat_message = MultimodalMessage(**chat_message)
# 清理聊天消息的文本,使用 cleandoc 方法对文本进行清理,如果文本不存在,则返回一个空字符串
chat_message.text = inspect.cleandoc(chat_message.text or "")
# 遍历消息中的所有文件
for file_ in chat_message.files:
# 使用 get_mimetype 方法获取文件的 MIME 类型,并设置给文件的 mime_type 属性
file_.file.mime_type = client_utils.get_mimetype(file_.file.path)
# 返回处理过后的聊天消息
return chat_message
在我们结束后端代码之前,让我们修改 example_value
和 example_payload
方法,以返回 ChatbotData
的有效字典表示形式:
# 定义example_value方法,返回一个任意类型的值
def example_value(self) -> Any:
# 返回一个示例聊天信息(仅包含文本“Hello!”)的列表
return [[{"text": "Hello!", "files": []}, None]]
# 定义example_payload方法,返回一个任意类型的值
def example_payload(self) -> Any:
# 返回一个示例聊天信息(仅包含文本“Hello!”)的列表
return [[{"text": "Hello!", "files": []}, None]]
恭喜 - 后端已完成!
第 3a 部分 - Index.svelte 文件
Chatbot
组件的前端分为两部分 - Index.svelte
文件和 shared/Chatbot.svelte
文件。 Index.svelte
文件对从服务器接收的数据进行一些处理,然后将对话的渲染委托给 shared/Chatbot.svelte
文件。首先我们将修改 Index.svelte
文件,以对后端将返回的新数据类型进行处理。
让我们开始将我们的自定义类型从我们的 Python data_model
移植到 TypeScript。打开 frontend/shared/utils.ts
,并在文件顶部添加以下类型定义:
// 定义了一个名为FileMessage的类型,
// 其中包含一个必需的file属性,类型为FileData,
// 和一个可选的alt_text属性,类型为字符串(string)。
export type FileMessage = {
file: FileData;
alt_text?: string;
};
// 定义了一个名为MultimodalMessage的类型,
// 其中包含一个必需的text属性,类型为字符串(string),
// 和一个可选的files属性,类型为FileMessage类型的数组。
export type MultimodalMessage = {
text: string;
files?: FileMessage[];
}
现在让我们在 Index.svelte
中导入它们,并修改 value
和 _value
的类型注释。
// 从 "shared/utils" 导入 FileMessage 和 MultimodalMessage 类型
import type { FileMessage, MultimodalMessage } from "./shared/utils";
// 定义一个名为 value 的变量,
// 其类型为一个数组,数组中的每个元素是一个长度为2的元组(Tuple),
// 且元组的两个元素都是 MultimodalMessage 或 null。
export let value: [
MultimodalMessage | null,
MultimodalMessage | null
][] = [];
// 定义一个名为 _value 的变量,其类型与 value 变量相同
// 但该 _value 变量并未被赋初始值
let _value: [
MultimodalMessage | null,
MultimodalMessage | null
][];
我们需要规范化每条消息,以确保每个文件都有一个正确的 URL 来获取其内容。我们还需要在 text
键中格式化任何嵌入的文件链接。让我们添加一个 process_message
实用程序函数,并在 value
发生变化时应用它。
// 定义一个函数process_message,
// 接受一个MultimodalMessage或null作为参数,
// 返回一个MultimodalMessage或null
function process_message(msg: MultimodalMessage | null): MultimodalMessage | null {
// 如果传入的消息为null,则直接返回null
if (msg === null) {
return msg;
}
// 将消息文本重定向至某个URL
// (具体实现取决于redirect_src_url函数,
// 在此未给出)
msg.text = redirect_src_url(msg.text);
// 对消息中的files进行标准化处理
// (具体实现取决于normalize_messages函数,
// 在此未给出)
msg.files = msg.files.map(normalize_messages);
// 返回处理后的消息对象
return msg;
}
// 创建一个响应式变量_value,
// 它的值取决于value变量
// 如果value为空,则_value也为空数组;
// 否则,_value的值为value数组中的每一对
// [user_msg, bot_msg]经过process_message函数处理后的结果。
$: _value = value
? value.map(([user_msg, bot_msg]) => [
process_message(user_msg),
process_message(bot_msg)
])
: [];
以上代码的功能是接收输入消息(用户或bot的消息),并进行相应的处理(包括重定向文本的URL和标准化文件消息),然后返回处理后的消息。其中_value
是一个响应式变量,它会随着value
的变化而相应变化。
第 3b 部分 - Chatbot.svelte 文件
让我们开始类似于 Index.svelte
文件,首先修改类型注释。在 <script>
部分的顶部导入 Mulimodal
消息,并使用它来为 value
和 old_value
变量进行类型定义。
// 从 "utils" 导入 MultimodalMessage 类型
import type { MultimodalMessage } from "./utils";
// 定义一个名为value的变量,
// 它的类型可以是一个数组(其中每个元素为一个元组,元组的长度为2,元素类型可以是MultimodalMessage或null),
// 也可以是null。
export let value:
| [
MultimodalMessage | null,
MultimodalMessage | null
][]
| null;
// 定义一个名为old_value的变量,它的类型与value变量一样,初始值为null。
let old_value:
| [
MultimodalMessage | null,
MultimodalMessage | null
][]
| null = null;
我们还需要修改 handle_select
和 handle_like
函数:
// 定义一个名为handle_select的函数,这个函数接收三个参数:
// i 和 j 分别为两个数字,表示操作的索引位置,
// message 是一个 MultimodalMessage 对象或者 null,
// 这个函数不返回任何值。
// 函数的功能是,当选中一个消息时,触发一个名为 "select" 的事件,并将相关信息作为事件的参数。
function handle_select(
i: number,
j: number,
message: MultimodalMessage | null
): void {
dispatch("select", { // 触发 select 事件
index: [i, j], // 将 i 和 j 作为索引位置信息
value: message // 将 message 作为选中的消息传入
});
}
// 定义一个名为handle_like的函数,这个函数接收四个参数:
// i 和 j 分别为两个数字,表示操作的索引位置,
// message 是一个 MultimodalMessage 对象或者 null,
// liked 是一个布尔值,表示是否喜欢。
// 这个函数不返回任何值。
// 函数的功能是,当对一个消息进行 "喜欢/不喜欢" 操作时,触发一个名为 "like" 的事件,并将相关信息作为事件的参数。
function handle_like(
i: number,
j: number,
message: MultimodalMessage | null,
liked: boolean
): void {
dispatch("like", { // 触发 like 事件
index: [i, j], // 将 i 和 j 作为索引位置信息
value: message, // 将 message 作为操作的消息传入
liked: liked // 将 liked 作为操作的类型传入 (喜欢或不喜欢)
});
}
现在开始有趣的部分,实际上在同一消息中渲染文本和文件!
你应该会看到类似以下的代码,它根据消息的类型确定是显示一个文件还是一个 markdown 消息:
// 如果消息类型为字符串
{#if typeof message === "string"}
// 使用Markdown组件来渲染字符串类型的消息
<Markdown
{message} // 传入消息文本
{latex_delimiters} // LaTeX分隔符,用于渲染LaTeX内容
{sanitize_html} // 清理HTML,防止跨站脚本攻击
{render_markdown} // 渲染Markdown,将Markdown格式的文本转换为 HTML
{line_breaks} // 处理换行
on:load={scroll} // 当内容加载完成时,执行滚动操作,以便查看新加载的内容
/>
// 否则如果消息不为null,且消息中的文件类型包含 "audio"
{:else if message !== null && message.file?.mime_type?.includes("audio")}
// 使用HTML的audio元素来渲染音频类型的消息
<audio
data-testid="chatbot-audio" // 设置数据测试id为 "chatbot-audio",便于进行单元测试
controls // 显示媒体播放器的默认控制器
preload="metadata" // 在页面加载时加载音频的元数据,但不加载音频文件本身
...
我们将修改这段代码,使其总是显示文本消息,然后遍历文件并显示所有存在的文件:
// 使用Markdown元素来渲染消息文本
<Markdown
message={message.text}
{latex_delimiters}
{sanitize_html}
{render_markdown}
{line_breaks}
on:load={scroll}
/>
// 循环遍历message中的文件数组
{#each message.files as file, k}
// 如果文件类型包含 "audio"
{#if file !== null && file.file.mime_type?.includes("audio")}
// 渲染audio元素来播放音频文件
<audio
data-testid="chatbot-audio"
controls
preload="metadata"
src={file.file?.url}
title={file.alt_text}
on:play
on:pause
on:ended
/>
// 如果文件类型包含 "video"
{:else if message !== null && file.file?.mime_type?.includes("video")}
// 渲染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>
// 如果文件类型包含 "image"
{:else if message !== null && file.file?.mime_type?.includes("image")}
// 渲染img元素来显示图片文件
<img
data-testid="chatbot-image"
src={file.file?.url}
alt={file.alt_text}
/>
// 如果文件不是音频、视频或图片文件
{:else if message !== null && file.file?.url !== null}
// 渲染a元素来提供文件下载链接
<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元素表示消息仍在发送中
<Pending {layout} />
{/if}
{/each}
我们做到了!🎉
第 4 部分 - 演示
对于这个教程,让我们保持演示的简单性,只展示一个假想用户和一个机器人之间的静态对话。这个演示将展示用户和机器人如何发送文件。在这个教程系列的第 2 部分,我们将构建一个功能齐全的聊天机器人演示!
演示代码将如下所示:
# 导入 gradio 库
import gradio as gr
# 导入来自 gradio_multimodalchatbot 的 MultimodalChatbot 类
from gradio_multimodalchatbot import MultimodalChatbot
# 导入来自 gradio.data_classes 的 FileData 类
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 中
conversation = [[user_msg1, bot_msg1], [user_msg2, bot_msg2], [user_msg3, bot_msg3]]
# 使用 gr.Blocks 构建交互界面
with gr.Blocks() as demo:
# 生成一个多模态聊天机器人的模块,设置值为聊天记录,设置窗口高度为 800
MultimodalChatbot(value=conversation, height=800)
# 启动交互界面
demo.launch()
提示:更改文件路径以对应于您的计算机上的文件。另外,如果您处于开发模式,请确保文件位于自定义组件目录的顶层。
第 5 部分 - 部署与总结
让我们用 gradio cc build
和 gradio cc deploy
构建并部署我们的演示!
您可以在 HuggingFace Spaces 查看我们部署的组件,所有源代码都可以在这里https://huggingface.co/spaces/freddyaboulton/gradio_multimodalchatbot/tree/main找到https://huggingface.co/spaces/freddyaboulton/gradio_multimodalchatbot/tree/main/src。
记录自定义组件
在 4.15 中,我们向 Gradio CLI 添加了一个新的 gradio cc docs
命令,以生成丰富的自定义组件文档。这个命令将自动为用户生成文档,但要充分利用它,您需要做一些事情。
我该如何使用它?
当运行 gradio cc build
时,文档将被生成。您可以传递 --no-generate-docs
参数来关闭此行为。
还有一个独立的 docs
命令,允许进行更大的自定义。如果您手动运行此命令,应该在您的 pyproject.toml
中的 version
被提升后但在构建组件之前运行。
所有参数都是可选的。
这段是关于使用 gradio cc 命令进行自定义组件文档生成的命令行参数说明。
path : 自定义组件的目录。
--demo-dir : 演示目录的路径。
--demo-name : 演示文件的名称。
--space-url : 用于链接的 Hugging Face Space 的 URL。
--generate-space : 创建一个文档空间。
--no-generate-space : 不创建一个文档空间。
--readme-path : README.md 文件的路径。
--generate-readme : 创建一个 README.md 文件。
--no-generate-readme : 不创建一个 README.md 文件。
--suppress-demo-check :抑制验证检查和警告。
以上这些参数都是为了帮助用户创建和配置自定义组件的文档,并能够快捷地进行演示和发布到 Hugging Face Space。
生成了什么?
gradio cc docs
命令将生成一个交互式的 Gradio 应用程序和一个具有各种功能的静态 README 文件。您可以在这里看到一个例子:
部署在 Hugging Face Spaces 的 Gradio 应用https://www.gradio.app/guides/documenting-custom-components
由 GitHub 渲染的 README.md https://www.gradio.app/guides/documenting-custom-components
README.md 和空间都具有以下特点:
一个描述。
安装说明。
一个完全功能的代码片段。
可选的链接到 PyPi、GitHub 和 Hugging Face Spaces。
包括以下内容的 API 文档:
用于组件初始化的参数表,显示类型、默认值和描述。
该组件如何影响用户的预测功能的描述。
事件及其描述的表格。
在初始化或预处理器或后处理器中可能使用的任何额外接口或类。
此外,Gradio 包括:
一个现场演示。
参数表的一个更丰富、互动的版本。
更好的样式!
我需要做什么?
文档生成器使用现有标准来提取必要信息,即类型提示和文档字符串。没有特定于 Gradio 的 API 用于文档,因此遵循最佳实践通常会产生最好的结果。
如果您在组件源代码中已经使用了类型提示和文档字符串,那么您不需要做太多工作就能从这个功能中受益,但有一些细节您需要注意。
Python 版本
为了获得最佳的文档体验,您在生成文档时需要使用 Python 3.10
或更高版本。这是因为一些用于生成文档的自省功能是在 3.10
中才添加的。
类型提示
Python 类型提示被广泛用于为用户提供有用的信息。 什么是类型提示?
我需要添加提示到哪里?
您不需要为代码的每个部分添加类型提示。为了使文档正确工作,您需要为以下组件方法添加类型提示:
__init__
参数应该被打字。postprocess
参数和返回值应该被打字。preprocess
参数和返回值应该被键入。
如果您正在使用 gradio cc create
,这些类型应该已经存在,但您可能需要根据您所做的任何更改来调整它们。
__init__
在这里,您只需要键入参数。如果您已经用 ` gradio
cc create` 克隆了一个模板,这些应该已经到位。您只需要为您添加或更改的任何内容添加新的提示:
def __init__(
self,
# "value" 参数用于接受一个字符串输入,其默认值为 None
# "value" 可以接受的类型是 字符串 (str) 或者 None
value: str | None = None,
*,
# "sources" 参数用于选择音频来源,可以是 "upload" (上传)或者 "microphone" (麦克风)
# "sources" 可以接受的值是 "upload" 或者 "microphone"
sources: Literal["upload", "microphone"] = "upload,
# "every" 参数表示每隔一段时间进行一次操作,其单位是秒,其默认值为 None
# "every" 可以接受的类型是 浮点型 (float) 或者 None
every: float | None = None,
...
):
# 函数体内容省略
...
preprocess
和 postprocess
preprocess
和 postprocess
方法确定传递给用户函数的值以及需要返回的值。
即使你的组件设计主要是作为输入或输出,添加类型提示到输入参数和返回值也是值得的,因为 Gradio 无法限制组件的使用方式。
在这种情况下,我们特别关心:
返回类型为
preprocess
。postprocess
的输入类型。
# "preprocess" 函数,用于预处理数据
# 接受一个 "payload" 参数,类型可以为 FileData 或 None,表示输入是可选的
# 函数返回的类型可以为包含整型和字符串的元组,或者字符串,或者 None
def preprocess(
self, payload: FileData | None
) -> tuple[int, str] | str | None:
# 函数体省略
...
# "postprocess" 函数,用于后处理数据
# 接受一个 "value" 参数,类型可以为包含整型和字符串的元组,或者 None
# 函数返回的类型可以为 FileData,或者字节串,或者 None,表示返回值是可选的
def postprocess(
self, value: tuple[int, str] | None
) -> FileData | bytes | None:
# 函数体省略
...
文档字符串
文档字符串也被广泛用于提取 API 某些部分更有意义、更易于人阅读的描述。
什么是文档字符串?
虽然文档字符串没有任何语法要求,但我们需要一个特定的结构来进行文档编写目的。
就像类型提示一样,我们关心的具体信息如下:
__init__
参数文档字符串。preprocess
返回文档字符串。postprocess
输入参数文档字符串。
其他一切都是可选的。
文档字符串应始终采用此格式,以便由文档生成器捕获:
Classes
"""
A description of the class.
This can span multiple lines and can _contain_ *markdown*.
"""
事件
在自定义组件中,事件被表示为存储在组件类的 events
字段上的列表。虽然我们不需要事件的类型,但我们确实需要一个人类可读的描述,以便用户可以理解事件的行为。
为了方便这一点,我们必须以特定的方式创建事件。
有两种方法可以向自定义组件添加事件。
内置事件
Gradio 附带了多种内置事件,这些可能已足够您的组件使用。如果您正在使用内置事件,您无需进行任何操作,因为我们可以提取它们已有的描述:
# 从 gradio.events 模块中导入 Events 类
from gradio.events import Events
# 定义一个名为 ParamViewer 的类,继承自 Component 类
class ParamViewer(Component):
# 类的其他定义部分省略...
# 在类中定义一个名为 EVENTS 的列表,列表中包含了两个由 Events 类定义的事件:change 和 upload
EVENTS = [
Events.change, # change 事件,通常会在表单值改变时触发
Events.upload, # upload 事件,通常会在上传文件完成时触发
]
自定义事件
如果内置事件不适合您的用例,您可以定义一个自定义事件。这是一个简单的过程,但您必须以这种方式创建事件,以便 docstrings 能够正确工作:
# 从 gradio.events 模块中导入 Events 和 EventListener 类
from gradio.events import Events, EventListener
# 定义一个名为 ParamViewer 的类,继承自 Component 类
class ParamViewer(Component):
# 类的其他定义部分省略...
# 在类中定义一个名为 EVENTS 的列表
EVENTS = [
# 包含了一个叫做 change 的事件,通常会在表单值改变时触发
Events.change,
# 使用 EventListener 创建了一个名为 "bingbong" 的事件监听器
# 当用户做出一次 "bingbong" 操作时,这个事件监听器就会触发
# "doc" 参数用来给这个事件监听器添加一段文档字符串,用于描述这个事件监听器的用途和作用
EventListener(
"bingbong",
doc="This listener is triggered when the user does a bingbong."
),
]
演示
demo/app.py
经常用于开发组件,生成实时演示和代码片段。这里唯一的严格规则是 demo.launch()
命令必须包含在如下的 __name__ == "__main__"
条件中:
if __name__ == "__main__":
demo.launch()
文档生成器将扫描此类条款,并在缺少时报错。如果您不在 demo/app.py
内启动演示,那么您可以传递 --suppress-demo-check
来关闭此检查。
演示推荐
虽然没有额外的规则,但有一些最佳实践你应该记住,以便从文档生成器中获得最佳体验。
这些只是指导原则,每种情况都是独特的,但它们是值得记住的健全原则。
保持演示紧凑
紧凑的演示看起来更好,也使用户更容易理解演示的作用。尽可能移除多余的 UI 元素,以便将用户的注意力集中在核心用例上。
有时,为文档专门设置一个 demo/app.py
,并为你的测试目的准备一个更复杂的应用程序是有意义的。你也可以创建其他空间,展示更复杂的示例,并通过主类文档字符串或 pyproject.toml
描述将它们链接起来。
保持代码简洁
“入门”代码片段使用了演示代码,应尽可能简短,以保持用户参与并避免混淆。
样本代码片段的任务不是展示整个 API;这个片段应该是新用户成功的最短路径。它应该易于键入或复制粘贴,并且容易理解。解释性评论应该简洁明了。
避免外部依赖
如上所述,用户应能够复制粘贴代码片段并拥有一个完全工作的应用程序。尝试避免第三方库依赖以便做到这一点。
您应该仔细考虑任何示例;避免需要额外文件的示例或对环境作出假设的示例通常是个好主意。
确保 demo 目录是自包含的
只有在某些情况下, demo
目录会被上传到 Hugging Face 空间,因为如果可能的话,组件会通过 PyPi 安装。至关重要的是,这个目录是自包含的,任何需要正确运行演示的文件都必须存在。
额外的 URLs
文档生成器将生成一些按钮,为用户提供有用的信息和链接。它们在某些情况下会自动获得,但有些需要在 pyproject.yaml
中明确包含。
PyPi 版本和链接 - 这是自动生成的。
GitHub 仓库 - 这是通过
pyproject.toml
的project.urls.repository
填充的。拥抱脸空间 - 这是通过
pyproject.toml
的project.urls.space
填充的。
一个例子 pyproject.toml
网址部分可能看起来像这样:
[project.urls]
repository = "https://github.com/user/repo-name"
space = "https://huggingface.co/spaces/user/space-name"