Events for live agents¶
Everything a live agent produces reaches your application as an Event: partial text as
the model composes it, raw audio bytes, transcriptions of both sides of the conversation,
tool calls, token counts, and errors. A single spoken reply can arrive as dozens of events,
and handling them correctly is what makes a voice interface feel immediate rather than
laggy.
Event is the same class ADK uses everywhere, documented in Events.
A live session fills in fields a request/response agent never touches — audio blobs,
transcriptions, interruption flags — and delivers them continuously instead of once. For the
loop that yields them, see Sessions.
Live agent event data¶
An Event is a Pydantic
model that extends LlmResponse. Live sessions use these fields:
| Field | What it holds |
|---|---|
content.parts[].text |
Text parts — in a live session, thought summaries and other non-spoken content |
content.parts[].inline_data |
Raw audio bytes for playback (ephemeral) |
content.parts[].file_data |
Reference to audio saved in artifacts (when save_live_blob=True) |
content.parts[].function_call / function_response |
Tool invocations and results (ADK executes these for you) |
input_transcription / output_transcription |
User and model speech as text |
partial |
True for an incremental chunk, False for the merged result |
turn_complete |
True when the model has finished its whole response |
interrupted |
True when the user barged in mid-response |
usage_metadata |
Token counts for cost and quota tracking |
error_code / error_message |
Failure diagnostics |
author |
Who produced the event (see below) |
Authorship¶
In a live session, event.author is "user" for transcribed user speech and the agent's
name (not the literal "model") for the model's own output. ADK sets author="user"
whenever the response carries an input_transcription or content.role == 'user'; checking
the transcription is what makes attribution reliable, since an input-transcription response
does not always carry role == 'user'
(base_llm_flow.py).
Using the agent name lets you filter by author in multi-agent sessions:
Event types¶
During a live session, an agent delivers its continuous output through several distinct event types, which can include partial text, audio, speech transcriptions, tool calls, and token usage metadata. The following sections describe these event types.
During a live session, an agent delivers its continuous output through several distinct event types, which can include partial text, audio, speech transcriptions, tool calls, and token usage metadata. The following sections describe these event types.
Text¶
Text arrives on event.content.parts[].text. In a live session this is thought summaries
and other non-spoken content — the model's spoken reply comes back as an
output transcription, not a text part, because
every Live model ADK supports takes audio in and produces audio out.
async for event in runner.run_live(...):
if event.content and event.content.parts:
for part in event.content.parts:
if part.text and not event.partial:
update_display(part.text)
Iterate over parts, never assume parts[0]
A single event can carry several parts, and Live models do this routinely.
event.content.parts[0].text silently drops the rest and breaks when the first part is
not text (a thought summary, a function call, an audio blob). Loop over the parts and
branch on which field is set.
Audio¶
With response_modalities=["AUDIO"] (the live default), the model returns audio as
inline_data:
async for event in runner.run_live(...):
if event.content and event.content.parts:
for part in event.content.parts:
if part.inline_data: # raw PCM bytes
await play_audio(part.inline_data.data)
inline_data is ephemeral and never persisted. Set
save_live_blob=True and ADK aggregates audio into files
in the artifact service, delivering a file_data reference instead of (not in addition to)
raw bytes so you can retrieve the audio later. For formats and playback, see
Audio and video.
Transcription¶
When transcription is enabled (on by default), user and model speech arrive on
event.input_transcription and event.output_transcription. They stream in fragments:
.text holds the latest fragment and .finished marks the last for the turn, mirrored by
event.partial. Concatenate the fragments to build the full transcript. See
Audio transcription.
async for event in runner.run_live(...):
if event.input_transcription and event.input_transcription.text:
show_caption(event.input_transcription.text, is_user=True)
if event.output_transcription and event.output_transcription.text:
show_caption(event.output_transcription.text, is_user=False)
Tool calls¶
The model requests a tool through part.function_call. ADK executes registered tools
automatically, so you rarely handle these directly. See
Automatic tool execution.
Metadata¶
event.usage_metadata carries token counts (prompt_token_count,
candidates_token_count, total_token_count, cached_content_token_count) for real-time
cost and quota tracking.
Streaming flags¶
Three flags drive a live UI: partial, turn_complete, and interrupted.
The partial flag distinguishes an incremental chunk from the merged result:
partial=True: only the new text since the last event.partial=False: the full merged text for this segment.
ADK accumulates the chunks for you (StreamingResponseAggregator), so a partial=False
event already holds the sum of the preceding partial=True chunks. If you do not need a
live typing effect, ignore the partials and act only on partial=False.
Event 1: partial=True, text="Hello", turn_complete=False
Event 2: partial=True, text=" world", turn_complete=False
Event 3: partial=False, text="Hello world", turn_complete=False
Event 4: partial=False, text="", turn_complete=True
A partial=False condition can occur several times per turn (once per sentence, for example), and
turn_complete=True arrives once, in its own event, after the last segment.
turn_complete and interrupted tell your UI what state to enter:
| turn_complete | interrupted | Your app should |
|---|---|---|
| True | False | Enable input, show "ready" |
| False | True | Stop playback, clear partial content |
| True | True | Turn is done; same as normal completion |
| False | False | Keep displaying streaming text |
async for event in runner.run_live(...):
if event.interrupted:
stop_audio_playback() # user barged in; drop queued audio
clear_streaming_text()
if event.turn_complete:
enable_microphone() # ready for the next turn
Without handling interrupted, already-buffered audio keeps playing over the user.
Handling errors¶
Errors surface on event.error_code and event.error_message. The one decision to make is
whether the model's response can continue: break when the model has stopped, continue
when the failure is transient.
try:
async for event in runner.run_live(...):
if event.error_code:
logger.error("Model error: %s - %s", event.error_code, event.error_message)
if event.error_code in ("SAFETY", "PROHIBITED_CONTENT", "BLOCKLIST", "MAX_TOKENS"):
break # Model terminated; no more events this turn.
continue # Transient; the stream may recover.
# ... handle content ...
finally:
live_request_queue.close() # Runs whether you break or finish.
| Error code | Category | Action |
|---|---|---|
SAFETY, PROHIBITED_CONTENT, BLOCKLIST |
Content policy | break — model terminated the response |
MAX_TOKENS |
Limit | break — model finished generating |
UNAVAILABLE, DEADLINE_EXCEEDED |
Transient | continue — network or timeout, may self-resolve |
RESOURCE_EXHAUSTED |
Rate limit | continue with exponential backoff |
CANCELLED |
Client | break — clean up |
UNKNOWN |
System | continue with logging |
For transient errors under a second, do not notify the user. For RESOURCE_EXHAUSTED, back
off and cap retries so you do not loop forever. Error codes come from the Gemini API; see
FinishReason
and the Agent Platform reference.
Sending events to a client¶
To stream events to a browser or mobile client, serialize them and send over your transport.
Event is a Pydantic model, so model_dump_json() does the work; base64-encoded audio
inflates JSON by ~33%, so send audio as binary frames. Both the serialization patterns and
the matching client-side handling live in
Custom server.