使用 Gradio 的重载模式快速构建 AI 应用

发布于 2024 年 4 月 16 日
在 GitHub 上更新

在本文中,我将向您展示如何使用 Gradio 的重载模式快速构建一个功能强大的 AI 应用。但在我们开始之前,我想解释一下重载模式的作用,以及为什么 Gradio 要实现自己的自动重载逻辑。如果您已经熟悉 Gradio 并希望直接开始构建,请跳至第三部分

重载模式是做什么的?

简而言之,它可以在不重启 Gradio 服务器的情况下,从您的源文件中加载最新的更改。如果这还不太好理解,请继续阅读。

Gradio 是一个用于创建交互式机器学习应用的热门 Python 库。Gradio 开发者完全在 Python 中声明他们的 UI 布局,并添加一些 Python 逻辑,这些逻辑会在发生 UI 事件时触发。如果您了解基本的 Python,它就很容易学习。如果您还不熟悉 Gradio,请查看这篇快速入门

Gradio 应用的启动方式与任何其他 Python 脚本一样,只需运行 python app.py(包含 Gradio 代码的文件可以任意命名)。这将启动一个 HTTP 服务器,用于渲染您的应用 UI 并响应用户操作。如果您想对应用进行更改,您需要停止服务器(通常使用 Ctrl + C),编辑源文件,然后重新运行脚本。

在开发应用时,不得不停止并重新启动服务器会带来很多延迟。如果有一种方法可以自动加载最新的代码更改,让您可以立即测试新想法,那就更好了。

这正是 Gradio 的重载模式所做的。只需运行 gradio app.py 而不是 python app.py,即可在重载模式下启动您的应用!

为什么 Gradio 要构建自己的重载器?

Gradio 应用使用 uvicorn 运行,这是一个用于 Python Web 框架的异步服务器。Uvicorn 已经提供了自动重载功能,但 Gradio 出于以下原因实现了自己的逻辑:

  1. 更快的重载:Uvicorn 的自动重载会关闭服务器然后重新启动。这比手动操作要快,但对于开发 Gradio 应用来说还是太慢了。Gradio 开发者在 Python 中构建他们的 UI,所以他们应该在做出更改后立即看到 UI 的外观。这在 Javascript 生态系统中是标准做法,但在 Python 中是新鲜事物。
  2. 选择性重载:Gradio 应用是 AI 应用。这意味着它们通常会将 AI 模型加载到内存中或连接到像向量数据库这样的数据存储。在开发过程中重新启动服务器将意味着重新加载模型或重新连接数据库,这在开发周期之间引入了太多的延迟。为了解决这个问题,Gradio 引入了一个 if gr.NO_RELOAD: 代码块,您可以用它来标记不应被重载的代码。这只有在 Gradio 实现自己的重载逻辑时才可能实现。

现在我将向您展示如何使用 Gradio 的重载模式快速构建一个 AI 应用。

构建一个文档分析应用

我们的应用将允许用户上传文档图片并就其提问。他们将以自然语言获得答案。我们将使用免费的 Hugging Face 推理 API,因此您应该可以在您的电脑上跟着操作。无需 GPU!

首先,我们来创建一个最基本的 gr.Interface。在一个名为 app.py 的文件中输入以下代码,并使用 gradio app.py 在重载模式下启动它。

import gradio as gr

demo = gr.Interface(lambda x: x, "text", "text")

if __name__ == "__main__":
    demo.launch()

这将创建以下简单的 UI。

Simple Interface UI

由于我想让用户在提问的同时上传图片文件,我将把输入组件切换为 gr.MultimodalTextbox()。注意 UI 是如何即时更新的!

Simple Interface with MultimodalTextbox

这个 UI 可以工作,但我认为如果输入文本框在输出文本框下面会更好。我可以使用 Blocks API 来实现这一点。我还通过添加占位符文本来定制输入文本框,以引导用户。

Switch to Blocks

现在我对 UI 感到满意了,我将开始实现 chat_fn 的逻辑。

由于我将使用 Hugging Face 的推理 API,我将从 huggingface_hub 包中导入 InferenceClient(它随 Gradio 预装)。我将使用 impira/layouylm-document-qa 模型来回答用户的问题。然后我将使用 HuggingFaceH4/zephyr-7b-beta LLM 以自然语言提供回应。

from huggingface_hub import InferenceClient

client = InferenceClient()

def chat_fn(multimodal_message):
    question = multimodal_message["text"]
    image = multimodal_message["files"][0]
    
    answer = client.document_question_answering(image=image, question=question, model="impira/layoutlm-document-qa")
    
    answer = [{"answer": a.answer, "confidence": a.score} for a in answer]
   
    user_message = {"role": "user", "content": f"Question: {question}, answer: {answer}"}
   
    message = ""
    for token in client.chat_completion(messages=[user_message],
                           max_tokens=200, 
                           stream=True,
                           model="HuggingFaceH4/zephyr-7b-beta"):
        if token.choices[0].finish_reason is not None:
           continue
        message += token.choices[0].delta.content
        yield message

这是我们的演示在运行中的样子!

Demoing our App

我还会提供一个系统消息,以便 LLM 保持答案简短,并且不包含原始的置信度分数。为了避免在每次更改时重新实例化 InferenceClient,我将把它放在一个“不重载”的代码块中。

if gr.NO_RELOAD:
    client = InferenceClient()

system_message = {
    "role": "system",
    "content": """
You are a helpful assistant.
You will be given a question and a set of answers along with a confidence score between 0 and 1 for each answer.
You job is to turn this information into a short, coherent response.

For example:
Question: "Who is being invoiced?", answer: {"answer": "John Doe", "confidence": 0.98}

You should respond with something like:
With a high degree of confidence, I can say John Doe is being invoiced.

Question: "What is the invoice total?", answer: [{"answer": "154.08", "confidence": 0.75}, {"answer": "155", "confidence": 0.25}

You should respond with something like:
I believe the invoice total is $154.08 but it can also be $155.
"""}

这是我们现在的演示!系统消息确实有助于让机器人的回答简短,并且不含长串的小数。

Demo of app with system message

作为最后的改进,我将在页面上添加一个 Markdown 标题。

Adding a Header

总结

在这篇文章中,我使用 Gradio 和 Hugging Face 推理 API 开发了一个可用的 AI 应用。当我开始开发时,我并不知道最终产品会是什么样子,因此 UI 和服务器逻辑的即时重载让我能非常迅速地迭代不同的想法。我花了大约一个小时就开发了整个应用!

如果您想查看这个演示的完整代码,请查看这个 空间

社区

注册登录 发表评论