> ## Documentation Index
> Fetch the complete documentation index at: https://docs.geko.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# Voice agents

> Use Geko as the low-latency voice for a phone or assistant loop.

Geko is the **speak** step of a voice agent. It's text-to-speech only — it doesn't transcribe audio and it doesn't run an LLM. You bring your own speech-to-text and your own model; Geko turns the model's text into a voice, fast enough to feel live.

```
caller audio ──▶ your STT ──▶ your LLM ──▶ Geko (speak) ──▶ caller hears it
```

The trick to making it feel responsive is **streaming**: as the LLM produces text, pipe it into [`geko.tts.stream()`](/guides/streaming) and play each WAV chunk the moment it arrives. The caller hears speech roughly one sentence after the model starts talking, not after the whole reply is written and rendered.

## 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](/guides/latency).
* **Stream, don't wait.** Use `tts.stream()` so playback starts on the first sentence instead of the full reply.
* **Stay warm.** The GPU backend scales to zero when idle, so the first call after a lull can cold-start for tens of seconds. On a live line that's a dead pause. Keep a container hot with predictable traffic or a pre-warm call before a session — see [cold starts](/guides/latency).

## 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](/voices) page and set `voice` once for the whole session.

<Warning>
  **Synthesis stays 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, the model, Geko) lives on your backend; the client only sends and plays audio. See [Use it in your app](/guides/frameworks).
</Warning>

## Wiring the LLM into Geko

Here's the Geko half of the loop: 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.

```ts theme={null}
import { Geko } from "@gekoai/sdk";

const geko = new Geko({ apiKey: process.env.GEKO_API_KEY });

// Split an incoming text stream on sentence boundaries so we synthesize
// a natural unit at a time instead of one word at a time.
async function* sentences(chunks: AsyncIterable<string>) {
  let buf = "";
  for await (const piece of chunks) {
    buf += piece;
    let m: RegExpMatchArray | null;
    // flush each completed sentence as it appears
    while ((m = buf.match(/[^.!?…]*[.!?…]+\s*/))) {
      yield m[0].trim();
      buf = buf.slice(m[0].length);
    }
  }
  if (buf.trim()) yield buf.trim(); // trailing text with no terminator
}

// `llmText` is whatever your model gives you as it streams — an async
// iterable of text deltas. Geko speaks each sentence as it's ready.
export async function* speak(
  llmText: AsyncIterable<string>,
  signal?: AbortSignal,
) {
  for await (const sentence of sentences(llmText)) {
    for await (const wav of geko.tts.stream({
      text: sentence,
      voice: "Aigerim",
      nfe: 16, // interactive: favor speed
      signal, // let the caller cut synthesis short on barge-in
    })) {
      yield wav; // hand this WAV chunk to your audio output
    }
  }
}
```

Drive it from your loop:

```ts theme={null}
const controller = new AbortController();

for await (const wav of speak(llmTextStream, controller.signal)) {
  playToCaller(wav); // your telephony / audio sink
}

// caller starts talking over the agent (barge-in) → stop speaking:
// controller.abort();
```

The pieces outside Geko — the phone/SIP transport, the STT, the LLM, and turning the caller's speech into `llmTextStream` — are yours to wire up; Geko slots in as the voice at the end. Batching on sentence boundaries keeps latency low without chopping words: each unit is big enough to sound natural and small enough to start fast.

<Note>
  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. See [Errors & retries](/sdk/errors).
</Note>

## Next steps

<CardGroup cols={2}>
  <Card title="Streaming audio" icon="waveform-lines" href="/guides/streaming">
    The full streaming API and the raw wav-frames protocol.
  </Card>

  <Card title="Latency & quality" icon="gauge-high" href="/guides/latency">
    `nfe`, cold starts, and keeping the backend warm.
  </Card>

  <Card title="Pick a voice" icon="microphone" href="/voices">
    Audition Aigerim, Arman, and the rest of the catalog.
  </Card>

  <Card title="Errors & retries" icon="shield-check" href="/sdk/errors">
    Timeouts, cancellation, and why streams don't retry.
  </Card>
</CardGroup>
