【Gradio】构建自定义多模态聊天机器人

55a77da8d3b7cae7ed98c7fa8c6f539e.png

这是我们构建自定义多模态聊天机器人组件两部分系列的第一部分。在第一部分中,我们将修改 Gradio 聊天机器人组件,使其能够在同一消息中显示文本和媒体文件(视频、音频、图片)。在第二部分中,我们将构建一个自定义的文本框组件,该组件能够向聊天机器人发送多模态消息(文本和媒体文件)。

您可以跟随这篇文章的作者,通过以下 YouTube 视频实现聊天机器人组件!

这里是我们的多模态聊天机器人组件将会是什么样子的预览:

ab509c942f7d8496538aea0f2d2f01fa.png

第 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):# 文件数据,类型为 FileDatafile: 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:# 如果聊天消息为空,则直接返回 Noneif 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,则直接返回nullif (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元素来渲染音频类型的消息<audiodata-testid="chatbot-audio" // 设置数据测试id为 "chatbot-audio",便于进行单元测试controls // 显示媒体播放器的默认控制器preload="metadata" // 在页面加载时加载音频的元数据,但不加载音频文件本身...

我们将修改这段代码,使其总是显示文本消息,然后遍历文件并显示所有存在的文件:

// 使用Markdown元素来渲染消息文本
<Markdownmessage={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元素来播放音频文件<audiodata-testid="chatbot-audio"controlspreload="metadata"src={file.file?.url}title={file.alt_text}on:playon:pauseon:ended/>// 如果文件类型包含 "video"{:else if message !== null && file.file?.mime_type?.includes("video")}// 渲染video元素来播放视频文件<videodata-testid="chatbot-video"controlssrc={file.file?.url}title={file.alt_text}preload="auto"on:playon:pauseon:ended><track kind="captions" /></video>// 如果文件类型包含 "image"{:else if message !== null && file.file?.mime_type?.includes("image")}// 渲染img元素来显示图片文件<imgdata-testid="chatbot-image"src={file.file?.url}alt={file.alt_text}/>// 如果文件不是音频、视频或图片文件{:else if message !== null && file.file?.url !== null}// 渲染a元素来提供文件下载链接<adata-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:# 生成一个多模态聊天机器人的模块,设置值为聊天记录,设置窗口高度为 800MultimodalChatbot(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。

记录自定义组件

146f1a69fc43d44a24fc2bd925e9062a.png

在 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) 或者 Nonevalue: str | None = None,*,# "sources" 参数用于选择音频来源,可以是 "upload" (上传)或者 "microphone" (麦克风)# "sources" 可以接受的值是 "upload" 或者 "microphone"sources: Literal["upload", "microphone"] = "upload,# "every" 参数表示每隔一段时间进行一次操作,其单位是秒,其默认值为 None# "every" 可以接受的类型是 浮点型 (float) 或者 Noneevery: 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 和 uploadEVENTS = [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"

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/857476.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深度解析RocketMq源码-持久化组件(一) MappedFile

1. 绪论 rocketmq之所以能够有如此大的吞吐量&#xff0c;离不开两个组件&#xff0c;一个是利用netty实现的高性能网络通信组件&#xff1b;另一个就是利用mmap技术实现的存储组件。而在rocketmq的存储组件中主要有三个组件&#xff0c;分别是持久化文件commitLog&#xff0c…

多模态MLLM都是怎么实现的(10)-Chameleon和Florence-2如果你想玩多模态就不能不了解

这个也是一个补充文&#xff0c;前9章基本把该讲的讲了&#xff0c;今天这个内容主要是因为Meta出了一个Chameleon&#xff0c;这个以后可能会成为LLaMA的一个很好的补充&#xff0c;或者说都有可能统一起来&#xff0c;叫LLaMA或者Chamleon或者什么别的&#xff0c;另外我司把…

【图解IO与Netty系列】Netty源码解析——事件循环

Netty源码解析——事件循环 Netty事件循环源码解析select()processSelectedKeys()NioMessageUnsafe#read()NioByteUnsafe#read() runAllTasks() Netty事件循环 当Netty服务端启动起来以后&#xff0c;就可以接受客户端发送的请求&#xff0c;接收到客户端发来的请求后就会有事…

计算机网络 交换机的VLAN配置

一、理论知识 1.VLAN的定义 ①VLAN虚拟局域网&#xff0c;是一种通过将局域网内的设备逻辑地而不是物理地划分成一个个网段从而实现虚拟工作组的技术。 ②IEEE于1999年颁布了用以标准化VLAN实现方案的802.1Q协议标准草案。 ③VLAN技术允许网络管理者将一个物理的LAN逻辑地划…

MySQL存储管理(一):删数据

从表中删除数据 从表中删除数据&#xff0c;也即是delete过程。 什么是表空间 表空间可以看做是InnoDB存储引擎逻辑结构的最高层&#xff0c;所有的数据都存放在表空间中。默认情况下&#xff0c;InnoDB存储引擎有一个共享表空间idbdata1&#xff0c;即所有数据都存放在这个表…

无限滚动表格

纵向无限滚动 单元格内部横向滚动 <!--* Description: 横向、纵向滚动表格* Author: liyanfeng liyanfenghopewind.com* Date: 2024-06-15 16:06:57* LastEditors: liyanfeng liyanfenghopewind.com* LastEditTime: 2024-06-20 17:15:37* FilePath: \plus-ui\src\componen…

SEO是什么?SEO相关发展历史

