You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: website/docs/user-guide/live/live.mdx
+5-3Lines changed: 5 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -9,18 +9,20 @@ sidebarTitle: "Overview"
9
9
10
10
| Pattern | Class | Latency | Use when… |
11
11
|---|---|---|---|
12
-
| Turn-by-turn voice |`Agent` + `OpenAITranscriber` + `TTSObserver`|~1–3 s per turn | You already have a text agent and want to add a voice front-end; you need tool execution, middleware, structured output, or any other text-agent feature. |
12
+
| Turn-by-turn voice |`Agent` + a transcriber + a TTS observer|~1–3 s per turn | You already have a text agent and want to add a voice front-end; you need tool execution, middleware, structured output, or any other text-agent feature. |
13
13
| Realtime full-duplex |`LiveAgent` + `OpenAIRealTimeConfig` / `GeminiRealTimeConfig`| <500ms| You want barge-in, interruption, semantic VAD, or a phone-call-like UX. |
14
14
15
+
For the turn-by-turn pattern, OpenAI and ElevenLabs are both available and support different halves incrementally — OpenAI streams transcription, ElevenLabs streams synthesis. See [Provider support](/docs/user-guide/live/stt_tts) for the full matrix. Gemini is realtime-only.
16
+
15
17
!!! note
16
18
Both patterns share the same audio I/O primitives — [`SoundDevicePlayer`](/docs/user-guide/live/stt_tts) and [`SoundDeviceRecorder`](/docs/user-guide/live/stt_tts) — and the same event stream. You can mix observers (TTS, logging, persistence) across both.
17
19
18
20
## Installation
19
21
20
-
The audio I/O classes depend on `sounddevice` and `numpy`; the OpenAIand Gemini integrations need their respective SDKs.
22
+
The audio I/O classes depend on `sounddevice` and `numpy`; the OpenAI, Gemini, and ElevenLabs integrations need their respective SDKs.
Copy file name to clipboardExpand all lines: website/docs/user-guide/live/stt_tts.mdx
+148-1Lines changed: 148 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -5,6 +5,27 @@ sidebarTitle: "STT & TTS"
5
5
6
6
The STT → Agent → TTS flow turns any existing `Agent` into a voice agent without changing the agent itself. Speech-to-text is added as a **pipeline wrapper**; text-to-speech is added as an **observer** that listens to the model's streamed message chunks.
7
7
8
+
## Provider support
9
+
10
+
Both halves are protocol-based, so providers are interchangeable — but they do not all support incremental delivery, and the two current providers are complementary opposites.
| OpenAI |`OpenAITranscriber`, `OpenAITranslationTranscriber`|**Yes** — emits `TranscriptionChunkEvent` deltas |`OpenAITTSConfig`| No — one buffered clip |
15
+
| ElevenLabs |`ElevenLabsTranscriber` (Scribe) | No — whole-file upload |`ElevenLabsTTSConfig`, `ElevenLabsStreamingTTSConfig`|**Yes** — via `ElevenLabsStreamingTTSConfig`|
16
+
| Gemini | — | — | — | — |
17
+
18
+
!!! note
19
+
**ElevenLabs is currently the only streaming TTS provider**, and **OpenAI is the only streaming STT provider**. Gemini has no standalone STT/TTS config in `ag2.live` — it is realtime-only, via `GeminiRealTimeConfig` and [LiveAgent](/docs/user-guide/live/live_agent).
20
+
21
+
Mixing providers across the two halves is supported and often the best setup: transcribe with OpenAI for live captions, speak with ElevenLabs for low time-to-first-byte.
22
+
23
+
ElevenLabs classes need the optional dependency and `ELEVENLABS_API_KEY` in the environment:
24
+
25
+
```bash
26
+
pip install "ag2[elevenlabs] sounddevice[numpy]"
27
+
```
28
+
8
29
## Audio I/O primitives
9
30
10
31
`SoundDeviceRecorder` captures microphone input and `SoundDevicePlayer` plays synthesized speech. Both are thin wrappers around the [`sounddevice`](https://python-sounddevice.readthedocs.io/){.external-linktarget="_blank"} library and share the same event stream.
@@ -71,6 +92,23 @@ from ag2.live import OpenAITranslationTranscriber
`language_code` is only sent when you set it — leave it unset and Scribe auto-detects. `diarize=True` asks for speaker labels.
106
+
107
+
!!! warning "Scribe is whole-file, so there are no partials"
108
+
The endpoint takes a **complete** recording, so there is nothing to emit until it answers. `ElevenLabsTranscriber` only ever sends `TranscriptionCompletedEvent` — never `TranscriptionChunkEvent`. Live-caption code written against the OpenAI transcriber's deltas will go quiet if you swap in Scribe.
109
+
110
+
It also blocks the whole turn: `VoicePipeline.ask` awaits `transcribe(...)` before the agent starts, so nothing can be spoken during the upload and transcription. On a short turn this often dominates the latency — see [Where the latency goes](#where-the-latency-goes).
111
+
74
112
## Text-to-Speech
75
113
76
114
`TTSObserver` is an observer that listens to `ModelMessageChunk` events as the agent streams its response, batches them into sentence-sized chunks, calls a TTS provider, and emits `SynthesizedAudioEvent`s onto the stream. A `SoundDevicePlayer` attached to the same stream then plays them.
@@ -104,8 +142,10 @@ if __name__ == "__main__":
104
142
105
143
### Voice and speed
106
144
107
-
`OpenAITTSConfig` accepts the standard OpenAI TTS parameters:
145
+
Any config implementing the `TTSConfig` protocol works with `TTSObserver`:
`ElevenLabsTTSConfig` is the buffered mode — required by `eleven_v3`, which cannot stream. For the flash and turbo families, prefer streaming (below).
174
+
175
+
!!! note
176
+
`output_format` defaults to `pcm_24000`, matching what `SoundDevicePlayer` and `SoundDeviceRecorder` expect. If you change it, change the player to match.
177
+
178
+
## Streaming TTS
179
+
180
+
`TTSObserver` still waits for each *sentence* to be fully synthesized before any of it plays. Streaming TTS removes that wait: audio chunks are forwarded as they are generated, so playback starts at time-to-first-byte.
181
+
182
+
!!! note
183
+
**ElevenLabs is currently the only provider with a streaming TTS config.**`OpenAITTSConfig` performs a single buffered read and has no streaming equivalent.
184
+
185
+
Streaming needs both a different config *and* a different observer:
186
+
187
+
```python linenums="1" hl_lines="6 12"
188
+
import asyncio
189
+
190
+
from ag2 import Agent, config
191
+
from ag2.live import ElevenLabsStreamingTTSConfig, ElevenLabsStreamingTTSObserver, SoundDevicePlayer
!!! warning "Pair the streaming config with the streaming observer"
211
+
`ElevenLabsStreamingTTSConfig` also implements `synthesize(...)`, so it satisfies the `TTSConfig` protocol and *will* work with the plain `TTSObserver` — but `synthesize` is implemented by draining the whole stream into one buffer, which throws away the entire latency advantage. Use `ElevenLabsStreamingTTSObserver` with `ElevenLabsStreamingTTSConfig`, and `TTSObserver` with `ElevenLabsTTSConfig` / `OpenAITTSConfig`.
212
+
213
+
### Streaming directly to the speaker
214
+
215
+
`ElevenLabsStreamingTTSConfig.stream(...)` is usable on its own, outside any agent, when you just want to speak a known string:
216
+
217
+
```python linenums="1" hl_lines="7-9"
218
+
import asyncio
219
+
220
+
from ag2.live import ElevenLabsStreamingTTSConfig, SoundDevicePlayer
asyncfor chunk in tts.stream("Audio arrives as it is generated."):
226
+
await player.play(chunk)
227
+
228
+
if__name__=="__main__":
229
+
asyncio.run(main())
230
+
```
231
+
232
+
`player.play(...)` only enqueues — a background worker thread writes to the output device, so this loop is never blocked by playback.
233
+
234
+
### Sentence batching
235
+
236
+
Both observers batch streamed text before synthesizing, since a per-token request would be slow and expensive. `min_chars` (default `60`) is the smallest amount of text worth a request; below it the buffer keeps accumulating even past a sentence boundary.
Lowering it starts the first sentence sooner, at the cost of more requests and more granular sentences.
243
+
244
+
## Where the latency goes
245
+
246
+
Streaming TTS removes one source of delay, not all of them. For a single turn, in order:
247
+
248
+
| Stage | Cost | Streaming TTS helps? |
249
+
|---|---|---|
250
+
| Transcription | With Scribe, the whole clip is uploaded and transcribed before the agent starts | No |
251
+
| Model time-to-first-token | Provider-dependent; large on reasoning models | No |
252
+
| Sentence buffer fill | Until `min_chars`**and** a sentence boundary is reached | No — lower `min_chars` instead |
253
+
| Synthesis | Time-to-first-byte vs. waiting for the full clip |**Yes** — this is the win |
254
+
255
+
!!! note
256
+
Synthesis is awaited inline in the observer, which runs inside the model's token loop. While one sentence is being synthesized the next tokens are not being consumed, so a long reply can still develop small gaps between sentences even in streaming mode.
257
+
119
258
## Combining STT and TTS
120
259
121
260
The full round-trip — voice in, voice out — is just both halves wired up at once: pipe the agent through the transcriber, attach a `TTSObserver`, and share a stream with the player.
@@ -166,6 +305,14 @@ if __name__ == "__main__":
166
305
!!! tip
167
306
`player.join()` blocks the main task until the synthesized audio queue has drained. Use it between turns when you want the assistant to finish speaking before the next recording starts — otherwise the recorder will capture the tail of the assistant's voice.
168
307
308
+
## Examples
309
+
310
+
-`examples/stt_tts.py/stt_tts.py` — the OpenAI round-trip
311
+
-`examples/stt_tts.py/elevenlabs_stt_tts_streaming.py` — streaming TTS, prints time-to-first-byte
312
+
-`examples/stt_tts.py/elevenlabs_stt_tts_sync.py` — buffered TTS with `eleven_v3`
313
+
314
+
Run the last two back to back to hear the difference on the first sentence.
315
+
169
316
## What's next
170
317
171
318
-**[LiveAgent](/docs/user-guide/live/live_agent)** — drop the turn-by-turn round-trip in favor of a streaming, full-duplex realtime session.
0 commit comments