Pipecat Gandr TTS
Official Gandr text-to-speech integration for Pipecat, the framework for building voice and multimodal conversational agents.
Note: This integration is maintained by Gandr. As the provider of the TTS service, we keep it current with Pipecat releases and with our own API.
Table of Contents
- Why Gandr
- Pipecat compatibility
- Installation
- Quick start
- Configuration
- Voices
- How the integration works
- Interruptions
- Long text
- Errors
- Metrics
- Pricing
- Environment variables
- Examples
- Requirements
- License
- Support
Why Gandr
146 ms to first audio byte over the open internet. Client-measured p50, n=25 interleaved runs against each named competitor in the same hours, from a neutral US vantage over a held WebSocket. Every pairwise gap is significant (p=0.0009 / 0.0041 / <0.001).
Read that claim precisely: it is the first audio byte, the moment audio starts arriving on the socket — not the moment a listener hears speech. It is the number a pipeline can actually act on, and it is the one we publish.
116 ms server-side p50, min 104 / max 130.
WER 1.982%, against a 2.171% human baseline. One whisper-large-v3 scorer
transcribed everything, including the human baseline, so the two numbers are
comparable. n=1,088, zero render errors.
Under load, overflow spills to a fallback lane that can take longer on its first request.
Pipecat compatibility
Tested with Pipecat v1.7.0 on Python 3.12.11 (2026-08-07): clean virtual
environment, pip install exit 0, imports, 11/11 unit tests pass, service
constructs.
Built against the WebsocketTTSService base class and the audio-context API,
so it supports pipecat-ai from 0.0.108 up to (but not including) 2.0.0.
The declared floor is 0.0.108 because that is what it was written against;
the tested figure above crosses a major version and about a year of API drift,
which is the number worth trusting.
Not claimed: no live socket has been opened against the service in that test. It covers install, import, unit tests and construction. Rendering audio costs money, so it is left to whoever runs it.
Installation
uv pip install git+https://github.com/Gandr-AI/gandr-pipecat.git
or with pip:
pip install git+https://github.com/Gandr-AI/gandr-pipecat.git
From source
git clone https://github.com/Gandr-AI/gandr-pipecat.git
cd gandr-pipecat
pip install -e .
Quick start
1. Get an API key
Keys are gnd_… strings. See gandr.ai/docs.
2. Basic usage
import os
from pipecat_gandr import GandrTTSService
tts = GandrTTSService(
api_key=os.getenv("GANDR_API_KEY"),
params=GandrTTSService.InputParams(
voice_id="gandr-mia",
language="en",
sample_rate=24000,
),
)
3. In a pipeline
pipeline = Pipeline(
[
transport.input(),
stt,
context_aggregator.user(),
llm,
tts,
transport.output(),
context_aggregator.assistant(),
]
)
A complete runnable pipeline is in
examples/foundational/gandr_tts_basic.py.
Configuration
GandrTTSService.InputParams:
| Parameter | Type | Default | Range / options | Description |
|---|---|---|---|---|
voice_id |
str |
"gandr-mia" |
a stock id, or a gnd: clone id |
Voice for synthesis |
language |
str |
"en" |
ISO language code | Language of the input text |
sample_rate |
int |
24000 |
8000, 16000, 22050, 24000 |
Output rate. Takes priority over a sample_rate passed to the constructor |
speed |
float |
None |
0.6 to 1.5 |
Playback speed, pitch preserving |
volume |
float |
None |
0.5 to 2.0 |
Output gain, soft-ceiling mastered |
temperature |
float |
None |
— | Expression control. Omit and the API chooses |
cfg_weight |
float |
None |
— | Expression control. Omit and nothing is sent |
seed |
int |
None |
— | Fixes the render for a reproducible result |
voice_wav_b64 |
str |
None |
base64 WAV | Reference audio for a cloned voice |
Constructor arguments beyond api_key and params:
| Argument | Default | Description |
|---|---|---|
url |
wss://tts-west.gandr.ai/ws |
Streaming endpoint |
text_aggregation_mode |
None |
How Pipecat aggregates text before synthesis |
utterance_timeout_s |
30.0 |
How long to wait for an utterance's closing frame |
busy_retry_s |
0.5 |
Wait before retrying after the server answers busy |
max_attempts |
3 |
Attempts per utterance, retries included |
reconnect_on_interruption |
True |
See Interruptions |
On sample_rate: 24000 is the API's default output rate and the right
choice for almost every agent, including telephony. If your transport needs
narrowband, let the transport resample — Pipecat does it for free — rather than
asking the server for a narrowband stream.
Changing voice mid-session
Voice, language and expression controls are read per utterance, so a
TTSUpdateSettingsFrame takes effect on the next thing the bot says:
from pipecat.frames.frames import TTSUpdateSettingsFrame
await task.queue_frame(TTSUpdateSettingsFrame(settings={"voice": "gandr-leo"}))
Cloned voices
A cloned voice is registered per connection. Pass the reference audio once and the service attaches it to the first utterance on each connection, and re-attaches it automatically if the connection is reopened:
tts = GandrTTSService(
api_key=os.getenv("GANDR_API_KEY"),
params=GandrTTSService.InputParams(
voice_id="gnd:your-clone-id",
voice_wav_b64=reference_wav_base64,
),
)
If the server asks for a voice and none was configured, the service raises a
clear error naming voice_wav_b64 rather than going silent.
Voices
Stock voices: gandr-mia, gandr-ava, gandr-jenny, gandr-dane,
gandr-leo, gandr-lewis. Cloned voices are gnd: identifiers.
How the integration works
The service holds one WebSocket to wss://tts-west.gandr.ai/ws for the life of
the pipeline. Each utterance is a JSON message; the server answers with binary
frames of raw PCM16LE mono audio as it renders, then a JSON frame that closes
the utterance.
The connection carries many utterances but renders one at a time, so the
service serialises sends: run_tts queues an utterance and a single sender
task delivers it, waits for the closing frame, and only then sends the next.
That is what keeps the server's busy backpressure from ever becoming the
normal case. If it happens anyway, the send is retried after busy_retry_s.
Audio is delivered through Pipecat's audio-context API, so frames stay bound to the turn that requested them and are dropped cleanly when that turn is cancelled.
Interruptions
The wire protocol has no cancel message. When the user barges in, audio already rendering is discarded client-side — but it would still occupy the connection, and the next turn's first byte would queue behind audio nobody is listening to.
So by default (reconnect_on_interruption=True) the service reopens the
connection on interruption. Set it to False to keep the connection and accept
that the next turn waits for the interrupted render to drain.
Long text
The API caps a single request's transcript at 2000 characters. Pipecat normally hands over a sentence at a time, so this rarely comes up; when it does, the service splits the text on the cleanest boundary it can find — sentence end first, then a word boundary — and sends the pieces back to back on the same connection under the same turn. Nothing is dropped, and only the last piece closes the turn.
Errors
Failures surface as Pipecat ErrorFrames through push_error, and the turn is
always closed with a TTSStoppedFrame so the pipeline never hangs waiting for
audio that is not coming. Connection loss is handled by reopening and, where
the utterance had not yet reached the wire, resending it.
Metrics
can_generate_metrics() is True.
Time to first byte is stopped on the first audio byte off the wire — the same event the 146 ms claim above measures, so what your dashboard shows is directly comparable to what we publish. Usage metrics are reported per request from the full text of the turn.
The server reports its own timings on the frame that closes each utterance. This service neither consumes nor logs them, so the only latency it ever reports is the one it measured itself.
Pricing
- $10 per million characters, prepaid packs.
- $150 per stream per month.
See gandr.ai/pricing.
Environment variables
GANDR_API_KEY=your_gandr_api_key_here
OPENAI_API_KEY=your_openai_key_here # if using with an LLM
DEEPGRAM_API_KEY=your_deepgram_key_here # if using with STT
Examples
examples/foundational/gandr_tts_basic.py— full pipeline with STT, LLM and TTS over the local audio transport.
uv add "pipecat-ai[deepgram,openai,silero,local]"
python examples/foundational/gandr_tts_basic.py
Tests
pytest tests
The transcript tests import pipecat_gandr._text directly and need neither
Pipecat nor a network, so they run anywhere.
Requirements
- Python >= 3.11
- pipecat-ai >= 0.0.108, < 2.0.0
- websockets >= 15.0.1, < 16.0
- pydantic >= 2.0
- loguru >= 0.7.3
- python-dotenv >= 1.1.1
License
MIT. See LICENSE.
Support
- Documentation: gandr.ai/docs
- Email: contact@gandr.ai
- Website: gandr.ai
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pipecat_gandr-0.1.0.tar.gz.
File metadata
- Download URL: pipecat_gandr-0.1.0.tar.gz
- Upload date:
- Size: 21.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.26 {"installer":{"name":"uv","version":"0.11.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6a9611db03423c31e337c3f1617d2ecee813c41afdc0f7c5fa9908539ae96d2c
|
|
| MD5 |
0954127b116d537e7fe7bbeefe72ad01
|
|
| BLAKE2b-256 |
3fec66b5c5a2b2bdfd7ed93268055a476c1dbb21930d7f3fa94612dc6bcff922
|
File details
Details for the file pipecat_gandr-0.1.0-py3-none-any.whl.
File metadata
- Download URL: pipecat_gandr-0.1.0-py3-none-any.whl
- Upload date:
- Size: 17.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.26 {"installer":{"name":"uv","version":"0.11.26","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
13696d410a914888d547b1cc511e24c3ec3b3257b51eed9d8a408d2d9697d9ef
|
|
| MD5 |
ae85f549a2d415b7c49861104f195495
|
|
| BLAKE2b-256 |
965bbde07e528fe9c614a035c6c0950261b2614e2d2c7c1f13ca888acaa23c56
|