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
`ag2.live` is the AG2 module for building voice-enabled agents. It covers two complementary patterns: a turn-by-turn **STT → Agent → TTS** pipeline built on top of a regular `Agent`, and a full-duplex **`LiveAgent`** that streams audio to and from a provider's realtime API.
6
+
`ag2.live` is the AG2 module for building voice-enabled agents. It covers two complementary patterns:
7
+
8
+
1.**CascadeTurn/Turn-by-Turn/Pipeline**`STT → Agent → TTS` approach built on top of a regular `Agent`
9
+
10
+
2.**Realtime/Speech-to-Speech/S2S**`LiveAgent` approach that streams audio to and from a provider's realtime API.
7
11
8
12
## When to use which
9
13
@@ -12,7 +16,7 @@ sidebarTitle: "Overview"
12
16
| 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
17
| Realtime full-duplex |`LiveAgent` + `OpenAIRealTimeConfig` / `GeminiRealTimeConfig`| <500ms| You want barge-in, interruption, semantic VAD, or a phone-call-like UX. |
14
18
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.
19
+
See [Provider support](/docs/user-guide/live/stt_tts) for the matrix of currently supported providers.
16
20
17
21
!!! note
18
22
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.
Copy file name to clipboardExpand all lines: website/docs/user-guide/live/stt_tts.mdx
+53-30Lines changed: 53 additions & 30 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -7,16 +7,12 @@ The STT → Agent → TTS flow turns any existing `Agent` into a voice agent wit
7
7
8
8
## Provider support
9
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
16
21
17
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.
`language_code` is only sent when you set it — leave it unset and Scribe auto-detects. `diarize=True` asks for speaker labels.
106
102
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
103
112
104
## Text-to-Speech
113
105
@@ -207,9 +199,6 @@ if __name__ == "__main__":
207
199
asyncio.run(main())
208
200
```
209
201
210
-
!!! 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
202
### Streaming directly to the speaker
214
203
215
204
`ElevenLabsStreamingTTSConfig.stream(...)` is usable on its own, outside any agent, when you just want to speak a known string:
Lowering it starts the first sentence sooner, at the cost of more requests and more granular sentences.
243
232
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
-
258
233
## Combining STT and TTS
259
234
260
235
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.
@@ -305,6 +280,54 @@ if __name__ == "__main__":
305
280
!!! tip
306
281
`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.
307
282
283
+
### Mixing providers
284
+
285
+
This is an example of how one can use `openai` STT and `elevenlabs` TTS together.
286
+
287
+
```python linenums="1" hl_lines="19 23 27 35"
288
+
import asyncio
289
+
290
+
from ag2 import Agent, config
291
+
from ag2.events import TranscriptionChunkEvent
292
+
from ag2.live import (
293
+
ElevenLabsStreamingTTSConfig,
294
+
ElevenLabsStreamingTTSObserver,
295
+
OpenAITranscriber,
296
+
SoundDevicePlayer,
297
+
SoundDeviceRecorder,
298
+
)
299
+
300
+
SAMPLE_RATE=24000# pcm_24000, what the player and recorder use
301
+
302
+
agent = Agent(
303
+
"assistant",
304
+
prompt="You are a helpful voice assistant. Keep answers to a few sentences.",
Both halves share one stream, so `TranscriptionChunkEvent` (from the transcriber) and `SynthesizedAudioEvent` (from the observer) land in the same place — the player only subscribes to the latter, leaving the captions to your own subscriber.
330
+
308
331
## Examples
309
332
310
333
-`examples/stt_tts.py/stt_tts.py` — the OpenAI round-trip
0 commit comments