Lot 9 : dictée vocale tenue par la façade (LOKI_DICTATE=rust)

La façade sert /api/transcribe et /api/dictate/* quand LOKI_DICTATE=rust
(expérimental ; le Go reste le défaut) et supervise elle-même le serveur
de transcription sherpa-onnx : lancé à la demande sur la boucle locale,
GPU masqués et sans le jeton interne, prêt dès que sa socket répond, mort
au démarrage expliquée, éteint après dix minutes d'inactivité ou quand
les réglages changent. Modèles Parakeet téléchargés et décompressés au fil
de l'eau (tar.bz2, entrées hors du dossier refusées, liens ignorés),
protocole WebSocket identique (en-tête de 8 octets, trames de 10 Kio).

Côté Go, facade_dictate.go remet ces routes à la façade quand elles
arrivent par le tunnel du relais, et asrShutdownVite (libération de la
VRAM, optimiseur) lui demande l'arrêt par l'API interne.

Parité : parity_dictate_test.go tire du Go la lecture de treize WAV, les
chemins d'archive, réglages, catalogue, arguments et treize appels de
route, rejoués par tests/dictate.rs ; supervision testée contre un faux
sherpa-onnx qui parle le protocole.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016tcGfJWefW5kUGoC2wL8cv
This commit is contained in:
Claude committed 2026-10-15 16:17:29 +00:00
1 parent 17ec2291d4
commit 46bfab3343
21 files changed
+2699 -19

No files matched your search

@@ -0,0 +1,96 @@
# Lot 9 — dictée vocale tenue par la façade
## Contexte
La dictée vocale vivait dans le Go :
- `dictate_config.go` : réglages ;
- `dictate_models.go` : catalogue Parakeet, téléchargement et extraction ;
- `dictate_server.go` : supervision de `sherpa-onnx-offline-websocket-server`
et protocole WebSocket ;
- `web_transcribe.go` : routes.
Lot 9 : la façade la tient quand `LOKI_DICTATE=rust` (expérimental ; sans la
variable rien ne change).
## Ce que sert la façade (`rust/loki-rs/src/dictate.rs`)
| Route | Rôle |
|---|---|
| `POST /api/transcribe` | un WAV (≥ 1000 octets, ≤ 32 Mio) entre, le texte sort ; modèle absent : 503 `{downloading, pct}` et téléchargement lancé (relancé après un échec, qui est dit) |
| `GET\|POST /api/dictate/config` | modèle (validé contre le catalogue) et réactivité ; enregistrer arrête le serveur, qui repart au prochain mot avec le nouveau modèle |
| `GET /api/dictate/models` | catalogue trié par taille, présence des quatre fichiers |
| `GET\|POST /api/dictate/models/download` | un téléchargement à la fois, progression sur les octets reçus |
| `GET /api/dictate/state` | serveur allumé (et depuis quand), modèle, présence, téléchargement |
| `POST /internal/facade/v1/dictate/stop` | (le Go, jeton interne) arrêt sans attendre derrière un démarrage |
Même mécanique que le Go :
**Archive `.tar.bz2`**
- Décompressée au fil du téléchargement, dans un dossier `.part` renommé à la fin.
- Entrées absolues ou qui remontent refusées, liens ignorés.
**Serveur de transcription**
- Lancé à la demande sur un port libre de la boucle locale, GPU masqués, sans
le jeton interne dans son environnement.
- Prêt dès que sa socket répond.
- Une mort au démarrage est rapportée avec sa cause et la dernière ligne du
journal ; un processus qui ne répond pas fait échouer le démarrage au bout
de 2 minutes.
- Éteint après dix minutes d'inactivité.
- `shutdown_fast` tue le processus qui démarre au lieu d'attendre le verrou.
**Protocole**
- En-tête de 8 octets (fréquence, nombre d'octets), échantillons float32 en
trames de 10 Kio, une trame texte en retour.
- Annotations (`[musique]`, `(rires)`) écartées.
## Côté Go (`facade_dictate.go`)
- `withFacadeChat` remet les routes du tunnel et de la redistribution E2E à la
façade. Si elle ne répond pas : 502 « dictée indisponible : … ».
- `asrShutdownVite` passe par la façade quand elle tient la dictée : son
serveur n'est pas un enfant de ce process. Cela couvre la libération de la
VRAM et l'arrêt du moteur principal par l'optimiseur.
## Écarts assumés
- Messages d'erreur de transport (WebSocket, HTTP) et de décompression au
format des crates Rust.
- Fin de connexion sans réponse : « lecture du résultat : connexion fermée ».
## Parité
`internal/loki/parity_dictate_test.go` tire du Go ce journal :
- treize WAV : mono, bloc `LIST` intercalé, stéréo moyenné, flottant 32 bits,
données tronquées, impaires, vides, et les refus ;
- les chemins d'archive ;
- les marqueurs ;
- les réglages par défaut ;
- les arguments du serveur ;
- les adresses des modèles ;
- la présence ;
- treize appels de route.
Le tout est rangé dans `tests/parity_dictate/dictate.json` et rejoué par
`tests/dictate.rs`, les routes par la façade. Une mutation (premier canal au
lieu de la moyenne) fait échouer le rejeu.
`tests/dictate.rs` couvre aussi :
- le protocole contre un faux serveur WebSocket (en-tête, découpage
10240 + 10240 + 3528, marqueurs, réponse illisible, serveur injoignable) ;
- l'extraction (lien ignoré, entrée qui remonte refusée sans rien laisser,
archive corrompue) ;
- le téléchargement depuis un faux serveur (100 %, HTTP 404, modèle inconnu,
un seul à la fois) ;
- la supervision d'un faux sherpa-onnx (script Python qui parle le
protocole) :
- transcription de bout en bout par la façade ;
- même serveur réutilisé ;
- arrêt demandé par le Go, et refusé sans le jeton ;
- réglage changé ;
- les démarrages ratés : sortie en code 3 avec sa dernière ligne, serveur
muet, arrêt « vite » qui tue le démarrage, binaire introuvable, modèle
absent.