Skip to main content

TTS — How the Intern Sounds

HAL turns every reply into speech with the configured TTS provider. Intern v2 ships with ElevenLabs through the Autonomous proxy; you can change the provider, the voice and the speed, bring your own key, or go fully offline with Piper.

Providers​

tts_providerWhat it isModel used
elevenlabsElevenLabs (Intern v2 default)eleven_v4_turbo (over HTTP, or WebSocket with HAL_TTS_ELEVENLABS_WS=true)
openaiOpenAI TTS, or any OpenAI-compatible /audio/speech servertts-1
geminiGemini TTS through the Autonomous proxyHAL_TTS_GEMINI_MODEL (default gemini-3.8-flash-tts)
piperLocal, offline Piper voices on the devicethe .onnx voice file

An empty or unknown provider falls back to the OpenAI-compatible backend.

The keys​

Key (config.json)DefaultNotes
tts_providerelevenlabs on Intern v2See table above
tts_voiceBy language: Rachel (en), Ngan (vi), Amy (zh)Voice name or provider voice id
tts_speed1.1 (from HAL_TTS_SPEED in /opt/hal/.env)0.25–4.0; the UI slider covers 0.5–2.0
tts_api_keyEmpty → uses llm_api_keyYour own provider key
tts_base_urlEmpty → uses llm_base_urlYour own provider URL
tts_instructions"Friendly" (HAL_TTS_INSTRUCTIONS)OpenAI only: style prompt. Hand-edit config.json and restart HAL — not in the API.

Change voice / provider​

Web UI: http://<device-ip>/setting?debug=true#tts → Voice. Choose Autonomous (proxy) (with an OpenAI / ElevenLabs / Gemini sub-select), OpenAI (direct), ElevenLabs (direct), Piper (Local — free) or Custom (BYO URL); pick a voice and speed; use Test Voice to hear it before saving.

API:

curl -s -X PUT $DEVICE/api/device/config \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"tts_provider":"elevenlabs","tts_voice":"Brian","tts_speed":1.0}'

TTS changes are pushed to HAL live — the next sentence uses the new voice. (If the live push fails, os-server restarts HAL instead.)

Your own ElevenLabs or OpenAI key​

# ElevenLabs direct — HAL detects api.elevenlabs.io (also api.us. / api.eu.)
curl -s -X PUT $DEVICE/api/device/config \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"tts_provider":"elevenlabs",
"tts_base_url":"https://api.elevenlabs.io",
"tts_api_key":"<elevenlabs-key>",
"tts_voice":"<voice-id-from-your-library>"}'

# OpenAI direct
curl -s -X PUT $DEVICE/api/device/config \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"tts_provider":"openai",
"tts_base_url":"https://api.openai.com/v1",
"tts_api_key":"sk-…",
"tts_voice":"nova"}'

Always set tts_base_url explicitly when you set your own key — an empty base URL falls back to the LLM endpoint. To remove a custom key, send {"clear_tts_api_key": true}.

Voices​

List what's available for a provider (optionally filtered by lang):

curl -s -H "Authorization: Bearer $TOKEN" "$DEVICE/api/device/voices?provider=elevenlabs&lang=vi"
ProviderVoices
ElevenLabs (built-in names)en: Rachel, Sarah, Nicole, Terra, Maria, Sophie, Piper, Mia, Kimmy, Brianna, Ally, Tori, Brian, Adam, Daniel, George, James, Liam, Charlie, Sam, Sean, Kael, Brooks, Erion · vi: Ngan, Linh, Huyen, Freya, Nathan, Quan · zh: Amy, Sage, Xiaoxi, Yun, Evan Zhao, Jin
ElevenLabs (any other)Put a raw ElevenLabs voice_id in tts_voice — it's passed through as-is, so any voice in your ElevenLabs account works
OpenAIalloy, ash, coral, echo, fable, onyx, nova, sage, shimmer
Gemini30 multilingual prebuilt voices, default Kore (unknown names fall back to Kore)
PiperAny .onnx voice installed on the device (below)

Offline TTS with Piper​

Piper runs entirely on the device — no network, no API cost — at lower quality than the cloud voices. It isn't pre-installed.

Web UI: Voice → Piper (Local — free) → install the engine and download a voice in the Piper panel → Save.

API (proxied to HAL, admin-gated):

# 1. Install the engine (downloads the aarch64 Piper binary to /opt/piper)
curl -s -X POST -H "Authorization: Bearer $TOKEN" $DEVICE/api/voice/piper/install

# 2. Download a voice from the catalog
curl -s -X POST -H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"name":"en_US-kristin-medium"}' $DEVICE/api/voice/piper/voice

# 3. Status (engine + installed voices)
curl -s -H "Authorization: Bearer $TOKEN" $DEVICE/api/voice/piper/status

# 4. Use it
curl -s -X PUT $DEVICE/api/device/config \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"tts_provider":"piper","tts_voice":"en_US-kristin-medium"}'

Catalog voices: en_US-ljspeech-medium, en_US-kristin-medium, en_US-libritts_r-medium, vi_VN-vais1000-medium, es_ES-davefx-medium, de_DE-thorsten-medium, pt_BR-faber-medium, fr_FR-siwis-medium.

To use any other Piper model, copy its .onnx (and .onnx.json) into /opt/piper/voices/ and set tts_voice to the file name without extension — or to an absolute path. Remove a voice with POST /api/voice/piper/voice/remove (the last voice can't be removed).

HAL envDefault
HAL_PIPER_BIN/opt/piper/piper
HAL_PIPER_VOICES/opt/piper/voices
HAL_PIPER_VOICEen_US-ljspeech-medium

Try a voice without saving​

This is what Test Voice in the UI calls — it speaks one sentence with the given voice and leaves the saved config alone:

curl -s -X POST $DEVICE/api/voice/preview \
-H "Authorization: Bearer $TOKEN" -H 'Content-Type: application/json' \
-d '{"text":"Testing a different voice","provider":"elevenlabs","voice":"Brian","speed":1.0}'

To make the device speak from your own code (including per-utterance voice overrides), see HAL API → TTS.

TTS cache​

Rendered phrases are cached as WAV files in /var/lib/hal/tts_cache (HAL_TTS_CACHE_DIR). The cache key includes provider, voice, model, speed and text, so changing any of them automatically uses fresh audio. Delete the directory to reclaim space; it refills on demand.

VarDefaultEffect
HAL_TTS_SPEED1.1 on Intern v2Speed when tts_speed isn't set
HAL_TTS_INSTRUCTIONSFriendlyOpenAI style prompt when tts_instructions isn't set
HAL_TTS_ELEVENLABS_WSfalseStream ElevenLabs over a reused WebSocket session (lower latency)
HAL_TTS_GEMINI_MODELgemini-3.8-flash-ttsGemini TTS model
TTS_VOICEnovaVoice when tts_voice is empty
HAL_TTS_CACHE_DIR/var/lib/hal/tts_cacheCache location

Restart HAL after editing: sudo systemctl restart hal.