Bootstrap

全网最详细Gradio教程系列5——Gradio Client: javascript

前言

本系列文章主要介绍WEB界面工具Gradio。Gradio是Hugging Face发布的一个简易的webui开发框架,它基于FastAPI和svelte,便于部署人工智能模型,是当前热门的非常易于开发和展示机器大语言模型及扩散模型的UI框架。本系列文章不仅从概念上介绍Gradio的详细技术架构、历史、应用场景、与其他框架Gradio/NiceGui/StreamLit/Dash/PyWebIO的区别,还进行了大量实践讲解。实践部分先讲解了多种不同的安装、运行和部署方式,然后实践了基础类的Interfaces、Blocks和Custom Components,最后对详解Gradio的多种高级特性,比如Gradio-Lite、Gradio Client和Tabular Data Science And Plots等。

本系列文章目录如下:

  1. 《全网最详细Gradio教程系列1——Gradio简介》
  2. 《全网最详细Gradio教程系列2——Gradio的安装与运行》
  3. 《全网最详细Gradio教程系列3——Gradio的3+1种部署方式实践》
  4. 《全网最详细Gradio教程系列4——浏览器集成Gradio-Lite》
  5. 《全网最详细Gradio教程系列5——Gradio Client: python》
  6. 《全网最详细Gradio教程系列5——Gradio Client: javascript》
  7. 《全网最详细Gradio教程系列5——Gradio Client: curl》
  8. 《全网最详细Gradio教程系列6——Interfaces》
  9. 《全网最详细Gradio教程系列7——Blocks》
  10. 《全网最详细Gradio教程系列8——Custom Components》
  11. 《全网最详细Gradio教程系列9——Tabular Data Science And Plots 》

本篇摘要

本章讲解访问API的Gradio Client的三种使用方式:python、javascript和curl。受字数限制,所以分三篇博客发布,本篇讲解javascript方式。

5. Gradio Client的三种使用方式

程序部署完成后,如何将Gradio App作为API访问使用呢,这就用到Gradio Client。Gradio Client的有三种使用方式:python、javascript和curl,以下分别讲解。

5.2 使用Gradio JavaScript Client

通过API使用Gradio应用程序,除了Python方式,还有JavaScript和Curl。JavaScript方式非常易于将任意Gradio应用程序用作API,本小节讲述以JavaScript方式使用Gradio Client,包括两种安装方式npm和CDN、web在线开发环境PLAYCODE、连接Gradio的两种方式URL和SpaceID、查看API的两种方式view_api和页面查看API和使用API的两种方式直接调用.predict()和异步调用job。

由于Python Client部分已经讲述了大部分程序细节,因此本节不再重复解释和编码,而是将对应Python命令替换为JavaScript方式,以减少篇幅。

5.2.1 安装

1. npm方式:node.js

Gradio JavaScript Client使用@gradio/client库,@gradio还包括一些其他库,比如lite、audio、image、vedio、chatbot及gallery等。安装@gradio/client库需要用到包管理器npm,而npm被包含在Node.js中,这里要求Node.js版本>=18.0.0。

首先,安装Node.js,其bash脚本如下:

# installs nvm (Node Version Manager)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash

# download and install Node.js (you may need to restart the terminal)
nvm install 20

# verifies the right Node.js version is in the environment
node -v # should print `v20.15.1`

# verifies the right NPM version is in the environment
npm -v # should print `10.7.0`

上述命令也可以直接在终端运行。安装完Node.js后,使用npm或其它包管理器安装@gradio/client:

$ npm i @gradio/client
added 64 packages in 10s

10 packages are looking for funding
  run `npm fund` for details

此命令将@gradio/client添加到项目依赖项中,以便将其导入JavaScript或TypeScript文件中。
此时可测试简单的脚本,比如在控制台输出日志,代码如下:

console.log("hello NodeJS")

保存为hello.js,使用命令node运行,输出如下:

