Events:¶
Events are the fundamental units of information flow within the Agent Development Kit (ADK). They represent every significant occurrence during an agent's interaction lifecycle, from initial user input to the final response and all the steps in between. Understanding events is crucial because they are the primary way components communicate, state is managed, and control flow is directed.
What Events Are and Why They Matter¶
An Event
in ADK is an immutable record representing a specific point in the agent's execution. It captures user messages, agent replies, requests to use tools (function calls), tool results, state changes, control signals, and errors. Technically, it's an instance of the google.adk.events.Event
class, which builds upon the basic LlmResponse
structure by adding essential ADK-specific metadata and an actions
payload.
# Conceptual Structure of an Event
# from google.adk.events import Event, EventActions
# from google.genai import types
# class Event(LlmResponse): # Simplified view
# # --- LlmResponse fields ---
# content: Optional[types.Content]
# partial: Optional[bool]
# # ... other response fields ...
# # --- ADK specific additions ---
# author: str # 'user' or agent name
# invocation_id: str # ID for the whole interaction run
# id: str # Unique ID for this specific event
# timestamp: float # Creation time
# actions: EventActions # Important for side-effects & control
# branch: Optional[str] # Hierarchy path
# # ...
Events are central to ADK's operation for several key reasons:
- Communication: They serve as the standard message format between the user interface, the
Runner
, agents, the LLM, and tools. Everything flows as anEvent
. - Signaling State & Artifact Changes: Events carry instructions for state modifications via
event.actions.state_delta
and track artifact updates viaevent.actions.artifact_delta
. TheSessionService
uses these signals to ensure persistence. - Control Flow: Specific fields like
event.actions.transfer_to_agent
orevent.actions.escalate
act as signals that direct the framework, determining which agent runs next or if a loop should terminate. - History & Observability: The sequence of events recorded in
session.events
provides a complete, chronological history of an interaction, invaluable for debugging, auditing, and understanding agent behavior step-by-step.
In essence, the entire process, from a user's query to the agent's final answer, is orchestrated through the generation, interpretation, and processing of Event
objects.
Understanding and Using Events¶
As a developer, you'll primarily interact with the stream of events yielded by the Runner
. Here's how to understand and extract information from them:
Identifying Event Origin and Type¶
Quickly determine what an event represents by checking:
- Who sent it? (
event.author
)'user'
: Indicates input directly from the end-user.'AgentName'
: Indicates output or action from a specific agent (e.g.,'WeatherAgent'
,'SummarizerAgent'
).
- What's the main payload? (
event.content
andevent.content.parts
)- Text: If
event.content.parts[0].text
exists, it's likely a conversational message. - Tool Call Request: Check
event.get_function_calls()
. If not empty, the LLM is asking to execute one or more tools. Each item in the list has.name
and.args
. - Tool Result: Check
event.get_function_responses()
. If not empty, this event carries the result(s) from tool execution(s). Each item has.name
and.response
(the dictionary returned by the tool). Note: For history structuring, therole
inside thecontent
is often'user'
, but the eventauthor
is typically the agent that requested the tool call.
- Text: If
- Is it streaming output? (
event.partial
)True
: This is an incomplete chunk of text from the LLM; more will follow.False
orNone
: This part of the content is complete (though the overall turn might not be finished ifturn_complete
is also false).
# Pseudocode: Basic event identification
# async for event in runner.run_async(...):
# print(f"Event from: {event.author}")
#
# if event.content and event.content.parts:
# if event.get_function_calls():
# print(" Type: Tool Call Request")
# elif event.get_function_responses():
# print(" Type: Tool Result")
# elif event.content.parts[0].text:
# if event.partial:
# print(" Type: Streaming Text Chunk")
# else:
# print(" Type: Complete Text Message")
# else:
# print(" Type: Other Content (e.g., code result)")
# elif event.actions and (event.actions.state_delta or event.actions.artifact_delta):
# print(" Type: State/Artifact Update")
# else:
# print(" Type: Control Signal or Other")
Extracting Key Information¶
Once you know the event type, access the relevant data:
- Text Content:
text = event.content.parts[0].text
(Always checkevent.content
andevent.content.parts
first). - Function Call Details:
- Function Response Details:
- Identifiers:
event.id
: Unique ID for this specific event instance.event.invocation_id
: ID for the entire user-request-to-final-response cycle this event belongs to. Useful for logging and tracing.
Detecting Actions and Side Effects¶
The event.actions
object signals changes that occurred or should occur. Always check if event.actions
exists before accessing its fields.
- State Changes:
delta = event.actions.state_delta
gives you a dictionary of{key: value}
pairs that were modified in the session state during the step that produced this event. - Artifact Saves:
artifact_changes = event.actions.artifact_delta
gives you a dictionary of{filename: version}
indicating which artifacts were saved and their new version number. - Control Flow Signals: Check boolean flags or string values:
event.actions.transfer_to_agent
(string): Control should pass to the named agent.event.actions.escalate
(bool): A loop should terminate.event.actions.skip_summarization
(bool): A tool result should not be summarized by the LLM.
Determining if an Event is a "Final" Response¶
Use the built-in helper method event.is_final_response()
to identify events suitable for display as the agent's complete output for a turn.
- Purpose: Filters out intermediate steps (like tool calls, partial streaming text, internal state updates) from the final user-facing message(s).
- When
True
?- The event contains a tool result (
function_response
) andskip_summarization
isTrue
. - The event contains a tool call (
function_call
) for a tool marked asis_long_running=True
. - OR, all of the following are met:
- No function calls (
get_function_calls()
is empty). - No function responses (
get_function_responses()
is empty). - Not a partial stream chunk (
partial
is notTrue
). - Doesn't end with a code execution result that might need further processing/display.
- No function calls (
- The event contains a tool result (
-
Usage: Filter the event stream in your application logic.
# Pseudocode: Handling final responses in application # full_response_text = "" # async for event in runner.run_async(...): # # Accumulate streaming text if needed... # if event.partial and event.content and event.content.parts and event.content.parts[0].text: # full_response_text += event.content.parts[0].text # # # Check if it's a final, displayable event # if event.is_final_response(): # print("\n--- Final Output Detected ---") # if event.content and event.content.parts and event.content.parts[0].text: # # If it's the final part of a stream, use accumulated text # final_text = full_response_text + (event.content.parts[0].text if not event.partial else "") # print(f"Display to user: {final_text.strip()}") # full_response_text = "" # Reset accumulator # elif event.actions.skip_summarization: # # Handle displaying the raw tool result if needed # response_data = event.get_function_responses()[0].response # print(f"Display raw tool result: {response_data}") # elif event.long_running_tool_ids: # print("Display message: Tool is running in background...") # else: # # Handle other types of final responses if applicable # print("Display: Final non-textual response or signal.")
By carefully examining these aspects of an event, you can build robust applications that react appropriately to the rich information flowing through the ADK system.
How Events Flow: Generation and Processing¶
Events are created at different points and processed systematically by the framework. Understanding this flow helps clarify how actions and history are managed.
-
Generation Sources:
- User Input: The
Runner
typically wraps initial user messages or mid-conversation inputs into anEvent
withauthor='user'
. - Agent Logic: Agents (
BaseAgent
,LlmAgent
) explicitlyyield Event(...)
objects (settingauthor=self.name
) to communicate responses or signal actions. - LLM Responses: The ADK model integration layer (e.g.,
google_llm.py
) translates raw LLM output (text, function calls, errors) intoEvent
objects, authored by the calling agent. - Tool Results: After a tool executes, the framework generates an
Event
containing thefunction_response
. Theauthor
is typically the agent that requested the tool, while therole
inside thecontent
is set to'user'
for the LLM history.
- User Input: The
-
Processing Flow:
- Yield: An event is generated and yielded by its source.
- Runner Receives: The main
Runner
executing the agent receives the event. - SessionService Processing (
append_event
): TheRunner
sends the event to the configuredSessionService
. This is a critical step:- Applies Deltas: The service merges
event.actions.state_delta
intosession.state
and updates internal records based onevent.actions.artifact_delta
. (Note: The actual artifact saving usually happened earlier whencontext.save_artifact
was called). - Finalizes Metadata: Assigns a unique
event.id
if not present, may updateevent.timestamp
. - Persists to History: Appends the processed event to the
session.events
list.
- Applies Deltas: The service merges
- External Yield: The
Runner
yields the processed event outwards to the calling application (e.g., the code that invokedrunner.run_async
).
This flow ensures that state changes and history are consistently recorded alongside the communication content of each event.
Common Event Examples (Illustrative Patterns)¶
Here are concise examples of typical events you might see in the stream:
- User Input:
- Agent Final Text Response: (
is_final_response() == True
) - Agent Streaming Text Response: (
is_final_response() == False
) - Tool Call Request (by LLM): (
is_final_response() == False
) - Tool Result Provided (to LLM): (
is_final_response()
depends onskip_summarization
){ "author": "TravelAgent", // Author is agent that requested the call "invocation_id": "e-xyz...", "content": { "role": "user", // Role for LLM history "parts": [{"function_response": {"name": "find_airports", "response": {"result": ["LHR", "LGW", "STN"]}}}] } // actions might have skip_summarization=True }
- State/Artifact Update Only: (
is_final_response() == False
) - Agent Transfer Signal: (
is_final_response() == False
) - Loop Escalation Signal: (
is_final_response() == False
)
Additional Context and Event Details¶
Beyond the core concepts, here are a few specific details about context and events that are important for certain use cases:
-
ToolContext.function_call_id
(Linking Tool Actions):- When an LLM requests a tool (
FunctionCall
), that request has an ID. TheToolContext
provided to your tool function includes thisfunction_call_id
. - Importance: This ID is crucial for linking actions like authentication (
request_credential
,get_auth_response
) back to the specific tool request that initiated them, especially if multiple tools are called in one turn. The framework uses this ID internally.
- When an LLM requests a tool (
-
How State/Artifact Changes are Recorded:
- When you modify state (
context.state['key'] = value
) or save an artifact (context.save_artifact(...)
) usingCallbackContext
orToolContext
, these changes aren't immediately written to persistent storage. - Instead, they populate the
state_delta
andartifact_delta
fields within theEventActions
object. - This
EventActions
object is attached to the next event generated after the change (e.g., the agent's response or a tool result event). - The
SessionService.append_event
method reads these deltas from the incoming event and applies them to the session's persistent state and artifact records. This ensures changes are tied chronologically to the event stream.
- When you modify state (
-
State Scope Prefixes (
app:
,user:
,temp:
):- When managing state via
context.state
, you can optionally use prefixes:app:my_setting
: Suggests state relevant to the entire application (requires a persistentSessionService
).user:user_preference
: Suggests state relevant to the specific user across sessions (requires a persistentSessionService
).temp:intermediate_result
or no prefix: Typically session-specific or temporary state for the current invocation.
- The underlying
SessionService
determines how these prefixes are handled for persistence.
- When managing state via
-
Error Events:
- An
Event
can represent an error. Check theevent.error_code
andevent.error_message
fields (inherited fromLlmResponse
). - Errors might originate from the LLM (e.g., safety filters, resource limits) or potentially be packaged by the framework if a tool fails critically. Check tool
FunctionResponse
content for typical tool-specific errors.
- An
These details provide a more complete picture for advanced use cases involving tool authentication, state persistence scope, and error handling within the event stream.
Best Practices for Working with Events¶
To use events effectively in your ADK applications:
- Clear Authorship: When building custom agents (
BaseAgent
), ensureyield Event(author=self.name, ...)
to correctly attribute agent actions in the history. The framework generally handles authorship correctly for LLM/tool events. - Semantic Content & Actions: Use
event.content
for the core message/data (text, function call/response). Useevent.actions
specifically for signaling side effects (state/artifact deltas) or control flow (transfer
,escalate
,skip_summarization
). - Idempotency Awareness: Understand that the
SessionService
is responsible for applying the state/artifact changes signaled inevent.actions
. While ADK services aim for consistency, consider potential downstream effects if your application logic re-processes events. - Use
is_final_response()
: Rely on this helper method in your application/UI layer to identify complete, user-facing text responses. Avoid manually replicating its logic. - Leverage History: The
session.events
list is your primary debugging tool. Examine the sequence of authors, content, and actions to trace execution and diagnose issues. - Use Metadata: Use
invocation_id
to correlate all events within a single user interaction. Useevent.id
to reference specific, unique occurrences.
Treating events as structured messages with clear purposes for their content and actions is key to building, debugging, and managing complex agent behaviors in ADK.