Events
Working with Events
Practical patterns and examples for handling events in ADK applications
This guide provides practical patterns for handling events in your ADK applications, from basic event processing to advanced patterns for complex workflows.
Event Processing Basics
Event Identification
First, understand what type of event you're dealing with:
import { Event } from '@iqai/adk';
function processEvent(event: Event) {
console.log(`Event from: ${event.author}`);
console.log(`Event ID: ${event.id}`);
console.log(`Invocation ID: ${event.invocationId}`);
// Check event type
if (event.content?.parts) {
const functionCalls = event.getFunctionCalls();
const functionResponses = event.getFunctionResponses();
if (functionCalls.length > 0) {
console.log("Type: Tool Call Request");
functionCalls.forEach(call => {
console.log(` Tool: ${call.name}`, call.args);
});
} else if (functionResponses.length > 0) {
console.log("Type: Tool Result");
functionResponses.forEach(response => {
console.log(` Result from: ${response.name}`, response.response);
});
} else if (event.content.parts[0]?.text) {
console.log(event.partial ? "Type: Streaming Text" : "Type: Complete Text");
}
}
}
Extracting Content
Access different types of content from events:
function extractText(event: Event): string | null {
if (event.content?.parts?.[0]?.text) {
return event.content.parts[0].text;
}
return null;
}
// Usage
const text = extractText(event);
if (text) {
console.log("Message:", text);
}
function processFunctionCalls(event: Event) {
const calls = event.getFunctionCalls();
for (const call of calls) {
console.log(`Tool requested: ${call.name}`);
console.log(`Arguments:`, call.args);
// Dispatch based on tool name
switch (call.name) {
case 'search':
handleSearchRequest(call.args);
break;
case 'sendEmail':
handleEmailRequest(call.args);
break;
default:
console.log(`Unknown tool: ${call.name}`);
}
}
}
function processFunctionResponses(event: Event) {
const responses = event.getFunctionResponses();
for (const response of responses) {
console.log(`Tool ${response.name} returned:`, response.response);
// Handle specific tool responses
if (response.name === 'search') {
displaySearchResults(response.response);
} else if (response.name === 'sendEmail') {
showEmailConfirmation(response.response);
}
}
}
Event Filtering Patterns
Final Response Detection
Use the built-in helper to identify display-ready events:
async function processAgentConversation(runner, query, session) {
let streamingText = '';
for await (const event of runner.runAsync(query, session)) {
// Handle streaming content
if (event.partial && event.content?.parts?.[0]?.text) {
streamingText += event.content.parts[0].text;
updateUI(streamingText); // Real-time updates
}
// Handle final responses
if (event.isFinalResponse()) {
if (event.content?.parts?.[0]?.text) {
// Complete text response
const finalText = streamingText + (event.content.parts[0].text || '');
displayFinalMessage(finalText.trim());
streamingText = ''; // Reset
} else if (event.actions?.skipSummarization) {
// Raw tool result
const toolResponse = event.getFunctionResponses()[0];
displayToolResult(toolResponse.response);
} else if (event.longRunningToolIds) {
// Background process
showProcessingIndicator();
}
}
// Handle actions
processEventActions(event);
}
}
Custom Event Filters
Create filters for specific event types:
// Filter by author
const agentEvents = events.filter(event => event.author !== 'user');
const userEvents = events.filter(event => event.author === 'user');
// Filter by content type
const textEvents = events.filter(event =>
event.content?.parts?.[0]?.text && !event.partial
);
const toolEvents = events.filter(event =>
event.getFunctionCalls().length > 0 ||
event.getFunctionResponses().length > 0
);
// Filter by time range
const recentEvents = events.filter(event =>
event.timestamp > (Date.now() / 1000) - 3600 // Last hour
);
State and Action Handling
Processing State Changes
Monitor and react to state updates:
function handleStateChanges(event: Event) {
if (!event.actions?.stateDelta) return;
const changes = event.actions.stateDelta;
for (const [key, value] of Object.entries(changes)) {
console.log(`State changed: ${key} = ${value}`);
// React to specific state changes
switch (key) {
case 'user_preference':
updateUserInterface(value);
break;
case 'task_status':
updateTaskProgress(value);
break;
case 'app:theme':
applyTheme(value);
break;
}
}
}
Artifact Tracking
Track file operations:
function handleArtifactChanges(event: Event) {
if (!event.actions?.artifactDelta) return;
const artifacts = event.actions.artifactDelta;
for (const [filename, version] of Object.entries(artifacts)) {
console.log(`File updated: ${filename} (version ${version})`);
// Refresh file displays
if (filename.endsWith('.pdf')) {
refreshPdfViewer(filename);
} else if (filename.endsWith('.json')) {
refreshDataView(filename);
}
}
}
Advanced Event Patterns
Event Aggregation
Combine related events for analysis:
class EventAggregator {
private events: Event[] = [];
addEvent(event: Event) {
this.events.push(event);
}
getConversationSummary(invocationId: string) {
const conversationEvents = this.events.filter(
event => event.invocationId === invocationId
);
return {
totalEvents: conversationEvents.length,
userMessages: conversationEvents.filter(e => e.author === 'user').length,
agentResponses: conversationEvents.filter(e => e.author !== 'user').length,
toolCalls: conversationEvents.reduce(
(sum, e) => sum + e.getFunctionCalls().length, 0
),
duration: this.getConversationDuration(conversationEvents)
};
}
private getConversationDuration(events: Event[]): number {
if (events.length < 2) return 0;
const sorted = events.sort((a, b) => a.timestamp - b.timestamp);
return sorted[sorted.length - 1].timestamp - sorted[0].timestamp;
}
}
Event Replay
Recreate conversation state from event history:
function replayEvents(events: Event[]): Record<string, any> {
const state: Record<string, any> = {};
for (const event of events.sort((a, b) => a.timestamp - b.timestamp)) {
if (event.actions?.stateDelta) {
for (const [key, value] of Object.entries(event.actions.stateDelta)) {
if (!key.startsWith('temp_')) {
state[key] = value;
}
}
}
}
return state;
}
Error Event Handling
Handle errors gracefully:
function processEventSafely(event: Event) {
try {
// Check for error conditions
if (event.errorCode) {
handleEventError(event);
return;
}
// Normal processing
processEvent(event);
} catch (error) {
console.error('Error processing event:', error);
logEventError(event, error);
}
}
function handleEventError(event: Event) {
console.error(`Event error: ${event.errorCode}`);
console.error(`Message: ${event.errorMessage}`);
// Show user-friendly error message
switch (event.errorCode) {
case 'SAFETY_FILTER_TRIGGERED':
showSafetyWarning();
break;
case 'RATE_LIMIT_EXCEEDED':
showRateLimitMessage();
break;
default:
showGenericError(event.errorMessage);
}
}
Building Event-Driven UIs
React Integration
import { useState, useEffect } from 'react';
function ConversationComponent({ runner, query, session }) {
const [events, setEvents] = useState<Event[]>([]);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
async function runConversation() {
setIsLoading(true);
const newEvents: Event[] = [];
try {
for await (const event of runner.runAsync(query, session)) {
newEvents.push(event);
setEvents([...newEvents]); // Update UI with each event
}
} finally {
setIsLoading(false);
}
}
runConversation();
}, [query]);
return (
<div>
{events.map(event => (
<EventDisplay key={event.id} event={event} />
))}
{isLoading && <LoadingIndicator />}
</div>
);
}
Best Practices
Performance
- Filter early: Apply filters before processing heavy operations
- Batch updates: Group related state changes when possible
- Cleanup: Remove event listeners and clear large event arrays
Error Handling
- Graceful degradation: Continue processing other events if one fails
- User feedback: Provide clear error messages for failed operations
- Logging: Record detailed error information for debugging
Memory Management
- Event retention: Only keep necessary events in memory
- Pagination: Use pagination for large event histories
- Cleanup: Clear temporary state and cached data
Events provide the foundation for building responsive, interactive agent applications with ADK.