$ node hello.js 
hello NodeJS

注意:

  1. 使用Ubuntu系统的apt-get命令并不能安装正确版本的nodejs或npm,安装的nodejs版本只有12.XX,并且不包含npm。
  2. 除了脚本方式,还可以通过Ubuntu software软件中心安装v20.15.1 Node.js。
2. CDN方式

为了将@gradio/client快速添加到web项目,可以使用jsDelivr CDN将最新版的@gradio/client加载到HTML文件中:

<script src="https://cdn.jsdelivr.net/npm/@gradio/client/dist/index.min.js"></script>

整体html文件内容如下所示:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="src/style.css">
    <script type="module" src="https://cdn.jsdelivr.net/npm/@gradio/client/dist/index.min.js"></script>
  </head>
  <body>
    <h1 id="header"></h1>

    <script type="module" src="src/script.js"></script>
    
  </body>
</html>

在文件script.js可填入javascript脚本内容,如下:

console.log("hello NodeJS")

注意:一定要将<script>内容添加到HTML的<head>标签中,这将安装最新版本的@gradio/client,但建议在生产环境中硬编码该版本。然后将js脚本放在<body>的<script>标签内。这种方法尽管有一定局限性,但非常适合实验或原型制作。更详细的用法可参考jsDelivr的官方网站:https://www.jsdelivr.com/package/npm/@gradio/client。

3. 在线运行环境:PLAYCODE

为了演示CDN方式使用@gradio/client,同时也便于运行使用SpaceID创建的App,我们引入国外的在线运行平台。在众多在线运行环境中,作者推荐PLAYCODE,官网地址:https://playcode.io/。它界面简洁,支持javascript/typescript/html/css/React/vue/svelte等多种格式及和它们相关的运行库,并且可以在线安装所需package,非常方便在线开发小程序。

在线安装@gradio/client后,在运行带有关键字await的示例程序时,会报以下错误:

Top-level await is currently not supported with the "cjs" output format

这是因为默认情况下,顶层的await不会被所有JavaScript运行时或者编译工具所支持。这时只需要用async()方法将await关键字代码包裹即可,我们以查看API代码为例(后面带有await关键字的代码也用同样方式处理即可,限于篇幅,作者不再赘述,请读者自行添加):

import { Client } from "@gradio/client";

(async () => {
const app = await Client.connect("abidlabs/whisper");

const app_info = await app.view_api();
console.log(app_info);
})();

运行截图如下:

在这里插入图片描述
这里主要为了演示CDN的使用方式,但本篇命令npm和cdn两种方式均适用。

5.2.2 连接到Gradio程序

1. 通过URL或SpaceID连接

首先,实例化Client时,需将其连接到运行在Hugging Face Spaces或其它在线网络上的Gradio应用程序,可以通过SpaceID和URL两种方式,如下:

import { Client } from "@gradio/client";

# 通过URL连接
const app = Client.connect("https://huggingface.co/spaces/gradio/calculator")
# 通过SpaceID连接
const app = Client.connect("gradio/calculator")
2. 辅助:duplicate()和hf_token

对于Space的无限制访问,可以使用duplicate()。当使用duplicate()或访问私有Space时,需要用到hf_token,示例代码如下:

import { Client } from "@gradio/client";

const response = await fetch(
	"https://audio-samples.github.io/samples/mp3/blizzard_unconditional/sample-0.mp3"
);
const audio_file = await response.blob();

const app = await Client.duplicate("abidlabs/whisper", { hf_token: "hf_..." });
const transcription = await app.predict("/predict", [audio_file]);

console.log(transcription.data);

注意:

  1. 如果之前复制了一个空间,重新运行Client.duplicate()不会创建新的空间。相反,客户端将连接到之前创建的空间。因此,使用相同的空格多次重新运行Client.duplicate()方法是安全的。
  2. 如果原始Space使用GPU,复制过来的私人Space也将使用GPU,我们的Hugging Face帐户将根据GPU的价格计费。为了尽量减少费用,可以让共享空间将在5分钟不活动后自动进入睡眠状态,或者使用duplicate参数的硬件和超时属性设置硬件,代码如下所示:
import { Client } from "@gradio/client";

const app = await Client.duplicate("abidlabs/whisper", {
	hf_token: "hf_...",
	timeout: 60,
	hardware: "a10g-small"    # 免费设置"cpu-basic"
});

5.2.3 查看API端点

与Python一样,javascript也有两种方式查看api:函数view_api()和页面查看API。

1. 函数view_api()

连接到Gradio应用程序后,我们可以通过调用客户端的view_api()方法查看可用的api。对于Whisper Space,由于直接使用SpaceID会出现连接错误,我们可以在本地运行abidlabs/whisper后,使用http://127.0.0.1:7860连接:

import { Client } from "@gradio/client";

const app = await Client.connect("http://127.0.0.1:7860");

const app_info = await app.view_api(all_endpoints=True);

console.log(app_info);

关于/predict及端点的解释请参考前面内容,这里只解释参数all_endpoints。当Gradio应用程序具有未命名的API端点时,可以设置all_endpoints来显示这些端点。
保存后缀为.js的文件后,使用命令node xxx.js运行,可以看到以下输出:

$ node gradio_client_viewapi.js 
{
  named_endpoints: {
    '/predict': { parameters: [Array], returns: [Array], type: [Object] }
  },
  unnamed_endpoints: { '0': { parameters: [Array], returns: [Array], type: [Object] } }
}
# 官方示例输出
{
	"named_endpoints": {
		"/predict": {
			"parameters": [
				{
					"label": "text",
					"component": "Textbox",
					"type": "string"
				}
			],
			"returns": [
				{
					"label": "output",
					"component": "Textbox",
					"type": "string"
				}
			]
		}
	},
	"unnamed_endpoints": {}
}

这里运行的结果和官方示例有一些出入,这里不做深究,了解用法就可以了。

2. 页面查看API

我们也可以单击Gradio应用程序页脚中的“通过api使用”链接,它向我们显示了相同的信息以及示例用法,如下图:

在这里插入图片描述
查看API页面还包括一个“API Recorder”,它可以记录我们与Gradio UI的交互信息,并将交互转换为相应的代码,以便与JS客户端一起运行。

5.2.4 使用API

查看完API后,就需要学会使用它们。有两种使用方式:直接调用.predict()和异步调用job。

1. 直接调用.predict()

最简单的使用API进行预测的方法就是使用适当的参数调用.predict()方法:

import { Client } from "@gradio/client";

const app = await Client.connect("abidlabs/en2fr");
const result = await app.predict("/predict", ["Hello"]);
console.log(result);

>>(5) {type: "data", time: Tue Jul 23 2024...}
type:"data"
time:Tue Jul 23 2024 10:42:09 GMT+0800 (中国标准时间)
data:(1) ["Bonjour."]
endpoint:"/predict"
fn_index:0
[[Prototype]]:{}

我们在来看一下其它的参数类型:
多个参数:可以将多个参数作为数组传递给.predict(),并且对结果进行json字符化处理,如下所示:

import { Client } from "@gradio/client";

const app = await Client.connect("gradio/calculator");
const result = await app.predict("/predict", [4, "add", 5]);
console.log(JSON.stringify(result, null, 2));

>>{
  "type": "data",
  "time": "2024-07-23T07:43:34.380Z",
  "data": [
    9
  ],
  "endpoint": "/predict",
  "fn_index": 0
}

多媒体格式:对于某些多媒体格式,如图像,可以根据需要传入Buffer、Blob或File。在Node节点中,可以使用Buffer或Blob;在浏览器环境中,可使用Blob或File。

import { Client } from "@gradio/client";