一、SEO是什么意思&#xff1f; SEO&#xff08;Search Engine Optimization&#xff09;&#xff0c;翻译成中文就是“搜索引擎优化”。简单来讲&#xff0c;seo是指自然搜索结果下获得的网站流量的技术&#xff0c;是可以不用花钱就可以让自己的网站有好的排名&#xff0c;也…

C语言:生命周期和作用域,static和extern

关键字static与extern 1.作用域&#xff08;scope&#xff09;&#xff1a;代码中能够访问到变量的范围&#xff08;变量可以被使用的文本区间&#xff09;。&#xff08;分为全局作用域和局部作用域&#xff09; ☺全局作用域&#xff1a;在整个程序中都能访问的变量。通常…

C语言入门系列:数据类型转换

文章目录 一&#xff0c;自动类型转换1&#xff0c;赋值运算1.1&#xff0c;浮点数赋值给整型变量-不安全1.2&#xff0c;整数赋值给浮点数变量-安全1.3&#xff0c;窄类型赋值给宽类型-安全1.4&#xff0c;宽类型赋值给窄类型-不安全 2&#xff0c;混合类型的运算2.1&#xff…

Ubuntu24使用kubeadm部署高可用K8S集群

Ubuntu24使用kubeadm部署高可用K8S集群 使用kubeadm部署一个k8s集群&#xff0c;3个master1个worker节点。 1. 环境信息 操作系统&#xff1a;ubuntu24.04内存: 2GBCPU: 2网络: 能够互访&#xff0c;能够访问互联网 hostnameip备注k8s-master1192.168.0.51master1k8s-maste…

20.Cargo和Crates.io

标题 一、采用发布配置自定义构建1.1 默认配置1.2 修改配置项 二、将crate发布到Crates.io2.1 编写文档注释2.2 常用&#xff08;文档注释&#xff09;部分2.3 文档注释作用测试2.4 为包含注释的项添加文档注释2.5 使用pub use导出公有API2.6 创建Crates.io账号2.7 发布2.8 版本…

基于STM32的智能停车场管理系统

目录 引言环境准备智能停车场管理系统基础代码实现&#xff1a;实现智能停车场管理系统 4.1 车位检测模块4.2 数据处理与分析4.3 控制系统实现4.4 用户界面与数据可视化应用场景&#xff1a;智能停车场管理与优化问题解决方案与优化收尾与总结 1. 引言 智能停车场管理系统通…

Linux常用命令(17)—pastesortcomm命令(有相关截图)

写在前面&#xff1a; 最近在学习Linux命令&#xff0c;记录一下学习Linux常用命令的过程&#xff0c;方便以后复习。仅供参考&#xff0c;若有不当的地方&#xff0c;恳请指正。如果对你有帮助&#xff0c;欢迎点赞&#xff0c;关注&#xff0c;收藏&#xff0c;评论&#xf…

仿中波本振电路的LC振荡器电路实验

手里正好有一套中波收音机套件的中周。用它来测试一下LC振荡器&#xff0c;电路如下&#xff1a; 用的是两只中频放大的中周&#xff0c;初步测试是用的中周自带的瓷管电容&#xff0c;他们应该都是谐振在465k附近。后续测试再更换电容测试。 静态电流&#xff0c;0.5到1mA。下…

malloc和new的本质区别

目录 一、结论 二、示例 1.实现类T 2.用malloc分配类T的内存空间 3.用new分配类T的内存空间 一、结论 malloc 和 new 都是用于在运行时动态分配内存的机制。但它们之间存在一些本质的区别&#xff0c;主要是在使用方面&#xff0c;现在我们直接说结论&#xff0c;然后在通过…

ArcGIS与Excel分区汇总统计三调各地类面积!数据透视表与汇总统计!

​ 点击下方全系列课程学习 点击学习—>ArcGIS全系列实战视频教程——9个单一课程组合系列直播回放 点击学习——>遥感影像综合处理4大遥感软件ArcGISENVIErdaseCognition 01 需求说明 介绍一下ArcGIS与Excel统计分区各地类的三调地类面积。 ArcGIS统计分析不会&#x…

Unity客户端的Http通讯实战

背景知识 在Unity游戏开发中&#xff0c;一个常见场景是&#xff0c;后端扔过来一个Swagger后端接口网页&#xff0c;需要你使用对应的接口对应的接口发送和接收数据&#xff0c;如图所示为发起Get请求&#xff1a; 我们可以通过点击Try it out按钮直接在网页上测试收发数据&a…

spring整合openAI大模型之Spring AI

文章目录 一、SpringAI简介1.什么是SpringAI2.SpringAI支持的大模型类型&#xff08;1&#xff09;聊天模型&#xff08;2&#xff09;文本到图像模型&#xff08;3&#xff09;转录&#xff08;音频到文本&#xff09;模型&#xff08;4&#xff09;嵌入模型&#xff08;5&…

Guava-EventBus 源码解析

EventBus 采用发布订阅者模式的实现方式&#xff0c;它实现了泛化的注册方法以及泛化的方法调用,另外还考虑到了多线程的问题,对多线程使用时做了一些优化&#xff0c;观察者模式都比较熟悉&#xff0c;这里会简单介绍一下&#xff0c;重点介绍的是如何泛化的进行方法的注册以及…

dial tcp 10.96.0.1:443: connect: no route to host

1、创建Pod一直不成功&#xff0c;执行kubectl describe pod runtime-java-c8b465b98-47m82 查看报错 Warning FailedCreatePodSandBox 2m17s kubelet Failed to create pod sandbox: rpc error: code Unknown desc failed to setup network for…