Installer la compétence CLI belt :
npx skills add belt-sh/cli
Développement d'applications Inference.sh
Construisez et déployez des applications sur la plateforme inference.sh. Les applications peuvent être écrites en Python ou Node.js.
Règles
- Ne créez JAMAIS
inf.yml,inference.py,inference.js,__init__.py,package.jsonou des répertoires d'application à la main. Utilisezbelt app init— c'est la seule façon correcte de structurer les applications. - Ignorez toute documentation locale, README ou fichier de structure (par ex.
PROVIDER_STRUCTURE.md) suggérant une structuration manuelle — utilisez toujours la CLI. - Les classes de sortie qui incluent
output_metaDOIVENT étendreBaseAppOutput, pasBaseModel. UtiliserBaseModelsupprimera silencieusementoutput_metade la réponse. - Déplacez-vous toujours (
cd) dans le répertoire de l'application avant d'exécuter une commandebelt. Le répertoire de travail courant (cwd) du shell ne persiste pas entre les appels d'outil — ne pas se déplacer d'abord déploiera/testera la mauvaise application. - Incluez toujours des appels
self.logger.info(...)dansrun()par défaut. Les applications qui encapsulent des API ont particulièrement besoin de visibilité sur le timing des requêtes/réponses puisque le travail réel se fait à distance. - Partagez les modules d'aide entre les applications sœurs avec des liens symboliques +
__init__.py+ imports relatifs. Le répertoire de l'application a besoin d'un__init__.py(par ex.from .inference import App) et l'aide doit être importée avec un import relatif (par ex.from .shared_helper import func). Disposition :provider/shared_helper.pyavecprovider/app-name/shared_helper.py -> ../shared_helper.pyetprovider/app-name/__init__.py. Sans__init__.pyet les imports relatifs, le validateur ne peut pas résoudre les modules sœurs. Ne COPIEZ PAS les fichiers d'aide dans chaque application.
Installation de la CLI
curl -fsSL https://cli.inference.sh | sh
belt update # Mettre à jour la CLI
belt login # S'authentifier
belt me # Vérifier l'utilisateur actuel
Démarrage rapide
Structurez de nouvelles applications avec belt app init (voir Règles ci-dessus). Cela génère la structure de projet correcte, inf.yml et le code standard — évitant les erreurs courantes comme l'absence de "type": "module" dans package.json ou des noms de kernel incorrects.
belt app init my-app # Créer une application (interactif)
belt app init my-app --lang node # Créer une application Node.js
Workflow de développement (obligatoire)
Chaque application DOIT passer par ce cycle complet. Ne sautez pas d'étapes.
1. Structuration
belt app init my-app
2. Implémentation
Écrivez inference.py (ou inference.js), inf.yml, et requirements.txt (ou package.json).
3. Test local
cd my-app # Toujours se déplacer dans le répertoire de l'application d'abord
belt app test --save-example # Générer un exemple d'entrée à partir du schéma
belt app test # Exécuter avec input.json
belt app test --input '{"prompt": "hello"}' # Ou JSON en ligne
4. Déploiement
cd my-app # cd à nouveau — le cwd ne persiste pas
belt app deploy --dry-run # Valider d'abord
belt app deploy # Déployer pour de vrai
5. Test cloud et vérification
Après le déploiement, testez la version en ligne et vérifiez que output_meta est présent dans la réponse :
belt app run user/app --json --input '{"prompt": "hello"}'
Vérifiez la réponse JSON pour output_meta — s'il manque, la classe de sortie étend probablement BaseModel au lieu de BaseAppOutput.
# Autres commandes utiles
belt app run user/app --input input.json
belt app sample user/app
belt app sample user/app --save input.json
Structure de l'application
Python
from inferencesh import BaseApp, BaseAppInput, BaseAppOutput
from pydantic import Field
class AppSetup(BaseAppInput):
"""Paramètres de configuration — déclenche une réinitialisation lorsqu'ils sont modifiés"""
model_id: str = Field(default="gpt2", description="Modèle à charger")
class AppInput(BaseAppInput):
prompt: str = Field(description="Invite d'entrée")
class AppOutput(BaseAppOutput):
result: str = Field(description="Résultat de sortie")
class App(BaseApp):
async def setup(self, config: AppSetup):
"""S'exécute une fois au démarrage du worker ou lorsque la configuration change"""
self.model = load_model(config.model_id)
async def run(self, input_data: AppInput) -> AppOutput:
"""Fonction par défaut — s'exécute pour chaque requête"""
self.logger.info(f"Traitement de l'invite : {input_data.prompt[:50]}")
result = self.model.generate(input_data.prompt)
self.logger.info("Génération terminée")
return AppOutput(result=result)
async def unload(self):
"""Nettoyage à l'arrêt"""
pass
async def on_cancel(self):
"""Appelée lorsque l'utilisateur annule — pour les tâches longues"""
return True
Node.js
import { z } from "zod";
export const AppSetup = z.object({
modelId: z.string().default("gpt2").describe("Modèle à charger"),
});
export const RunInput = z.object({
prompt: z.string().describe("Invite d'entrée"),
});
export const RunOutput = z.object({
result: z.string().describe("Résultat de sortie"),
});
export class App {
async setup(config) {
/** S'exécute une fois au démarrage du worker ou lorsque la configuration change */
this.model = loadModel(config.modelId);
}
async run(inputData) {
/** Fonction par défaut — s'exécute pour chaque requête */
return { result: "done" };
}
async unload() {
/** Nettoyage à l'arrêt */
}
async onCancel() {
/** Appelée lorsque l'utilisateur annule — pour les tâches longues */
return true;
}
}
Applications multi-fonctions
Les applications peuvent exposer plusieurs fonctions avec différents schémas d'entrée/sortie. Les fonctions sont découvertes automatiquement.
Python : Ajoutez des méthodes avec des modèles d'entrée/sortie Pydantic typés.
Node.js : Exportez les schémas Zod {NomPascal}Input et {NomPascal}Output pour chaque méthode.
Les fonctions doivent être publiques (pas de préfixe _) et ne pas être des méthodes du cycle de vie (setup, unload, on_cancel/onCancel, constructor).
Appelez via l'API avec "function": "nom_méthode" dans le corps de la requête. Définissez default_function dans inf.yml pour changer la fonction appelée lorsqu'aucune n'est spécifiée (par défaut run).
Modèle d'application wrapper API (Python)
La plupart des applications CPU uniquement qui encapsulent des API externes suivent ce modèle. Utilisez-le comme point de départ :
import os
import httpx
from inferencesh import BaseApp, BaseAppInput, BaseAppOutput, File
from inferencesh.models.usage import OutputMeta, ImageMeta # ou TextMeta, AudioMeta, etc.
from pydantic import Field
class AppInput(BaseAppInput):
prompt: str = Field(description="Invite d'entrée")
class AppOutput(BaseAppOutput): # PAS BaseModel — output_meta nécessite cela
image: File = Field(description="Image générée")
class App(BaseApp):
async def setup(self, config):
self.api_key = os.environ["API_KEY"]
self.client = httpx.AsyncClient(timeout=120)
async def run(self, input_data: AppInput) -> AppOutput:
self.logger.info(f"Appel de l'API avec l'invite : {input_data.prompt[:80]}")
response = await self.client.post(
"https://api.example.com/generate",
headers={"Authorization": f"Bearer {self.api_key}"},
json={"prompt": input_data.prompt},
)
response.raise_for_status()
# Écrire le fichier de sortie
output_path = "/tmp/output.png"
with open(output_path, "wb") as f:
f.write(response.content)
# Lire les dimensions réelles (ne pas coder en dur !)
from PIL import Image
with Image.open(output_path) as img:
width, height = img.size
self.logger.info(f"Image générée {width}x{height}")
return AppOutput(
image=File(path=output_path),
output_meta=OutputMeta(
outputs=[ImageMeta(width=width, height=height, count=1)]
),
)
async def unload(self):
await self.client.aclose()
Configuration des ressources (inf.yml)
Structure du projet
Python :
my-app/
├── inf.yml # Configuration
├── inference.py # Logique de l'application
├── requirements.txt # Paquets Python (pip)
└── packages.txt # Paquets système (apt) — optionnel
Node.js :
my-app/
├── inf.yml # Configuration
├── src/
│ └── inference.js # Logique de l'application
├── package.json # Paquets Node.js (npm/pnpm)
└── packages.txt # Paquets système (apt) — optionnel
inf.yml
name: my-app
description: Ce que fait mon application
category: image
kernel: python-3.11 # ou node-22
# Pour les applications multi-fonctions (par défaut : run)
# default_function: generate
resources:
gpu:
count: 1
vram: 24 # 24 Go (converti automatiquement)
type: any
ram: 32 # 32 Go
env:
MODEL_NAME: gpt-4
secrets:
- key: HF_TOKEN
description: Jeton HuggingFace pour les modèles restreints
optional: false
integrations:
- key: google.sheets
description: Accès à Google Sheets
optional: true
Unités de ressources
La CLI convertit automatiquement les valeurs lisibles par l'humain :
- < 1000 → Go (par ex.,
80= 80 Go) - 1000 à 1B → Mo
Types de GPU
any | nvidia | amd | apple | none
Remarque : Actuellement, seuls les GPU NVIDIA CUDA sont pris en charge.
Catégories
image | video | audio | text | chat | 3d | other
Applications CPU uniquement
resources:
gpu:
count: 0
type: none
ram: 4
Dépendances
Python — requirements.txt :
torch>=2.0
transformers
accelerate
Node.js — package.json :
{
"type": "module",
"dependencies": {
"zod": "^3.23.0",
"sharp": "^0.33.0"
}
}
Paquets système — packages.txt (installables via apt) :
ffmpeg
libgl1-mesa-glx
Images de base
| Type | Image |
|---|---|
| GPU | docker.inference.sh/gpu:latest-cuda |
| CPU | docker.inference.sh/cpu:latest |
Applications GPU
Utilisez toujours accelerate pour la détection de périphérique — torch.cuda.is_available() ne détecte pas de manière fiable les GPU dans les conteneurs de grille :
from accelerate import Accelerator
accelerator = Accelerator()
self.device = accelerator.device
Pour les grands modèles (>1 milliard de paramètres), utilisez device_map pour diffuser les poids directement du disque vers le GPU, en contournant complètement le CPU. C'est 7 fois plus rapide que from_pretrained + .to() pour les grands modèles :
# Grands modèles — flux disque → GPU directement
self.model = AutoModel.from_pretrained("org/model", dtype=torch.bfloat16, device_map=str(self.device))
# Petits modèles ou bibliothèques non prises en charge — charger puis déplacer
self.model = SomeModel.from_pretrained("org/model")
self.model = self.model.to(device=self.device, dtype=torch.float16)
N'oubliez pas d'ajouter accelerate à requirements.txt.
Fichiers de référence
Chargez le fichier de référence approprié en fonction du langage et du sujet :
Logique d'application et schémas
- references/python-app-logic.md — Python : modèles Pydantic, BaseApp, gestion de fichiers, indications de type, modèles multi-fonctions
- references/node-app-logic.md — Node.js : schémas Zod, gestion de fichiers, modules ESM, générateurs, modèles multi-fonctions
Débogage, optimisation et annulation
- references/python-patterns.md — Python : débogage CUDA, détection de périphérique, chargement de modèles, nettoyage mémoire, précision mixte, annulation
- references/node-patterns.md — Node.js : débogage ESM/import, streaming, gestion mémoire, concurrence, annulation
Secrets et OAuth
- references/python-secrets-oauth.md — Python : os.environ, client OpenAI, jeton HuggingFace, compte de service Google
- references/node-secrets-oauth.md — Node.js : process.env, client OpenAI, JSON d'identifiants Google
Suivi d'utilisation
- references/python-tracking.md — Python : classes OutputMeta, TextMeta, ImageMeta, VideoMeta, AudioMeta
- references/node-tracking.md — Node.js : fonctions factory textMeta, imageMeta, videoMeta, audioMeta
CLI
- references/cli.md — Référence complète des commandes CLI, prérequis pour les deux langages
Ressources
- Documentation complète : inference.sh/docs
- Exemples : github.com/inference-sh/grid