Transcription en temps réel avec /v1/realtime
Le modèle ClovisSTT/realtime transforme un flux audio en texte. Une connexion WebSocket transporte les blocs audio vers le serveur et les fragments de transcription vers votre application.
Point d’accès
wss://llm-gateway.clovis-ai.fr/v1/realtime?model=ClovisSTT%2Frealtime
Utilisez une clé API autorisée sur cette passerelle. Les résultats de tests présentés dans ce guide ont été obtenus en staging.
L’authentification utilise l’en-tête Authorization: Bearer <CLOVIS_API_KEY> lors de l’ouverture de la connexion. Gardez cette clé côté serveur : l’API WebSocket native d’un navigateur ne permet pas d’ajouter cet en-tête. Pour un client web, faites transiter la connexion par votre backend.
Deux noms à distinguer sur le staging actuel
Le paramètre d’URL model=ClovisSTT%2Frealtime sélectionne le modèle public ClovisSTT/realtime sur la passerelle. Sans ce paramètre, la connexion a été refusée avec un HTTP 403 lors du test.
Dans l’événement session.update, le backend attend actuellement voxtral-mini-4b-realtime. Lui envoyer le nom public renvoie model_not_found. Cette différence a été vérifiée sur le staging ; elle pourra évoluer si la passerelle prend en charge la traduction des noms dans les événements.
Préparer l’audio
Le protocole Realtime attend des blocs PCM16 mono à 16 000 Hz, encodés en Base64. Envoyez les échantillons audio bruts, sans en-tête WAV. Un fichier MP3, un conteneur WAV ou un enregistrement WebM doit donc être décodé et converti avant envoi.
Exemple de conversion avec FFmpeg :
ffmpeg -i enregistrement.mp3 -ac 1 -ar 16000 -c:a pcm_s16le audio.wav
Déroulement d’une session
Le guide ci-dessous suit le client Realtime officiel de vLLM. Il constitue une base d’intégration avec la version déployée sur votre gateway Clovis.
- Ouvrez la connexion et attendez
session.created. - Sélectionnez le modèle avec
{"type": "session.update", "model": "voxtral-mini-4b-realtime"}. - Lancez le traitement avec
{"type": "input_audio_buffer.commit"}. - Envoyez les blocs avec
{"type": "input_audio_buffer.append", "audio": "<PCM16 en Base64>"}et écoutez les réponses en parallèle. - Signalez la fin de l’audio avec
{"type": "input_audio_buffer.commit", "final": true}. - Attendez
transcription.doneavant de fermer la connexion.
Événements reçus
| Type | Contenu utile | Utilisation |
|---|---|---|
session.created | id | La connexion est établie. |
transcription.delta | delta | Ajoutez ce fragment au texte affiché. |
transcription.done | text, éventuellement usage | Utilisez text comme transcription finale. |
error | error, éventuellement code | Interrompez le traitement et examinez l’erreur. |
Exemple Python
Cet exemple lit un WAV PCM16 mono à 16 kHz, envoie des blocs de 100 ms et reçoit le texte simultanément. Il nécessite Python 3.11 ou une version ultérieure.
python -m pip install 'websockets>=14,<16'
export CLOVIS_API_KEY='votre-cle-api-staging'
python transcrire.py audio.wav
Enregistrez le code suivant dans transcrire.py :
import asyncio
import base64
import json
import os
import sys
import wave
from websockets.asyncio.client import connect
ENDPOINT = "wss://llm-gateway.clovis-ai.fr/v1/realtime?model=ClovisSTT%2Frealtime"
async def transcrire(path):
with wave.open(path, "rb") as audio:
if (
audio.getnchannels() != 1
or audio.getframerate() != 16000
or audio.getsampwidth() != 2
or audio.getcomptype() != "NONE"
):
raise ValueError("Le WAV doit être PCM16 mono à 16 kHz.")
if audio.getnframes() == 0:
raise ValueError("Le fichier audio est vide.")
async with connect(
ENDPOINT,
additional_headers={
"Authorization": f"Bearer {os.environ['CLOVIS_API_KEY']}"
},
open_timeout=15,
) as ws:
async def envoyer(event):
await ws.send(json.dumps(event))
initial = json.loads(await asyncio.wait_for(ws.recv(), timeout=15))
if initial.get("type") != "session.created":
raise RuntimeError(f"Session non créée : {initial}")
await envoyer({"type": "session.update", "model": "voxtral-mini-4b-realtime"})
await envoyer({"type": "input_audio_buffer.commit"})
async def envoyer_audio():
while bloc := audio.readframes(1600):
await envoyer({
"type": "input_audio_buffer.append",
"audio": base64.b64encode(bloc).decode("ascii"),
})
await asyncio.sleep(len(bloc) / (16000 * 2))
await envoyer({"type": "input_audio_buffer.commit", "final": True})
async def recevoir_texte():
while True:
event = json.loads(await asyncio.wait_for(ws.recv(), timeout=60))
if event.get("type") == "transcription.delta":
print(event["delta"], end="", flush=True)
elif event.get("type") == "transcription.done":
print("\n\nTranscription finale :", event["text"])
return
elif event.get("type") == "error":
raise RuntimeError(f"Erreur de transcription : {event}")
async with asyncio.TaskGroup() as tasks:
tasks.create_task(envoyer_audio())
tasks.create_task(recevoir_texte())
if __name__ == "__main__":
asyncio.run(transcrire(sys.argv[1]))
Le délai d’attente de 60 secondes entre événements est un choix du client d’exemple ; adaptez-le à votre application. Pour un microphone, remplacez la lecture du WAV par une capture qui produit le même format PCM16 et conservez la réception simultanée des événements.
Dépannage
| Symptôme | Vérification |
|---|---|
| Connexion refusée ou erreur d’authentification | Vérifiez l’adresse de staging, la clé et les droits d’accès. |
| Modèle non reconnu | Vérifiez ClovisSTT/realtime dans le paramètre d’URL et voxtral-mini-4b-realtime dans session.update. |
| Audio invalide ou texte incohérent | Vérifiez le PCM16 mono à 16 kHz, l’encodage Base64 et l’absence d’en-tête WAV dans les blocs. |
| Aucune transcription finale | Envoyez le commit initial, puis le commit avec final: true après le dernier bloc. |
| Texte incomplet dans l’interface | Concaténez les delta, puis récupérez le champ text de transcription.done. |