Explorez Hugging Face pour utiliser les modèles NLP open source : API Inference côté back-end et transformers.js pour faire tourner les modèles dans le navigateur.
L'écosystème Hugging Face
Hugging Face est la plateforme de référence pour les modèles de machine learning open source. Elle héberge plus de 700 000 modèles NLP, vision, audio et multimodaux — accessibles via API cloud, dans le navigateur avec Transformers.js, ou localement.
| Composant | Description | Usage JS/TS |
|---|---|---|
| Model Hub | 500k+ modèles pré-entraînés (BERT, Mistral, Whisper…) | Parcourir et télécharger |
| Inference API | API REST cloud — appel sans hébergement | fetch() ou @huggingface/inference |
| Transformers.js | Inférence WebAssembly/ONNX dans le navigateur | @xenova/transformers |
| Inference Endpoints | Déploiement serverless dédié, auto-scaling | REST API sur URL custom |
| Spaces | Démos Gradio/Streamlit hébergées | Embed ou API |
| Datasets | 200k+ datasets pour l'entraînement | Download via CLI/API |
API Inference hébergée — client avec retry
L'API Inference permet d'appeler n'importe quel modèle HF sans infrastructure. Clé gratuite sur huggingface.co/settings/tokens. Limite : ~1000 requêtes/jour gratuit. Pour la production : Inference Endpoints (payant).
// hf-client.ts — Client typé avec retry et warm-up
const HF_API_URL = 'https://api-inference.huggingface.co/models';
interface HFOptions {
waitForModel?: boolean; // attendre que le modèle charge (cold start)
useCache?: boolean; // utiliser le cache HF (défaut: true)
}
async function hfInference<T>(
model: string,
inputs: string | object,
options: HFOptions = {},
maxRetries = 3
): Promise<T> {
const token = process.env.HF_API_TOKEN; // jamais côté client
for (let attempt = 0; attempt <= maxRetries; attempt++) {
const response = await fetch(`${HF_API_URL}/${model}`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
'X-Wait-For-Model': options.waitForModel ? 'true' : 'false',
'X-Use-Cache': options.useCache === false ? 'false' : 'true',
},
body: JSON.stringify({ inputs }),
});
if (response.status === 503) {
// Modèle en cours de chargement (cold start ~20-30s)
const { estimated_time } = await response.json();
if (attempt < maxRetries) {
const waitMs = (estimated_time || 20) * 1000;
console.log(`Modèle en chargement — attente ${waitMs/1000}s...`);
await new Promise(r => setTimeout(r, waitMs));
continue;
}
}
if (response.status === 429) {
// Rate limit — backoff exponentiel
const delay = Math.pow(2, attempt) * 2000;
await new Promise(r => setTimeout(r, delay));
continue;
}
if (!response.ok) {
const error = await response.json();
throw new Error(`HF API Error ${response.status}: ${error.error}`);
}
return response.json() as Promise<T>;
}
throw new Error('Max retries atteint');
}
// Analyse de sentiment avec le SDK officiel
import { HfInference } from '@huggingface/inference'; // npm install @huggingface/inference
const hf = new HfInference(process.env.HF_API_TOKEN);
// Classification de texte
const sentiment = await hf.textClassification({
model: 'cardiffnlp/twitter-roberta-base-sentiment-latest',
inputs: 'Angular 17 est incroyable pour les SPAs !',
});
// [{ label: 'positive', score: 0.96 }]
// Génération de texte
const generated = await hf.textGeneration({
model: 'mistralai/Mistral-7B-Instruct-v0.2',
inputs: '<s>[INST] Explique les Signals Angular en 3 points [/INST]',
parameters: { max_new_tokens: 300, temperature: 0.3 }
});
Transformers.js — inférence dans le navigateur
@xenova/transformers (maintenant @huggingface/transformers) exécute les modèles ONNX directement dans le navigateur via WebAssembly — zéro dépendance serveur, 100% privé.
npm install @xenova/transformers
# ou version officielle HF (2024) :
npm install @huggingface/transformers
// sentiment.service.ts — Service Angular avec transformers.js
import { Injectable, signal } from '@angular/core';
import { pipeline, Pipeline, env } from '@xenova/transformers';
// Configurer le cache des modèles
env.allowLocalModels = false; // utiliser HF Hub uniquement
env.useBrowserCache = true; // mise en cache IndexedDB
@Injectable({ providedIn: 'root' })
export class SentimentService {
private pipelineInstance: Pipeline | null = null;
readonly loading = signal(false);
readonly modelLoaded = signal(false);
readonly progress = signal(0);
async loadModel(): Promise<void> {
if (this.pipelineInstance) return; // déjà chargé
this.loading.set(true);
try {
// Callback de progression pour l'UX
const progressCallback = (data: { progress?: number; status: string }) => {
if (data.progress) {
this.progress.set(Math.round(data.progress));
}
};
// Modèle quantisé — 4× plus léger (82 Mo → 23 Mo)
this.pipelineInstance = await pipeline(
'sentiment-analysis',
'Xenova/distilbert-base-uncased-finetuned-sst-2-english',
{ quantized: true, progress_callback: progressCallback }
);
this.modelLoaded.set(true);
} finally {
this.loading.set(false);
this.progress.set(100);
}
}
async analyze(text: string): Promise<{ label: string; score: number }[]> {
if (!this.pipelineInstance) await this.loadModel();
return this.pipelineInstance!(text) as any;
}
}
Tâches NLP — guide complet des pipelines
import { pipeline } from '@xenova/transformers';
// 1. Classification de texte / sentiment
const classifier = await pipeline(
'text-classification',
'Xenova/distilbert-base-uncased-finetuned-sst-2-english'
);
const result = await classifier('Angular Signals are amazing!');
// [{ label: 'POSITIVE', score: 0.9998 }]
// 2. Question Answering — extraction de réponse depuis un contexte
const qa = await pipeline('question-answering');
const answer = await qa({
question: 'Qui a créé Angular ?',
context: 'Angular est un framework TypeScript open-source créé par Google en 2016.'
});
// { answer: 'Google', start: 62, end: 68, score: 0.9987 }
// 3. Résumé automatique
const summarizer = await pipeline('summarization', 'Xenova/distilbart-cnn-6-6');
const summary = await summarizer(longText, { max_new_tokens: 150, min_new_tokens: 40 });
// [{ summary_text: '...' }]
// 4. Traduction multi-langues
const translator = await pipeline('translation', 'Xenova/opus-mt-fr-en');
const translated = await translator('Bonjour le monde !', { tgt_lang: 'en' });
// [{ translation_text: 'Hello World!' }]
// 5. Génération de texte (GPT-style)
const generator = await pipeline('text-generation', 'Xenova/gpt2');
const generated = await generator('Angular est un framework', {
max_new_tokens: 100,
temperature: 0.7,
do_sample: true,
repetition_penalty: 1.2
});
// 6. Reconnaissance d'entités nommées (NER)
const ner = await pipeline('token-classification', 'Xenova/bert-base-NER');
const entities = await ner('Apple a été fondée par Steve Jobs à Cupertino.');
// [{ entity: 'ORG', word: 'Apple', score: 0.99, ... }]
// 7. Transcription audio (Whisper)
const transcriber = await pipeline('automatic-speech-recognition', 'Xenova/whisper-tiny');
const transcription = await transcriber(audioArray);
// { text: '...' }
// 8. Génération d'embeddings (pour RAG)
const embedder = await pipeline('feature-extraction', 'Xenova/all-MiniLM-L6-v2');
const embeddings = await embedder('Angular Signals', { pooling: 'mean', normalize: true });
// Float32Array[384] — vecteur d'embedding
Modèles recommandés par tâche
| Tâche | Modèle Transformers.js | Modèle API Inference |
|---|---|---|
| Sentiment (EN) | Xenova/distilbert-base-uncased-finetuned-sst-2-english | cardiffnlp/twitter-roberta-base-sentiment-latest |
| Sentiment (FR) | Xenova/camembert-base-sentiment | cmarkea/distilcamembert-base-sentiment |
| Résumé (EN) | Xenova/distilbart-cnn-6-6 | facebook/bart-large-cnn |
| Traduction FR→EN | Xenova/opus-mt-fr-en | Helsinki-NLP/opus-mt-fr-en |
| QA (FR) | — | etalab-ia/camembert-base-squadFR-fquad-piaf |
| Génération code | Xenova/codegen-350M-mono | bigcode/starcoder2-3b |
| Transcription audio | Xenova/whisper-tiny | openai/whisper-large-v3 |
| Embeddings | Xenova/all-MiniLM-L6-v2 | sentence-transformers/all-mpnet-base-v2 |
Intégration Angular avec Web Worker
L'inférence avec Transformers.js est CPU-intensive — toujours l'exécuter dans un Web Worker pour ne pas bloquer l'UI.
// src/app/workers/nlp.worker.ts — Inférence hors thread principal
/// <reference lib="webworker" />
import { pipeline, env } from '@xenova/transformers';
env.allowLocalModels = false;
env.useBrowserCache = true;
// Cache des pipelines par tâche (ne pas recharger à chaque appel)
const pipelineCache = new Map<string, any>();
async function getPipeline(task: string, model: string) {
const key = `${task}:${model}`;
if (!pipelineCache.has(key)) {
const pipe = await pipeline(task, model, {
quantized: true,
progress_callback: (data: any) => {
// Envoyer la progression au thread principal
self.postMessage({ type: 'progress', progress: data.progress });
}
});
pipelineCache.set(key, pipe);
}
return pipelineCache.get(key);
}
self.onmessage = async ({ data }) => {
const { id, task, model, inputs, options = {} } = data;
try {
const pipe = await getPipeline(task, model);
const result = await pipe(inputs, options);
self.postMessage({ type: 'result', id, result });
} catch (error: any) {
self.postMessage({ type: 'error', id, error: error.message });
}
};
// nlp-worker.service.ts — Service Angular pour communiquer avec le worker
import { Injectable, NgZone } from '@angular/core';
import { Observable } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class NlpWorkerService {
private worker = new Worker(
new URL('./workers/nlp.worker', import.meta.url),
{ type: 'module' }
);
private pendingRequests = new Map<string, (value: any) => void>();
readonly progressUpdates = new Subject<number>();
constructor(private zone: NgZone) {
this.worker.onmessage = ({ data }) => {
this.zone.run(() => {
if (data.type === 'progress') {
this.progressUpdates.next(data.progress);
} else if (data.type === 'result') {
const resolve = this.pendingRequests.get(data.id);
resolve?.(data.result);
this.pendingRequests.delete(data.id);
}
});
};
}
run(task: string, model: string, inputs: any, options = {}): Promise<any> {
return new Promise((resolve, reject) => {
const id = crypto.randomUUID();
this.pendingRequests.set(id, resolve);
this.worker.postMessage({ id, task, model, inputs, options });
});
}
}
Déployer sur Inference Endpoints
Pour la production, Hugging Face Inference Endpoints offre un déploiement serverless auto-scaling — évite le cold start de l'API gratuite.
// Appel à un Inference Endpoint dédié
// URL format: https://api-inference.huggingface.co/models/{model_id}
// ou endpoint custom: https://{endpoint-id}.{region}.aws.endpoints.huggingface.cloud
async function callEndpoint(endpointUrl: string, inputs: any) {
const response = await fetch(endpointUrl, {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.HF_API_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ inputs }),
});
if (!response.ok) throw new Error(`Endpoint error: ${response.status}`);
return response.json();
}
// Pour la classification en batch — 10× plus efficace que les appels individuels
const batchResults = await callEndpoint(MY_ENDPOINT_URL, [
'Premier texte à analyser',
'Deuxième texte à analyser',
'Troisième texte à analyser',
// ... jusqu'à 32 textes par batch
]);
Bonnes pratiques et optimisation
- Singleton par pipeline : charger une seule fois, réutiliser — le chargement prend 5-30 secondes selon le modèle et la connexion
- Web Worker obligatoire pour Transformers.js — l'inférence CPU-intensive bloque l'UI thread principal
- Modèles quantisés (
quantized: true) pour le navigateur — 4× plus légers, qualité quasi identique - Progress callback pour informer l'utilisateur pendant le téléchargement du modèle
- Cache IndexedDB (
env.useBrowserCache = true) — évite de re-télécharger à chaque session - Retry avec backoff sur les erreurs 503 (cold start) — attendre
estimated_timesecondes avant de réessayer - Batch processing — passer plusieurs textes en tableau pour réduire les appels API
- Jamais d'appel API Inference directement depuis le navigateur — clé API exposée dans le JS