ADR 0094 – Service de transcription vocale (dictée souveraine)
Accepté (2026-10-17, dans le cadre de l'ADR 0086 §3 validé par Aurélien)
ADR 0094 – Service de transcription vocale (dictée souveraine)
Statut : Accepté (2026-10-17, dans le cadre de l’ADR 0086 §3 validé par Aurélien)
Ligne C1 du fil C du backlog du jalon 4. Le bouton « Dicter » (C2) et son intégration dans la capture (C4) viennent après ; cet ADR ne fixe que le service et l’API.
Décision
- Service compose
transcription: imagefedirz/faster-whisper-server(variante0.5.0-cpu, surchargeable parSB_TRANSCRIPTION_IMAGE), modèleSystran/faster-whisper-small(SB_TRANSCRIPTION_MODEL),int8sur CPU. Profil composevoice: absent des lancements habituels, activé parCOMPOSE_PROFILES=voice(ou--profile voice). Aucun port publié (expose: 8000) : seul le backend l’appelle, sur le réseau du compose. Le modèle (environ 500 Mo) se télécharge à la première dictée et reste dans le volumetranscription-cache. Présent dansdocker-compose.yml,docker-compose.prod.ymletdeploy/dokploy/docker-compose.yml. - Réglages :
app.transcription.url←SB_TRANSCRIPTION_URL(vide ou absent = fonction inactive),timeout(60 s,SB_TRANSCRIPTION_TIMEOUT),max-size(10 Mo),model,language(fr). Le backend dit où trouver le service ; rien d’autre n’est requis. POST /api/capture/transcribe: multipart, partiefile, formatswebm,ogg,mp4(type annoncé, paramètres;codecs=admis, puis contrôle de la signature du contenu : EBML,OggS, boîteftyp). Réponse{ text }. Le serveur relaie à{url}/v1/audio/transcriptions(API OpenAI,response_format=json,language=fr) ; l’audio n’est ni stocké ni journalisé.- 503 si inactif ; 400 fichier absent ou vide ; 413 au-delà de 10 Mo ; 415 format ou signature refusés ; 502 service en échec ou réponse inattendue ; 504 délai dépassé. Erreurs au format ProblemDetail, comme le reste de l’API.
GET /api/capture/capabilities→{ transcription: boolean }, extensible : le front y lit s’il affiche le micro.- Durée : les deux minutes ne sont pas mesurées côté serveur (décoder l’audio demanderait une dépendance de plus). Elles sont bornées par la taille (10 Mo couvrent largement deux minutes d’opus à débit de voix), par le délai du relais, et par l’arrêt automatique de l’enregistreur côté navigateur (C2).
- Code dans un paquet à part,
capture/transcription/, sans toucherCaptureService,CaptureParserniCaptureDtos(fil B) ;CaptureControllerest laissé intact, les deux contrôleurs partagent le préfixe/api/capture.
Conséquences
- Sans service ni variable, l’application se comporte comme avant ;
capabilities.transcriptionvautfalse. - Tests : unitaires (formats, relais, erreurs) et d’intégration contre un faux service HTTP du JDK (
com.sun.net.httpserver), sans nouvelle dépendance de test. - Coût d’exploitation : environ 1 Go de mémoire et un cœur CPU pendant une dictée ; le service décharge le modèle après inactivité.
- À vérifier au premier déploiement : le nom de l’image et le tag (le projet amont a été renommé
speaches), et les variablesWHISPER__*. Le contrat reste l’API OpenAI : changer d’image (speaches, whisper.cpp serveur) ne touche pas le backend.
Alternatives écartées
- API de transcription en ligne (OpenAI, Google) : l’audio quitterait le serveur (ADR 0086).
- Transcription dans la JVM (whisper.cpp via JNI) : dépendance native lourde et fragile ; un service séparé est remplaçable.
- Mesurer la durée côté serveur : voir plus haut.