diff --git a/.changeset/eleven-turkeys-talk.md b/.changeset/eleven-turkeys-talk.md
new file mode 100644
index 0000000000000..b65af05cd57db
--- /dev/null
+++ b/.changeset/eleven-turkeys-talk.md
@@ -0,0 +1,37 @@
+---
+"gradio": minor
+"gradio_client": minor
+---
+
+highlight:
+
+#### Create Discord Bots from Gradio Apps 🤖
+
+We're excited to announce that Gradio can now automatically create a discord bot from any `gr.ChatInterface` app.
+
+It's as easy as importing `gradio_client`, connecting to the app, and calling `deploy_discord`!
+
+*🦙 Turning Llama 2 70b into a discord bot 🦙*
+```python
+import gradio_client as grc
+grc.Client("ysharma/Explore_llamav2_with_TGI").deploy_discord(to_id="llama2-70b-discord-bot")
+```
+
+
+
+#### Getting started with template spaces
+
+To help get you started, we have created an organization on Hugging Face called [gradio-discord-bots](https://huggingface.co/gradio-discord-bots) with template spaces you can use to turn state of the art LLMs powered by Gradio to discord bots.
+
+Currently we have template spaces for:
+
+* [Llama-2-70b-chat-hf](https://huggingface.co/spaces/gradio-discord-bots/Llama-2-70b-chat-hf) powered by a FREE Hugging Face Inference Endpoint!
+* [Llama-2-13b-chat-hf](https://huggingface.co/spaces/gradio-discord-bots/Llama-2-13b-chat-hf) powered by Hugging Face Inference Endpoints.
+* [Llama-2-13b-chat-hf](https://huggingface.co/spaces/gradio-discord-bots/llama-2-13b-chat-transformers) powered by Hugging Face transformers.
+* [falcon-7b-instruct](https://huggingface.co/spaces/gradio-discord-bots/falcon-7b-instruct) powered by Hugging Face Inference Endpoints.
+* [gpt-3.5-turbo](https://huggingface.co/spaces/gradio-discord-bots/gpt-35-turbo), powered by openai. Requires an OpenAI key.
+
+But once again, you can deploy ANY `gr.ChatInterface` app exposed on the internet! So don't hesitate to try it on your own Chatbots.
+
+❗️ Additional Note ❗️: Technically, any gradio app that exposes an api route that takes in a single string and outputs a single string can be deployed to discord. But `gr.ChatInterface` apps naturally lend themselves to discord's chat functionality so we suggest you start with those.
+
diff --git a/client/python/gradio_client/cli/__init__.py b/client/python/gradio_client/cli/__init__.py
new file mode 100644
index 0000000000000..0c79625348914
--- /dev/null
+++ b/client/python/gradio_client/cli/__init__.py
@@ -0,0 +1,3 @@
+from gradio_client.cli import deploy_discord
+
+__all__ = ["deploy_discord"]
diff --git a/client/python/gradio_client/cli/deploy_discord.py b/client/python/gradio_client/cli/deploy_discord.py
new file mode 100644
index 0000000000000..bb26c088abeef
--- /dev/null
+++ b/client/python/gradio_client/cli/deploy_discord.py
@@ -0,0 +1,58 @@
+import argparse
+
+from gradio_client import Client
+
+
+def main():
+ parser = argparse.ArgumentParser(description="Deploy Space as Discord Bot.")
+ parser.add_argument("deploy-discord")
+ parser.add_argument(
+ "--src",
+ type=str,
+ help="The space id or url or gradio app you want to deploy as a gradio bot.",
+ )
+ parser.add_argument(
+ "--discord-bot-token",
+ type=str,
+ help="Discord bot token. Get one on the discord website.",
+ )
+ parser.add_argument(
+ "--api-names",
+ nargs="*",
+ help="Api names to turn into discord bots",
+ default=[],
+ )
+ parser.add_argument(
+ "--to-id",
+ type=str,
+ help="Name of the space used to host the discord bot",
+ default=None,
+ )
+ parser.add_argument(
+ "--hf-token",
+ type=str,
+ help=(
+ "Hugging Face token. Can be ommitted if you are logged in via huggingface_hub cli. "
+ "Must be provided if upstream space is private."
+ ),
+ default=None,
+ )
+ parser.add_argument(
+ "--private",
+ type=bool,
+ nargs="?",
+ help="Whether the discord bot space is private.",
+ const=True,
+ default=False,
+ )
+ args = parser.parse_args()
+ for i, name in enumerate(args.api_names):
+ if "," in name:
+ args.api_names[i] = tuple(name.split(","))
+ Client(args.src).deploy_discord(
+ discord_bot_token=args.discord_bot_token,
+ api_names=args.api_names,
+ to_id=args.to_id,
+ hf_token=args.hf_token,
+ private=args.private,
+ )
diff --git a/client/python/gradio_client/client.py b/client/python/gradio_client/client.py
index 24b380c9fbced..c689ab6bc16e9 100644
--- a/client/python/gradio_client/client.py
+++ b/client/python/gradio_client/client.py
@@ -5,6 +5,7 @@
import json
import os
import re
+import secrets
import tempfile
import threading
import time
@@ -20,7 +21,7 @@
import huggingface_hub
import requests
import websockets
-from huggingface_hub import SpaceHardware, SpaceStage
+from huggingface_hub import CommitOperationAdd, SpaceHardware, SpaceStage
from huggingface_hub.utils import (
RepositoryNotFoundError,
build_hf_headers,
@@ -603,6 +604,147 @@ def _get_config(self) -> dict:
)
return config
+ def deploy_discord(
+ self,
+ discord_bot_token: str | None = None,
+ api_names: list[str | tuple[str, str]] | None = None,
+ to_id: str | None = None,
+ hf_token: str | None = None,
+ private: bool = False,
+ ):
+ """
+ Deploy the upstream app as a discord bot. Currently only supports gr.ChatInterface.
+ Parameters:
+ discord_bot_token: This is the "password" needed to be able to launch the bot. Users can get a token by creating a bot app on the discord website. If run the method without specifying a token, the space will explain how to get one. See here: https://huggingface.co/spaces/freddyaboulton/test-discord-bot-v1.
+ api_names: The api_names of the app to turn into bot commands. This parameter currently has no effect as ChatInterface only has one api_name ('/chat').
+ to_id: The name of the space hosting the discord bot. If None, the name will be gradio-discord-bot-{random-substring}
+ hf_token: HF api token with write priviledges in order to upload the files to HF space. Can be ommitted if logged in via the HuggingFace CLI, unless the upstream space is private. Obtain from: https://huggingface.co/settings/token
+ private: Whether the space hosting the discord bot is private. The visibility of the discord bot itself is set via the discord website. See https://huggingface.co/spaces/freddyaboulton/test-discord-bot-v1
+ """
+
+ if self.config["mode"] == "chat_interface" and not api_names:
+ api_names = [("chat", "chat")]
+
+ valid_list = isinstance(api_names, list) and (
+ isinstance(n, str)
+ or (
+ isinstance(n, tuple) and isinstance(n[0], str) and isinstance(n[1], str)
+ )
+ for n in api_names
+ )
+ if api_names is None or not valid_list:
+ raise ValueError(
+ f"Each entry in api_names must be either a string or a tuple of strings. Received {api_names}"
+ )
+ assert (
+ len(api_names) == 1
+ ), "Currently only one api_name can be deployed to discord."
+
+ for i, name in enumerate(api_names):
+ if isinstance(name, str):
+ api_names[i] = (name, name)
+
+ fn = next(
+ (ep for ep in self.endpoints if ep.api_name == f"/{api_names[0][0]}"), None
+ )
+ if not fn:
+ raise ValueError(
+ f"api_name {api_names[0][0]} not present in {self.space_id or self.src}"
+ )
+ inputs = [
+ inp for inp in fn.input_component_types if fn not in utils.SKIP_COMPONENTS
+ ]
+ outputs = [
+ inp for inp in fn.input_component_types if fn not in utils.SKIP_COMPONENTS
+ ]
+ if not inputs == ["textbox"] and outputs == ["textbox"]:
+ raise ValueError(
+ "Currently only api_names with a single textbox as input and output are supported. "
+ f"Received {inputs} and {outputs}"
+ )
+
+ is_private = False
+ if self.space_id:
+ is_private = huggingface_hub.space_info(self.space_id).private
+ if is_private:
+ assert hf_token, (
+ f"Since {self.space_id} is private, you must explicitly pass in hf_token "
+ "so that it can be added as a secret in the discord bot space."
+ )
+
+ if to_id:
+ if "/" in to_id:
+ to_id = to_id.split("/")[1]
+ space_id = huggingface_hub.get_full_repo_name(to_id, token=hf_token)
+ else:
+ if self.space_id:
+ space_id = f'{self.space_id.split("/")[1]}-gradio-discord-bot'
+ else:
+ space_id = f"gradio-discord-bot-{secrets.token_hex(4)}"
+ space_id = huggingface_hub.get_full_repo_name(space_id, token=hf_token)
+
+ api = huggingface_hub.HfApi()
+
+ try:
+ huggingface_hub.space_info(space_id)
+ first_upload = False
+ except huggingface_hub.utils.RepositoryNotFoundError:
+ first_upload = True
+
+ huggingface_hub.create_repo(
+ space_id,
+ repo_type="space",
+ space_sdk="gradio",
+ token=hf_token,
+ exist_ok=True,
+ private=private,
+ )
+ if first_upload:
+ huggingface_hub.metadata_update(
+ repo_id=space_id,
+ repo_type="space",
+ metadata={"tags": ["gradio-discord-bot"]},
+ )
+
+ with open(str(Path(__file__).parent / "templates" / "discord_chat.py")) as f:
+ app = f.read()
+ app = app.replace("<>", self.src)
+ app = app.replace("<>", api_names[0][0])
+ app = app.replace("<>", api_names[0][1])
+
+ with tempfile.NamedTemporaryFile(mode="w", delete=False) as app_file:
+ with tempfile.NamedTemporaryFile(mode="w", delete=False) as requirements:
+ app_file.write(app)
+ requirements.write("\n".join(["discord.py==2.3.1"]))
+
+ operations = [
+ CommitOperationAdd(path_in_repo="app.py", path_or_fileobj=app_file.name),
+ CommitOperationAdd(
+ path_in_repo="requirements.txt", path_or_fileobj=requirements.name
+ ),
+ ]
+
+ api.create_commit(
+ repo_id=space_id,
+ commit_message="Deploy Discord Bot",
+ repo_type="space",
+ operations=operations,
+ token=hf_token,
+ )
+
+ if discord_bot_token:
+ huggingface_hub.add_space_secret(
+ space_id, "DISCORD_TOKEN", discord_bot_token, token=hf_token
+ )
+ if is_private:
+ huggingface_hub.add_space_secret(
+ space_id, "HF_TOKEN", hf_token, token=hf_token
+ )
+
+ url = f"https://huggingface.co/spaces/{space_id}"
+ print(f"See your discord bot here! {url}")
+ return url
+
class Endpoint:
"""Helper class for storing all the information about a single API endpoint."""
diff --git a/client/python/gradio_client/templates/discord_chat.py b/client/python/gradio_client/templates/discord_chat.py
new file mode 100644
index 0000000000000..881ff65fb5e5b
--- /dev/null
+++ b/client/python/gradio_client/templates/discord_chat.py
@@ -0,0 +1,193 @@
+import asyncio
+import os
+import threading
+from threading import Event
+from typing import Optional
+
+import discord
+import gradio as gr
+from discord import Permissions
+from discord.ext import commands
+from discord.utils import oauth_url
+
+import gradio_client as grc
+from gradio_client.utils import QueueError
+
+event = Event()
+
+DISCORD_TOKEN = os.getenv("DISCORD_TOKEN")
+
+
+async def wait(job):
+ while not job.done():
+ await asyncio.sleep(0.2)
+
+
+def get_client(session: Optional[str] = None) -> grc.Client:
+ client = grc.Client("<>", hf_token=os.getenv("HF_TOKEN"))
+ if session:
+ client.session_hash = session
+ return client
+
+
+def truncate_response(response: str) -> str:
+ ending = "...\nTruncating response to 2000 characters due to discord api limits."
+ if len(response) > 2000:
+ return response[: 2000 - len(ending)] + ending
+ else:
+ return response
+
+
+intents = discord.Intents.default()
+intents.message_content = True
+bot = commands.Bot(command_prefix="/", intents=intents)
+
+
+@bot.event
+async def on_ready():
+ print(f"Logged in as {bot.user} (ID: {bot.user.id})")
+ synced = await bot.tree.sync()
+ print(f"Synced commands: {', '.join([s.name for s in synced])}.")
+ event.set()
+ print("------")
+
+
+thread_to_client = {}
+thread_to_user = {}
+
+
+@bot.hybrid_command(
+ name="<>",
+ description="Enter some text to chat with the bot! Like this: /<> Hello, how are you?",
+)
+async def chat(ctx, prompt: str):
+ if ctx.author.id == bot.user.id:
+ return
+ try:
+ message = await ctx.send("Creating thread...")
+
+ thread = await message.create_thread(name=prompt)
+ loop = asyncio.get_running_loop()
+ client = await loop.run_in_executor(None, get_client, None)
+ job = client.submit(prompt, api_name="/<>")
+ await wait(job)
+
+ try:
+ job.result()
+ response = job.outputs()[-1]
+ await thread.send(truncate_response(response))
+ thread_to_client[thread.id] = client
+ thread_to_user[thread.id] = ctx.author.id
+ except QueueError:
+ await thread.send(
+ "The gradio space powering this bot is really busy! Please try again later!"
+ )
+
+ except Exception as e:
+ print(f"{e}")
+
+
+async def continue_chat(message):
+ """Continues a given conversation based on chathistory"""
+ try:
+ client = thread_to_client[message.channel.id]
+ prompt = message.content
+ job = client.submit(prompt, api_name="/<>")
+ await wait(job)
+ try:
+ job.result()
+ response = job.outputs()[-1]
+ await message.reply(truncate_response(response))
+ except QueueError:
+ await message.reply(
+ "The gradio space powering this bot is really busy! Please try again later!"
+ )
+
+ except Exception as e:
+ print(f"Error: {e}")
+
+
+@bot.event
+async def on_message(message):
+ """Continue the chat"""
+ try:
+ if not message.author.bot:
+ if message.channel.id in thread_to_user:
+ if thread_to_user[message.channel.id] == message.author.id:
+ await continue_chat(message)
+ else:
+ await bot.process_commands(message)
+
+ except Exception as e:
+ print(f"Error: {e}")
+
+
+# running in thread
+def run_bot():
+ if not DISCORD_TOKEN:
+ print("DISCORD_TOKEN NOT SET")
+ event.set()
+ else:
+ bot.run(DISCORD_TOKEN)
+
+
+threading.Thread(target=run_bot).start()
+
+event.wait()
+
+if not DISCORD_TOKEN:
+ welcome_message = """
+
+ ## You have not specified a DISCORD_TOKEN, which means you have not created a bot account. Please follow these steps:
+
+ ### 1. Go to https://discord.com/developers/applications and click 'New Application'
+
+ ### 2. Give your bot a name 🤖
+
+ ![](https://gradio-builds.s3.amazonaws.com/demo-files/discordbots/BotName.png)
+
+ ## 3. In Settings > Bot, click the 'Reset Token' button to get a new token. Write it down and keep it safe 🔐
+
+ ![](https://gradio-builds.s3.amazonaws.com/demo-files/discordbots/ResetToken.png)
+
+ ## 4. Optionally make the bot public if you want anyone to be able to add it to their servers
+
+ ## 5. Scroll down and enable 'Message Content Intent' under 'Priviledged Gateway Intents'
+
+ ![](https://gradio-builds.s3.amazonaws.com/demo-files/discordbots/MessageContentIntent.png)
+
+ ## 6. Save your changes!
+
+ ## 7. The token from step 3 is the DISCORD_TOKEN. Rerun the deploy_discord command, e.g client.deploy_discord(discord_bot_token=DISCORD_TOKEN, ...), or add the token as a space secret manually.
+"""
+else:
+ permissions = Permissions(326417525824)
+ url = oauth_url(bot.user.id, permissions=permissions)
+ welcome_message = f"""
+ ## Add this bot to your server by clicking this link:
+
+ {url}
+
+ ## How to use it?
+
+ The bot can be triggered via `/<>` followed by your text prompt.
+
+ This will create a thread with the bot's response to your text prompt.
+ You can reply in the thread (without `/<>`) to continue the conversation.
+ In the thread, the bot will only reply to the original author of the command.
+
+ ⚠️ Note ⚠️: Please make sure this bot's command does have the same name as another command in your server.
+
+ ⚠️ Note ⚠️: Bot commands do not work in DMs with the bot as of now.
+ """
+
+
+with gr.Blocks() as demo:
+ gr.Markdown(
+ f"""
+ # Discord bot of <>
+ {welcome_message}
+ """
+ )
+
+demo.launch()
diff --git a/client/python/gradio_client/version.txt b/client/python/gradio_client/version.txt
index 13dead7ebf1a3..0d91a54c7d439 100644
--- a/client/python/gradio_client/version.txt
+++ b/client/python/gradio_client/version.txt
@@ -1 +1 @@
-0.2.10
+0.3.0
diff --git a/gradio/cli.py b/gradio/cli.py
index 04b099eacb754..b521bbf0a3cb5 100644
--- a/gradio/cli.py
+++ b/gradio/cli.py
@@ -1,5 +1,7 @@
import sys
+from gradio_client.cli import deploy_discord # type: ignore
+
import gradio.cli_env_info
import gradio.deploy_space
import gradio.reload
@@ -13,5 +15,7 @@ def cli():
gradio.deploy_space.deploy()
elif args[0] == "environment":
gradio.cli_env_info.print_environment_info()
+ elif args[0] == "deploy-discord":
+ deploy_discord.main()
else:
gradio.reload.main()
diff --git a/guides/04_chatbots/03_creating-a-discord-bot-from-a-gradio-app.md b/guides/04_chatbots/03_creating-a-discord-bot-from-a-gradio-app.md
new file mode 100644
index 0000000000000..14c511167e7c3
--- /dev/null
+++ b/guides/04_chatbots/03_creating-a-discord-bot-from-a-gradio-app.md
@@ -0,0 +1,131 @@
+# 🚀 Creating Discord Bots from Gradio Apps 🚀
+
+Tags: NLP, TEXT, CHAT
+
+We're excited to announce that Gradio can now automatically create a discord bot from a deployed app! 🤖
+
+Discord is a popular communication platform that allows users to chat and interact with each other in real-time. By turning your Gradio app into a Discord bot, you can bring cutting edge AI to your discord server and give your community a whole new way to interact.
+
+## 💻 How does it work? 💻
+
+With `gradio_client` version `0.3.0`, any gradio `ChatInterface` app on the internet can automatically be deployed as a discord bot via the `deploy_discord` method of the `Client` class.
+
+Technically, any gradio app that exposes an api route that takes in a single string and outputs a single string can be deployed to discord. In this guide, we will focus on `gr.ChatInterface` as those apps naturally lend themselves to discord's chat functionality.
+
+## 🛠️ Requirements 🛠️
+
+Make sure you have the latest `gradio_client` and `gradio` versions installed.
+
+```bash
+pip install gradio_client>=0.3.0 gradio>=3.38.0
+```
+
+Also, make sure you have a [Hugging Face account](https://huggingface.co/) and a [write access token](https://huggingface.co/docs/hub/security-tokens).
+
+⚠️ Tip ⚠️: Make sure you login to the Hugging Face Hub by running `huggingface-cli login`. This will let you skip passing your token in all subsequent commands in this guide.
+
+## 🏃♀️ Quickstart 🏃♀️
+
+### Step 1: Implementing our chatbot
+Let's build a very simple Chatbot using `ChatInterface` that simply repeats the user message. Write the following code into an `app.py`
+
+```python
+import gradio as gr
+
+def slow_echo(message, history):
+ return message
+
+demo = gr.ChatInterface(slow_echo).queue().launch()
+```
+
+### Step 2: Deploying our App
+In order to create a discord bot for our app, it must be accessible over the internet. In this guide, we will use the `gradio deploy` command to deploy our chatbot to Hugging Face spaces from the command line. Run the following command.
+
+```bash
+gradio deploy --title echo-chatbot --app-file app.py
+```
+
+This command will ask you some questions, e.g. requested hardware, requirements, but the default values will suffice for this guide.
+Note the URL of the space that was created. Mine is https://huggingface.co/spaces/freddyaboulton/echo-chatbot
+
+### Step 3: Creating a Discord Bot
+Turning our space into a discord bot is also a one-liner thanks to the `gradio deploy-discord`. Run the following command:
+
+```bash
+gradio deploy-discord --src freddyaboulton/echo-chatbot
+```
+
+❗️ Advanced ❗️: If you already have a discord bot token you can pass it to the `deploy-discord` command. Don't worry, if you don't have one yet!
+
+```bash
+gradio deploy-discord --src freddyaboulton/echo-chatbot --discord-bot-token
+```
+
+Note the URL that gets printed out to the console. Mine is https://huggingface.co/spaces/freddyaboulton/echo-chatbot-gradio-discord-bot
+
+### Step 4: Getting a Discord Bot Token
+If you didn't have a discord bot token for step 3, go to the URL that got printed in the console and follow the instructions there.
+Once you obtain a token, run the command again but this time pass in the token:
+
+```bash
+gradio deploy-discord --src freddyaboulton/echo-chatbot --discord-bot-token
+```
+
+### Step 5: Add the bot to your server
+Visit the space of your discord bot. You should see "Add this bot to your server by clicking this link:" followed by a URL. Go to that URL and add the bot to your server!
+
+### Step 6: Use your bot!
+By default the bot can be called by starting a message with `/chat`, e.g. `/chat `.
+
+⚠️ Tip ⚠️: If either of the deployed spaces goes to sleep, the bot will stop working. By default, spaces go to sleep after 48 hours of inactivity. You can upgrade the hardware of your space to prevent it from going to sleep. See this [guide](https://huggingface.co/docs/hub/spaces-gpus#using-gpu-spaces) for more information.
+
+
+
+
+### Using the `gradio_client.Client` Class
+You can also create a discord bot from a deployed gradio app with python.
+
+```python
+import gradio_client as grc
+grc.Client("freddyaboulton/echo-chatbot").deploy_discord()
+```
+
+## 🦾 Using State of The Art LLMs 🦾
+
+We have created an organization on Hugging Face called [gradio-discord-bots](https://huggingface.co/gradio-discord-bots) containing several template spaces that explain how to deploy state of the art LLMs powered by gradio as discord bots.
+
+The easiest way to get started is by deploying Meta's Llama 2 LLM with 70 billion parameter. Simply go to this [space](https://huggingface.co/spaces/gradio-discord-bots/Llama-2-70b-chat-hf) and follow the instructions.
+
+The deployment can be done in one line! 🤯
+
+```python
+import gradio_client as grc
+grc.Client("ysharma/Explore_llamav2_with_TGI").deploy_discord(to_id="llama2-70b-discord-bot")
+```
+
+## 🦜 Additional LLMs 🦜
+
+In addion to Meta's 70 billion Llama 2 model, we have prepared template spaces for the following LLMs and deployment options:
+
+* [gpt-3.5-turbo](https://huggingface.co/spaces/gradio-discord-bots/gpt-35-turbo), powered by openai. Required OpenAI key.
+* [falcon-7b-instruct](https://huggingface.co/spaces/gradio-discord-bots/falcon-7b-instruct) powered by Hugging Face Inference Endpoints.
+* [Llama-2-13b-chat-hf](https://huggingface.co/spaces/gradio-discord-bots/Llama-2-13b-chat-hf) powered by Hugging Face Inference Endpoints.
+* [Llama-2-13b-chat-hf](https://huggingface.co/spaces/gradio-discord-bots/llama-2-13b-chat-transformers) powered by Hugging Face transformers.
+
+To deploy any of these models to discord, simply follow the instructions in the linked space for that model.
+
+## Deploying non-chat gradio apps to discord
+
+As mentioned above, you don't need a `gr.ChatInterface` if you want to deploy your gradio app to discord. All that's needed is an api route that takes in a single string and outputs a single string.
+
+The following code will deploy a space that translates english to german as a discord bot.
+
+```python
+import gradio_client as grc
+client = grc.Client("freddyaboulton/english-to-german")
+client.deploy_discord(api_names=['german'])
+```
+
+## Conclusion
+
+That's it for this guide! We're really excited about this feature. Tag [@Gradio](https://twitter.com/Gradio) on twitter and show us how your discord community interacts with your discord bots.
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
index ba3de6c9dda43..faa08fe030bc8 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -3,7 +3,7 @@ aiohttp~=3.0
altair>=4.2.0,<6.0
fastapi
ffmpy
-gradio_client>=0.2.10
+gradio_client>=0.2.9
httpx
huggingface_hub>=0.14.0
Jinja2<4.0