Aller au contenu principal

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.

  1. Ouvrez la connexion et attendez session.created.
  2. Sélectionnez le modèle avec {"type": "session.update", "model": "voxtral-mini-4b-realtime"}.
  3. Lancez le traitement avec {"type": "input_audio_buffer.commit"}.
  4. Envoyez les blocs avec {"type": "input_audio_buffer.append", "audio": "<PCM16 en Base64>"} et écoutez les réponses en parallèle.
  5. Signalez la fin de l’audio avec {"type": "input_audio_buffer.commit", "final": true}.
  6. Attendez transcription.done avant de fermer la connexion.

Événements reçus​

TypeContenu utileUtilisation
session.createdidLa connexion est établie.
transcription.deltadeltaAjoutez ce fragment au texte affiché.
transcription.donetext, éventuellement usageUtilisez text comme transcription finale.
errorerror, éventuellement codeInterrompez 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ômeVérification
Connexion refusée ou erreur d’authentificationVérifiez l’adresse de staging, la clé et les droits d’accès.
Modèle non reconnuVérifiez ClovisSTT/realtime dans le paramètre d’URL et voxtral-mini-4b-realtime dans session.update.
Audio invalide ou texte incohérentVérifiez le PCM16 mono à 16 kHz, l’encodage Base64 et l’absence d’en-tête WAV dans les blocs.
Aucune transcription finaleEnvoyez le commit initial, puis le commit avec final: true après le dernier bloc.
Texte incomplet dans l’interfaceConcaténez les delta, puis récupérez le champ text de transcription.done.