Gradium Client
Experimental
The Gradium client is experimental: its interface and typed payloads may change between minor releases without a deprecation cycle.
Gradium is a speech provider: low-latency text-to-speech and speech-to-text in English, French,
German, Spanish and Portuguese. GradiumClient is standalone like the ElevenLabs client: it is not an
LLMClientBase subclass and LLMClient does not route to it. It covers the REST endpoints and voice management;
GradiumRealtimeTTSClient, GradiumRealtimeSTTClient and GradiumRealtimeS2SClient cover the WebSocket streams
(install the realtime extra).
Configuration
Set GRADIUM_API_KEY in your environment or pass api_key= explicitly. base_url defaults to
https://api.gradium.ai/api/; use https://eu.api.gradium.ai/api/ or https://us.api.gradium.ai/api/ for data
residency.
Requests retry up to three times on connection errors; GET, PUT and DELETE also on HTTP 502, 503 and 504 (Gradium
reports its own failures as 500, which is not retried, and a POST is billed or creates a voice, so it is never resent). A rate limit (429) raises TooManyRequestsError; other API errors, including errors streamed
after a successful transcription response started, raise LLMError with provider="gradium". Bad arguments (no voice,
unknown audio extension) raise ValueError.
Text-to-speech
from pathlib import Path
from padwan_ai import GradiumClient
async with GradiumClient(voice_id="YTpq7expH9539ERJ") as client:
speech = await client.generate_speech(
"Hello from Padwan.",
output_format="wav", # or pcm, opus, pcm_24000, ulaw_8000, ...
config={"temp": 0.3, "padding_bonus": -1}, # lower padding_bonus speaks faster
)
Path("hello.wav").write_bytes(speech.audio)
generate_speech returns a GradiumSpeech with the raw audio bytes and their mime_type. model="default" selects
the latest production model. Voice IDs come from the
voice library or your custom voices.
Streaming
stream_speech takes the same arguments and yields audio chunks as they are generated, so playback can start before
the whole text is synthesized:
async with GradiumClient(voice_id="YTpq7expH9539ERJ") as client:
with Path("hello.wav").open("wb") as f:
async for chunk in client.stream_speech("Hello from Padwan."):
f.write(chunk)
Realtime input (WebSocket)
When the text itself arrives in pieces (e.g. from an LLM stream), GradiumRealtimeTTSClient sends it over a WebSocket
as it comes and yields audio back. stream() accepts a sync or async iterable of text and regroups tokens on
whitespace, since the server joins consecutive messages with a space:
from padwan_ai import GradiumRealtimeTTSClient
async def llm_tokens():
yield "Hello there, "
yield "this is realtime speech."
async with GradiumRealtimeTTSClient(
voice_id="YTpq7expH9539ERJ",
output_format="pcm", # default: 48 kHz s16le mono; or pcm_24000, opus, ...
) as tts:
print(tts.ready["request_id"])
async for audio in tts.stream(llm_tokens()):
... # play or buffer the PCM chunk
For finer control, call send_text(text) and end_input() yourself and iterate the connection: audio_bytes()
decodes audio messages, transcript() returns the timed text messages and is_final() marks the end.
Speech-to-text
async with GradiumClient() as client:
result = await client.transcribe(
file="meeting.wav", # path or raw bytes: wav, raw pcm (24 kHz s16le mono) or Ogg Opus
language="fr", # default "any" auto-detects
config={"keywords": {"words": ["Padwan", "Polarsen"], "boost": 3}},
)
print(result["text"])
for segment in result["segments"]:
print(segment["start"], segment["end"], segment["text"])
The input format is inferred from the file extension (.wav, .pcm/.raw, .ogg/.opus) and defaults to wav for
raw bytes; pass input_format= to override. MP3 is not accepted by the API. transcribe returns a
GradiumTranscription with the joined text and timed segments (end is None for a trailing open segment). See
transcription settings for config options.
Realtime transcription (WebSocket)
GradiumRealtimeSTTClient streams audio (default input_format="pcm": 24 kHz s16le mono, ideally in 80 ms chunks of
1920 samples) and yields server messages: text transcripts, end_text timestamps, step voice activity every 80 ms
and flushed replies to flush().
from padwan_ai import GradiumRealtimeSTTClient
async with GradiumRealtimeSTTClient(language="en") as stt:
async for message in stt.stream(pcm_chunks): # then sends end_of_stream
if text := stt.transcript(message):
print(text)
elif message["type"] == "step" and message["vad"][2]["inactivity_prob"] > 0.5:
... # the speaker has likely finished their turn
To drive the socket yourself, call append_audio(chunk), flush() and end_input() and iterate the connection;
stream() ends the input, so a connection streams once.
Speech-to-speech (WebSocket)
GradiumRealtimeS2SClient transcribes streamed audio, translates it into target_language and re-synthesizes it with
voice_id, returning both the translated text and the output audio:
from padwan_ai import GradiumRealtimeS2SClient
async with GradiumRealtimeS2SClient(
voice_id="YTpq7expH9539ERJ", target_language="fr"
) as s2s:
async for message in s2s.stream(pcm_chunks): # 24 kHz s16le mono by default
if audio := s2s.audio_bytes(message):
... # output PCM at the rate reported in s2s.ready["sample_rate"]
elif text := s2s.transcript(message):
print(text)
The defaults (model="s2s-translate", stt_model="stt-translate", tts_model="default") are the only combination
the API accepts today.
Gradium has no plain voice conversion: a session without target_language is refused. To re-voice speech without
translating it, set target_language to the spoken language (e.g. "en" for English input) and pick a voice in that
language. The pipeline transcribes then re-synthesizes, so the words are kept but not the original tone, emotion or
pacing (unlike ElevenLabs' speech_to_speech).
Raw pcm input must be 24 kHz s16le mono, while pcm output is 48 kHz: feeding TTS pcm output straight back in
garbles the transcription. Send wav (input_format="wav") or resample first.
Error frames on any of the WebSocket streams (bad key, unknown model, no capacity) raise LLMError while iterating.
Voices
async with GradiumClient() as client:
voices = await client.list_voices(include_catalog=True) # with flagship voices
voice = await client.get_voice("YTpq7expH9539ERJ")
voice_id = await client.clone_voice("Narrator", "sample.wav", language="en")
await client.update_voice(voice_id, description="Calm narrator")
await client.delete_voice(voice_id)
clone_voice creates a custom voice from one audio sample and returns its ID; the format is inferred from the file
extension (pass input_format= for raw bytes). Voice design (prompt-generated voices), voice enhancement, localization
and pronunciation dictionaries are not covered yet.