OpenClaw en mode vocal — STT + TTS 100% local
Comment parler à son agent IA self-hosted via Telegram sans aucune API externe : whisper.cpp, ffmpeg statique, Edge TTS. Zéro clé, zéro cloud.
Partie 6 de la série OpenClaw — Comment parler à son agent IA en self-hosted, sans API externe, sans clé, sans cloud.
Mon stack OpenClaw tourne sur une VM Ubuntu, dans un container Docker, avec Ollama comme backend LLM local et Telegram comme interface principale. Tout est self-hosted, tout est local.
La question naturelle après quelques semaines d’utilisation : peut-on interagir par la voix ?
Envoyer un vocal sur Telegram, que l’agent comprenne, réponde — et idéalement réponde aussi en audio. Sans passer par OpenAI Whisper API, sans ElevenLabs, sans aucune clé externe.
Spoiler : c’est possible.
Architecture finale

🎤 Vocal Telegram
↓
ffmpeg-static ← conversion .ogg → .wav
↓
whisper-cli ← transcription locale (whisper.cpp)
↓
OpenClaw Gateway ← agent turn sur le modèle local
↓
Edge TTS (natif) ← synthèse vocale, aucune API key
↓
🔊 Réponse audio Telegram
Tout roule dans le container Docker d’OpenClaw, via le volume ~/.openclaw déjà monté.
Étape 1 — Compiler whisper.cpp sur le host
whisper.cpp est un binaire C++ natif — zéro dépendance Python, zéro modèle PyTorch. On le compile sur le host Ubuntu, puis on l’expose dans le container via le volume ~/.openclaw.
sudo apt install -y cmake build-essential git
cd ~
git clone https://github.com/ggerganov/whisper.cpp
cd whisper.cpp
cmake -B build
cmake --build build -j$(nproc)
# Modèle small (465 MB, bon compromis précision/vitesse)
bash models/download-ggml-model.sh small
Vérifie que ça fonctionne :
./build/bin/whisper-cli \
-m models/ggml-small.bin \
-l fr -nt \
samples/jfk.wav 2>/dev/null
Tu dois voir la transcription du discours JFK.
Étape 2 — Copier les binaires dans ~/.openclaw/bin
mkdir -p ~/.openclaw/bin
# whisper-cli + libs
cp ~/whisper.cpp/build/bin/whisper-cli ~/.openclaw/bin/
cp ~/whisper.cpp/build/src/libwhisper.so.1 ~/.openclaw/bin/
cp ~/whisper.cpp/build/ggml/src/libggml.so ~/.openclaw/bin/
cp ~/whisper.cpp/build/ggml/src/libggml-base.so ~/.openclaw/bin/
cp ~/whisper.cpp/build/ggml/src/libggml-cpu.so ~/.openclaw/bin/
# Symlinks nécessaires
cd ~/.openclaw/bin
ln -sf libggml.so libggml.so.0
ln -sf libggml-base.so libggml-base.so.0
ln -sf libggml-cpu.so libggml-cpu.so.0
# Modèle
cp ~/whisper.cpp/models/ggml-small.bin ~/.openclaw/bin/
Étape 3 — ffmpeg statique
Telegram envoie les vocaux en .ogg (opus). whisper-cli n’accepte que .wav. Le ffmpeg Ubuntu est compilé avec des dizaines de libs absentes du container Alpine — on prend donc le build statique officiel.
curl -L https://github.com/BtbN/FFmpeg-Builds/releases/download/latest/ffmpeg-master-latest-linux64-gpl.tar.xz \
-o /tmp/ffmpeg-static.tar.xz
tar -xf /tmp/ffmpeg-static.tar.xz -C /tmp/
cp /tmp/ffmpeg-master-latest-linux64-gpl/bin/ffmpeg ~/.openclaw/bin/ffmpeg-static
chmod +x ~/.openclaw/bin/ffmpeg-static
Test dans le container :
docker exec openclaw-openclaw-gateway-1 \
/home/node/.openclaw/bin/ffmpeg-static -version 2>&1 | head -1
Étape 4 — Le wrapper whisper
OpenClaw appelle le CLI avec ses propres arguments. Le wrapper doit convertir .ogg → .wav avant de passer à whisper-cli.
cat > ~/.openclaw/bin/whisper << 'EOF'
#!/bin/sh
export LD_LIBRARY_PATH=/home/node/.openclaw/bin
TMP="/tmp/whisper_$$.wav"
INPUT=""
for arg; do INPUT="$arg"; done
ARGS=""
for arg; do
if [ "$arg" != "$INPUT" ]; then
ARGS="$ARGS $arg"
fi
done
/home/node/.openclaw/bin/ffmpeg-static \
-i "$INPUT" -ar 16000 -ac 1 "$TMP" -y 2>/dev/null
/home/node/.openclaw/bin/whisper-cli $ARGS "$TMP" 2>/dev/null
rm -f "$TMP"
EOF
chmod +x ~/.openclaw/bin/whisper
Étape 5 — Configurer le STT dans openclaw.json
python3 -c "
import json
with open('/home/rogmini/.openclaw/openclaw.json') as f:
config = json.load(f)
config['tools']['media'] = {
'audio': {
'enabled': True,
'models': [{
'type': 'cli',
'command': '/home/node/.openclaw/bin/whisper',
'args': [
'-m', '/home/node/.openclaw/bin/ggml-small.bin',
'-l', 'fr', '-nt', '{{MediaPath}}'
],
'timeoutSeconds': 60
}]
}
}
with open('/home/rogmini/.openclaw/openclaw.json', 'w') as f:
json.dump(config, f, indent=2)
print('OK')
"
{{MediaPath}} est un template OpenClaw — remplacé automatiquement par le chemin du fichier audio.
Étape 6 — Configurer le TTS
OpenClaw intègre nativement Edge TTS de Microsoft — gratuit, aucune clé, voix neurales. auto: "inbound" déclenche les réponses audio uniquement quand l’entrée est aussi audio.
python3 -c "
import json
with open('/home/rogmini/.openclaw/openclaw.json') as f:
config = json.load(f)
config['messages']['tts'] = {
'auto': 'inbound',
'provider': 'edge',
'edge': {
'enabled': True,
'voice': 'fr-FR-DeniseNeural',
'lang': 'fr-FR',
'outputFormat': 'audio-24khz-48kbitrate-mono-mp3'
}
}
with open('/home/rogmini/.openclaw/openclaw.json', 'w') as f:
json.dump(config, f, indent=2)
print('OK')
"
Étape 7 — Redémarre et teste
cd ~/openclaw && docker compose restart
Envoie un vocal sur Telegram. Tu reçois une réponse audio.
Points d’attention
Le JSON d’OpenClaw est strict — valide avant de redémarrer :
python3 -c "import json; json.load(open('/home/rogmini/.openclaw/openclaw.json')); print('OK')"
whisper-cli ne lit pas le .ogg nativement malgré ce que dit la doc — la conversion ffmpeg est obligatoire pour les vocaux Telegram (format opus/ogg).
Le container Alpine n’a pas pip — la solution avec le binaire statique + volume monté évite complètement ce problème.
auto: "inbound" est crucial — sans lui, l’agent répond en audio à tous les messages texte aussi.
Résultat
Toi (vocal Telegram) : "Bonjour, comment ça va ?"
Agent (audio Telegram) : "Bonjour ! Ça va bien, merci.
Je suis prêt — qu'est-ce qu'on fait aujourd'hui ?"
Latence totale : ~8-12 secondes sur CPU (transcription ~3s + inférence ~5s + TTS ~1s).
Ce qui vient ensuite
- Upgrade vers le modèle
mediumpour une meilleure précision en français - VAD (Voice Activity Detection) pour couper le silence automatiquement
- Interface Pipecat pour une boucle vocale temps réel depuis le navigateur
Partager cet article