09 / Feature modules

Piper text-to-speech (synaplan-tts)

On this page 9

Module id: piper_tts · Kind: sidecar · Configured by: SYNAPLAN_TTS_URL · Capability: text_to_speech · Provider: piper

Spoken answers without a cloud: synaplan-tts is a small HTTP service around Piper with five voices baked into the image — English, German, Spanish, French, Turkish. The UI language picks the voice; the audio never leaves your infrastructure. Starts with the standard stack.

The companion page Text-to-Speech covers voices, the image and adding your own models. This page is the module view.


What it adds#

  • The speaker control on chat answers, using a local voice.
  • The text_to_speech capability the assistant can use in multi-task plans ("read this back to me").
  • A voice per UI language — German UI → kerstin, Spanish → davefx, and so on — with no separate picker to configure.

Without it#

The speaker control does not appear and the capability inventory reports text-to-speech as unavailable — unless you use a cloud voice instead: ELEVENLABS_API_KEY, or a catalog TTS model from Gemini, Mistral Voxtral or xAI. Those live inside the main app and do not need this module. Speech input (Whisper transcription) ships with the core and is unaffected either way.


How to enable#

Option A — with the stack (default)#

shell
make up                     # dev / evaluation stack: tts starts with it
docker compose stop tts     # hide spoken answers again

The standard compose files (dev and deploy/compose.yaml) start the published ghcr.io/metadist/synaplan-tts image and default SYNAPLAN_TTS_URL to http://tts:10200, so the backend finds it without further configuration. Production keeps the service internal-only (no host port); the development stack additionally publishes it on 127.0.0.1:10200. The minimal stack keeps the opt-in profile:

shell
docker compose -f docker-compose-minimal.yml --profile tts up -d     # minimal stack

Option B — standalone container, same or another host#

shell
docker run -d --name synaplan-tts \
  -p 127.0.0.1:10200:10200 \
  ghcr.io/metadist/synaplan-tts:latest

Then point the backend at it and restart backend + worker:

shell
SYNAPLAN_TTS_URL=http://host.docker.internal:10200    # same host
SYNAPLAN_TTS_URL=http://192.168.1.50:10200               # a LAN box or GPU host serving several Synaplan nodes

Production deploy/compose.yaml already includes the service, so this option is only for a speaker that lives outside the stack (another host, a GPU box).


Check it works#

  • Operate → Feature Status → Piper text-to-speech shows Available / Synaplan TTS is running. The probe is GET /health on the service. Synaplan TTS is not reachable means the URL is set but nothing answered; SYNAPLAN_TTS_URL is unset means the module is off.
  • Send a chat message and click the speaker icon on the answer.
  • Directly from inside the backend container (works on both stacks; production has no host port):
shell
docker compose exec -T backend php -r 'echo file_get_contents("http://tts:10200/health"), "\n";'

On the development stack curl http://127.0.0.1:10200/health on the host works too.


Configuration reference#

Variable Default Meaning
SYNAPLAN_TTS_URL Compose default http://tts:10200 (the stack service) Base URL of synaplan-tts. Empty = module off.

Extra voices: mount .onnx + .onnx.json Piper models into the service's extra-voices volume — see the synaplan-tts README. Cloud alternatives (ELEVENLABS_API_KEY, catalog TTS models) are provider settings, not part of this module.


Troubleshooting#

  • not reachable although curl on the host works. The backend resolves the URL from inside its container. localhost there is the backend; use host.docker.internal (Compose adds the mapping) or the host's LAN address.
  • No speaker icon. The control only renders when the service answers; check Feature Status first, then the browser console for the /api/v1/tts/stream call.
  • Wrong voice. The backend sends the frontend locale; if it detects a different reply language, that language's voice wins. There is no manual voice picker by design.