const response = await fetch(
	"https://audio-samples.github.io/samples/mp3/blizzard_unconditional/sample-0.mp3"
);
const audio_file = await response.blob();

const app = await Client.connect("abidlabs/whisper");
const result = await app.predict("/predict", [audio_file]);

>>(5) {type: "data", time: Tue Jul 23 2024...}
type:"data"
time:Tue Jul 23 2024 10:55:37 GMT+0800 (中国标准时间)
data:(1) ["AutomaticSpeechRecognitionOutput(t...]
0:"AutomaticSpeechRecognitionOutput(text=" My thought I have nobody by a beauty and will as you poured. Mr. Rochester is sir, but that so don't find simpus, and devoted abode, to at might in a", chunks=None)"
[[Prototype]]:[]
endpoint:"/predict"
fn_index:0
[[Prototype]]:{}

由于需要传输.mp3文件,所以示例代码有很大几率失败,多运行几次即可。

2. 异步调用job

因为.predict()是一个阻塞操作,所以当等待API返回结果期间,可以让其在后台运行。我们可以通过创建Job实例并利用其.submit()提交操作,让JOB在后台运行,通过使用可迭代接口获取返回的结果。这对可迭代端点或生成器端点特别有用,它们会随着时间的推移返回一系列的离散响应值。示例代码如下:

import { Client } from "@gradio/client";

function log_result(payload) {
	const {
		data: [translation]
	} = payload;

	console.log(`The translated result is: ${translation}`);
}

const app = await Client.connect("abidlabs/en2fr");
const job = app.submit("/predict", ["Hello"]);

for await (const message of job) {
	log_result(message);
}

在示例代码中,我们首先利用Client创建JOB,然后循环等待可迭代端点返回结果,最后在平台日志输出返回的结果。输出效果参考下面例子。

事件与状态:在连接Gradio应用程序时,默认返回数据。我们还可以在实例化client时,通过设置事件接口.connect()的参数events,将status和data作为数组传递给events,以便同时获取正在运行job的状态和数据,示例代码如下:

import { Client } from "@gradio/client";

function log_status(status) {
	console.log(
		`The current status for this job is: ${JSON.stringify(status, null, 2)}.`
	);
}

const app = await Client.connect("abidlabs/en2fr", {
	events: ["status", "data"]
});
const job = app.submit("/predict", ["Hello"]);

for await (const message of job) {
	if (message.type === "status") {
		log_status(message);
	}
}

运行截图如下所示:
在这里插入图片描述
返回的状态包括以下属性:stage(当前作业的可读状态,如"pending" | “generating” | “complete” | “error”),code(gradio的job状态代码),position(此作业在队列中的当前位置),queue(是否排队),size(队列总大小),eta(此作业预计完成的时间),success(作业是否成功完成的布尔值),以及time(Date对象,详细说明状态生成的时间)。

取消作业:job实例还有一个.cancel()方法,用于取消已排队但尚未启动的作业,如下:

import { Client } from "@gradio/client";

const app = await Client.connect("abidlabs/en2fr");
const job_one = app.submit("/predict", ["Hello"]);
const job_two = app.submit("/predict", ["Friends"]);

job_one.cancel();
job_two.cancel();

如果第一个作业已开始处理,则不会取消,但client客户端将不再接受更新信息(丢弃作业)。如果第二个作业尚未启动,它将被成功取消并从队列中删除。

生成器端点:某些Gradio API端点不返回单个值,而是返回一系列值。这时可以使用可迭代接口实时监听这些值:

import { Client } from "@gradio/client";

const app = await Client.connect("gradio/count_generator");
const job = app.submit(0, [9]);

for await (const message of job) {
	console.log(message.data);
}

setTimeout(() => {
	job.cancel();
}, 3000);

上面代码将输出endpoint端点产生的系列值,然后通过设置超时函数setTimeout(),在3秒后取消迭代输出的job,这时job将立即完成。

参考文献

  1. Getting Started with the Gradio JavaScript Client
;