Skip to main content
A voice agent is a loop: hear the caller, decide what to say, say it. Geko covers both speech ends of that loop — speech-to-text for the listening and text-to-speech for the speaking — on one API key and one balance. You bring the model in the middle.
Both halves matter for Kazakh specifically: the caller may switch between Kazakh and Russian mid-sentence, which is exactly what seta-kk-ru-v2 is built to transcribe without being told a language, and general-purpose ASR is not.

Keep it low-latency

Two settings and one habit do most of the work:
  • nfe: 16. Half the diffusion steps of the default 32, noticeably faster, still very usable for a conversation. Save 32 for narration you’ll ship. See latency & quality.
  • Stream both directions. Use stt.stream() so the transcript exists while the caller talks (a final lands ~0.4 s after they stop, at a 200 ms threshold), and tts.stream() so playback starts on the first sentence instead of the full reply.
  • Mind the first call after a lull. Both GPU services scale to zero when idle; a cold start is ~15 s (the streaming service restores from a GPU snapshot). Set a generous connect timeout — the SDK defaults to 60 s — and don’t park idle “spare” sockets to dodge it: they’re closed after 30 s of silence. For a scheduled call window, ask us to pin a container.

Choosing a voice

Pick one that fits the role. Aigerim (warm, professional) reads well for reception and support; there are male and female alternatives (Arman, Ainur, Aruzhan, Sanzhar, Yerlan). Audition them on the voices page and set voice once for the whole session.
Both directions stay server-side. Your sk-tokay-… key is server-only — never ship it in a browser or mobile bundle. In a voice agent, the media pipeline (telephony, transcription, the model, synthesis) lives on your backend; the client only sends and plays audio. See Use it in your app.

Listening: caller audio to text

Open one stream per call and feed it the line audio continuously — silence included, because the silence is how the server knows the caller stopped. Every final is a completed turn: end-of-utterance detection happens server-side, tuned by you, so your telephony layer does not need its own silence detector.
While the caller is still talking, a partial arrives every half-second. Each one carries audio_to — how far into your audio the transcript has reached — so you can start the LLM on a stable prefix before the turn closes, and let the final merely confirm it. If your transport hands you complete recordings instead of a live line — voicemail, an uploaded file, a post-call pipeline — use stt.transcribe(): it is more accurate on whole utterances and a third of the price.
Transcripts are lowercase and unpunctuated — the model’s output vocabulary contains neither. That is usually fine for an LLM prompt, but don’t feed it somewhere that expects sentence case. See the STT overview.

Speaking: model text to audio

Here’s the synthesis half: take an async iterable of text as the model produces it, batch it into sentences, and stream each sentence through tts.stream() — yielding WAV chunks you feed straight to the caller’s audio channel.

The whole turn

Wiring both ends together, one caller turn end to end:
The pieces outside Geko — the phone/SIP transport, turn detection, and the LLM — are yours to wire up. Batching on sentence boundaries keeps latency low without chopping words: each unit is big enough to sound natural and small enough to start fast.
Streams aren’t retried, so on a live call bound each turn with a per-call timeout and use signal to cancel on barge-in or hang-up. Transcription is retried on transient failures. See Errors & retries.

Next steps

Streaming audio

The full streaming API and the raw wav-frames protocol.

Speech-to-text

Transcription options, response shape, and audio formats.

Latency & quality

nfe, cold starts, and keeping the backend warm.

Errors & retries

Timeouts, cancellation, and why streams don’t retry.