Jarvis Voice — Canal vocal Voxtral (STT + TTS)

Architecture

Navigateur          Plugin jarvis-voice (OpenClaw)
┌─────────┐  WS   ┌────────────────────────────────────────┐
│ ● micro │──────►│  Voxtral STT ──► OpenClaw agent        │
│ ■ stop  │       │  (realtime)      (Mistral Large)       │
│ ✕ coupe │◄──────│  Voxtral TTS ◄──  + tools (gog, mail)  │
│ 🔊 audio│  WS   │  (streaming)                           │
│ ⌨️ texte │──────►│                                        │
└─────────┘       └────────────────────────────────────────┘

Flux de données

Micro (16kHz PCM) → WebSocket → Voxtral STT realtime → texte
                                                            │
                                                            ▼
                              OpenClaw agent (Mistral Large) ──► texte streamé
                                                            │
                                                            ▼
                              Voxtral TTS streaming (24kHz PCM) → WebSocket → Navigateur

Configuration

openclaw.json — Section tts

{
  "tts": {
    "provider": "mistral-voxtral",
    "mode": "final",
    "providers": {
      "mistral-voxtral": {
        "apiKey": "mstrl_...",
        "voice": "gb_oliver_neutral",
        "model": "voxtral-mini-tts-2603",
        "language": "fr",
        "responseFormat": "pcm"
      }
    }
  }
}

openclaw.json — Section channels.jarvis

{
  "channels": {
    "jarvis": {
      "enabled": true,
      "apiKey": "mstrl_...",
      "voiceId": "gb_oliver_neutral",
      "ttsModel": "voxtral-mini-tts-2603",
      "sttModel": "voxtral-mini-transcribe-realtime-2602",
      "language": "fr",
      "dmPolicy": "open",
      "allowFrom": ["*"]
    }
  }
}

openclaw.json — Section plugins

{
  "plugins": {
    "entries": {
      "jarvis-voice": { "enabled": true }
    },
    "load": {
      "paths": ["/home/ubuntu/voice-assistant/jarvis-voice"]
    }
  }
}

Fichiers du plugin

voice-assistant/jarvis-voice/
├── index.js              — Entrée plugin, auth, routes HTTP
├── openclaw.plugin.json  — Manifest plugin
├── package.json          — Dépendances (ws)
└── src/
    ├── accounts.js       — Config canal, DM policy
    ├── channel.js        — Plugin channel OpenClaw
    ├── config.js         — Schéma config
    ├── dispatch.js       — Dispatch tour vers agent
    ├── speech-provider.js— Provider TTS Voxtral (pour autres canaux)
    ├── ui.js             — Interface web (HTML/CSS/JS)
    ├── voice-server.js   — Serveur WebSocket, STT/TTS streaming
    ├── voxtral-stt.js    — Session STT Voxtral realtime
    └── voxtral-tts.js    — Streaming TTS Voxtral

Points clés d’implémentation

STT — Voxtral realtime (voxtral-stt.js)

  • URL : wss://api.mistral.ai/v1/audio/transcriptions/realtime
  • Modèle : voxtral-mini-transcribe-realtime-2602
  • Audio : PCM s16le 16kHz mono, base64
  • Paramètres : model, target_streaming_delay_ms=400
  • ⚠️ Pas de paramètre language — le modèle ne supporte pas le “language forcing”
  • Événements : session.created, transcription.text.delta (partials), transcription.segment, transcription.done

TTS — Voxtral streaming (voxtral-tts.js)

  • URL : POST https://api.mistral.ai/v1/audio/speech
  • Modèle : voxtral-mini-tts-2603
  • Voix : gb_oliver_neutral (britannique neutre)
  • Format : pcm (streaming SSE) ou mp3 (buffered)
  • Sample rate : 24kHz s16le mono
  • Streaming : SSE avec événements speech.audio.delta (base64) et speech.audio.done

Nettoyage TTS (voice-server.js)

La fonction cleanForTts() retire avant synthèse :

  • Emojis (🤖, 😀, etc.) — Voxtral rejette le texte vide après sanitization
  • Markdown (*, #, backticks)
  • Espaces multiples
function cleanForTts(text) {
  return text
    .replace(/[\u{1F300}-\u{1FAFF}\u{2600}-\u{27BF}\u{2190}-\u{21FF}\u{FE0F}\u{200D}]/gu, "")
    .replace(/\*+/g, "")
    .replace(/#{1,6}\s*/g, "")
    .replace(/`[^`]*`/g, "")
    .replace(/\s+/g, " ")
    .trim();
}

Interface web

  • URL : http://<host>:18789/jarvis?token=<gateway-token>
  • Auth : Bearer token ou ?token= query param
  • Features :
    • Bouton micro (push-to-talk)
    • Visualisation onde animée (record/speak/idle)
    • Transcription partielle en temps réel
    • Streaming réponse texte + audio
    • Champ texte (fallback)
    • Bouton stop (interruption / barge-in)
    • Bouton mute
    • Reconnexion automatique

Tests effectués (2026-10-13)

TestStatutDétails
Connexion WebSocket✅Session créée, voix gb_oliver_neutral
Texte → Agent → TTS✅8 chunks audio, réponse correcte
Audio → STT Voxtral✅Transcription partielle streamée, finale correcte
STT → Agent → TTS✅32 chunks audio, réponse adaptée au contenu audio
Barge-in (interruption)✅Nouveau message interrompt le tour en cours
Nettoyage emojis TTS✅Plus d’erreur Voxtral sur 🤖

Commandes utiles

# Redémarrer le gateway
systemctl --user restart openclaw-gateway
 
# Voir les logs
journalctl --user -u openclaw-gateway -f
 
# Statut
openclaw status
 
# Tester le canal (script Node.js)
node /tmp/test-jarvis-e2e.js

Quota et coûts

  • Mistral API : Voxtral STT + TTS consomment des crédits
  • Surveiller sur https://console.mistral.ai
  • Voix gb_oliver_neutral : voix britannique neutre

Ancien système (à désactiver à terme)

  • voice-assistant/app.py — Port 7860 — Whisper local + ElevenLabs
  • Service : jarvis-voice.service (systemd user)
  • À remplacer entièrement par le plugin jarvis-voice

Prochaines étapes possibles

  1. Voix française : Tester les voix fr_* via listVoxtralVoices()
  2. Ancien système : Arrêter jarvis-voice.service (port 7860)
  3. Talk mode : Activer le mode talk (TTS auto sur Telegram)
  4. Personas : Configurer des personas TTS (majordome, etc.)