Ereignisse
Events sind die fundamentalen Bausteine der Konversationshistorie in ADK-Rust. Jede Interaktion mit einem Agenten – sei es eine Benutzernachricht, eine Agentenantwort oder eine Tool-Ausführung – wird als ein Event aufgezeichnet. Events bilden ein unveränderliches Protokoll, das die vollständige Ausführungsverfolgung einer Agenten-Session festhält.
Übersicht
Das Event-System dient mehreren wichtigen Zwecken:
- Konversationshistorie: Events bilden die chronologische Aufzeichnung aller Interaktionen in einer Session
- Zustandsverwaltung: Events übermitteln Zustandsänderungen über das
state_delta-Feld - Artefakt-Verfolgung: Events zeichnen Artefakt-Operationen über das
artifact_delta-Feld auf - Agenten-Koordination: Events ermöglichen Agenten-Transfers und Eskalationen
- Debugging & Observability: Events bieten eine vollständige Prüfspur des Agentenverhaltens
Event-Struktur
Ein Event repräsentiert eine einzelne Interaktion in einer Konversation. ADK-Rust verwendet einen vereinheitlichten Event-Typ, der LlmResponse einbettet, entsprechend dem in ADK-Go verwendeten Designmuster:
pub struct Event {
pub id: String, // Unique event identifier (UUID)
pub timestamp: DateTime<Utc>, // When the event occurred
pub invocation_id: String, // Links related events in a single invocation
pub branch: String, // For future branching support
pub author: String, // Who created this event (user, agent name, tool name)
pub llm_response: LlmResponse, // Contains content and LLM metadata
pub actions: EventActions, // Side effects and metadata
pub long_running_tool_ids: Vec<String>, // IDs of long-running tools
}
Die LlmResponse-Struktur enthält:
pub struct LlmResponse {
pub content: Option<Content>, // The message content (text, parts, etc.)
pub usage_metadata: Option<UsageMetadata>,
pub finish_reason: Option<FinishReason>,
pub partial: bool, // True for streaming partial responses
pub turn_complete: bool, // True when the turn is complete
pub interrupted: bool, // True if generation was interrupted
pub error_code: Option<String>,
pub error_message: Option<String>,
}
Auf den Content wird konsistent über event.llm_response.content zugegriffen:
if let Some(content) = &event.llm_response.content {
for part in &content.parts {
if let Part::Text { text } = part {
println!("{}", text);
}
}
}
Schlüsselfelder
-
id: Eine eindeutige UUID, die dieses spezifische Event identifiziert. Wird zum Abrufen und Sortieren von Events verwendet.
-
timestamp: Der UTC-Zeitstempel, wann das Event erstellt wurde. Events werden in der Session chronologisch geordnet.
-
invocation_id: Gruppiert Events, die zur selben Agenten-Invocation gehören. Wenn ein Agent eine Nachricht verarbeitet, teilen alle generierten Events (Agentenantwort, Tool-Aufrufe, Sub-Agenten-Aufrufe) dieselbe
invocation_id. -
branch: Reserviert für zukünftige Branching-Funktionalität. Derzeit ungenutzt, ermöglicht aber Konversations-Branching in zukünftigen Versionen.
-
author: Identifiziert, wer das Event erstellt hat:
- Benutzernachrichten: typischerweise "user" oder ein Benutzer-Identifikator
- Agentenantworten: der Name des Agenten
- Tool-Ausführungen: der Name des Tools
- System-Events: "system"
-
llm_response: Enthält den Nachrichten-Content und LLM-Metadaten. Greifen Sie auf den Content über
event.llm_response.contentzu. DerContent-Typ kann Text, multimodale Parts (Bilder, Audio) oder strukturierte Daten enthalten. Einige Events (wie reine Zustandsaktualisierungen) könnencontent: Nonehaben.
EventActions
Das EventActions-Struct enthält Metadaten und Nebeneffekte, die mit einem Ereignis verbunden sind:
pub struct EventActions {
pub state_delta: HashMap<String, Value>, // State changes to apply
pub artifact_delta: HashMap<String, i64>, // Artifact version changes
pub skip_summarization: bool, // Skip this event in summaries
pub transfer_to_agent: Option<String>, // Transfer control to another agent
pub escalate: bool, // Escalate to human or supervisor
}
state_delta
Das state_delta-Feld enthält Schlüssel-Wert-Paare, die Änderungen am Session-State darstellen. Wenn ein Event zu einer Session hinzugefügt wird, werden diese Änderungen in den State der Session zusammengeführt.
State-Schlüssel können Präfixe verwenden, um den Geltungsbereich zu steuern:
app:key- App-bezogener State (wird von allen Benutzern geteilt)user:key- Benutzerbezogener State (wird von allen Sessions für einen Benutzer geteilt)temp:key- Temporärer State (wird zwischen Aufrufen gelöscht)- Kein Präfix - Session-bezogener State (Standard)
Example:
let mut actions = EventActions::default();
actions.state_delta.insert("user_name".to_string(), json!("Alice"));
actions.state_delta.insert("temp:current_step".to_string(), json!(3));
artifact_delta
Das artifact_delta-Feld verfolgt Änderungen an Artifacts. Schlüssel sind Artifact-Namen und Werte sind Versionsnummern. Dies ermöglicht dem System, zu verfolgen, welche Artifacts während eines Events erstellt oder modifiziert wurden.
Example:
actions.artifact_delta.insert("report.pdf".to_string(), 1);
actions.artifact_delta.insert("chart.png".to_string(), 2);
skip_summarization
Wenn true, wird dieses Event aus Gesprächszusammenfassungen ausgeschlossen. Nützlich für interne Events, Debugging-Informationen oder ausführliche Tool-Ausgaben, die nicht Teil des Hauptgesprächsflusses sein sollten.
transfer_to_agent
Wenn auf einen agent-Namen gesetzt, wird die Kontrolle an diesen agent übertragen. Dies ermöglicht Multi-agent-Workflows, bei denen ein agent die Kontrolle an einen anderen übergeben kann. Der Ziel-agent muss als Sub-agent konfiguriert sein.
Example:
actions.transfer_to_agent = Some("specialist_agent".to_string());
escalate
Wenn true, signalisiert dies, dass das Gespräch an einen menschlichen Operator oder Supervisor-agent eskaliert werden sollte. Das spezifische Eskalationsverhalten hängt von der Implementierung Ihrer Anwendung ab.
Conversation History Formation
Events bilden die Gesprächshistorie, indem sie in chronologischer Reihenfolge innerhalb einer Session akkumuliert werden. Wenn ein agent eine Anfrage bearbeitet:
- User Message Event: Ein neues Event wird mit der Benutzereingabe erstellt
- Agent Processing: Der agent empfängt die Gesprächshistorie (alle vorherigen Events)
- Agent Response Event: Die Antwort des agents wird als neues Event aufgezeichnet
- Tool Execution Events: Jeder Tool-Aufruf kann zusätzliche Events generieren
- State Updates: State-Deltas von allen Events werden in den Session-State zusammengeführt
Die Gesprächshistorie wird konstruiert durch:
- Abrufen aller Events aus der Session in chronologischer Reihenfolge
- Konvertieren des Inhalts jedes Events in das entsprechende Format für den LLM
- Einbeziehung von State-Informationen aus akkumulierten State-Deltas
- Herausfiltern von Events, die mit
skip_summarizationmarkiert sind, wenn angemessen
Event Flow Example
Session Start
↓
[Event 1] User: "What's the weather in Tokyo?"
↓
[Event 2] Agent: "Let me check that for you."
↓
[Event 3] Tool (weather_api): {"temp": 22, "condition": "sunny"}
↓
[Event 4] Agent: "It's 22°C and sunny in Tokyo."
↓
Session State Updated
Jedes Event baut auf den vorherigen auf und erstellt einen vollständigen Audit-Trail des Gesprächs.
Arbeiten mit Events
Zugriff auf Events aus einer Session
use adk_rust::session::{SessionService, GetRequest};
// Eine Session mit ihren Events abrufen
let session = session_service.get(GetRequest {
app_name: "my_app".to_string(),
user_id: "user_123".to_string(),
session_id: session_id.clone(),
num_recent_events: None, // Alle Events abrufen
after: None,
}).await?;
// Auf die Events zugreifen
let events = session.events();
println!("Total events: {}", events.len());
// Events durchlaufen (Hinweis: Session Events verwenden llm_response.content)
for i in 0..events.len() {
if let Some(event) = events.at(i) {
println!("Event {}: {} by {} at {}",
event.id,
event.llm_response.content.as_ref().map(|_| "has content").unwrap_or("no content"),
event.author,
event.timestamp
);
}
}
Event-Details prüfen
// Ein bestimmtes Event aus der Session abrufen (verwendet llm_response.content)
if let Some(event) = events.at(0) {
// Den Autor prüfen
println!("Author: {}", event.author);
// Inhalt prüfen (Session Events verwenden llm_response.content)
if let Some(content) = &event.llm_response.content {
for part in &content.parts {
if let Part::Text { text } = part {
println!("Text: {}", text);
}
}
}
// Auf Statusänderungen prüfen
if !event.actions.state_delta.is_empty() {
println!("State changes:");
for (key, value) in &event.actions.state_delta {
println!(" {} = {}", key, value);
}
}
// Auf Agent-Transfers prüfen
if let Some(target) = &event.actions.transfer_to_agent {
println!("Transfers to: {}", target);
}
// Auf Artefakte prüfen
if !event.actions.artifact_delta.is_empty() {
println!("Artifacts modified:");
for (name, version) in &event.actions.artifact_delta {
println!(" {} (v{})", name, version);
}
}
}
Event-Historie begrenzen
Für lange Unterhaltungen möchten Sie möglicherweise nur die neuesten Events abrufen:
// Nur die letzten 10 Events abrufen
let session = session_service.get(GetRequest {
app_name: "my_app".to_string(),
user_id: "user_123".to_string(),
session_id: session_id.clone(),
num_recent_events: Some(10),
after: None,
}).await?;
Wie Ereignisse fließen: Generierung und Verarbeitung
Das Verständnis, wie Ereignisse erstellt und verarbeitet werden, hilft zu klären, wie das Framework Aktionen und den Verlauf verwaltet.
Generierungsquellen
Ereignisse werden an verschiedenen Punkten im Lebenszyklus der Agenten-Ausführung erstellt:
- Benutzereingabe: Der Runner verpackt Benutzernachrichten in ein Event mit
author = "user" - Agenten-Antworten: Agents erzeugen Event-Objekte (setzen
author = agent.name()), um Antworten zu kommunizieren - LLM Output: Die Modellintegrationsschicht übersetzt LLM output (Text, Funktionsaufrufe) in Event-Objekte
- Tool Results: Nach der Tool-Ausführung generiert das Framework ein Event, das die Tool-Antwort enthält
Verarbeitungsfluss
Wenn ein Ereignis generiert wird, folgt es diesem Verarbeitungspfad:
- Generierung: Ein Ereignis wird von seiner Quelle (Agent, Tool oder Benutzereingabe-Handler) erstellt und ausgegeben
- Runner empfängt: Der Runner, der den Agent ausführt, empfängt das Ereignis
- SessionService Verarbeitung: Der Runner sendet das Ereignis an den SessionService, der:
- Deltas anwenden:
state_deltain den Session State zusammenführt und Artefakt-Aufzeichnungen aktualisiert - Metadaten finalisieren: Eine eindeutige
idzuweist, falls nicht vorhanden,timestampsetzt - Im Verlauf speichern: Das Ereignis an
session.eventsanhängt
- Deltas anwenden:
- Stream-Ausgabe: Der Runner gibt das verarbeitete Ereignis an die aufrufende Anwendung aus
Dieser Fluss stellt sicher, dass Statusänderungen und der Verlauf konsistent zusammen mit dem Kommunikationsinhalt aufgezeichnet werden.
// Konzeptioneller Fluss
User Input → Runner → Agent → LLM → Event Generated
↓
SessionService
- Apply state_delta
- Record in history
↓
Event Stream → Application
Ereignistypen identifizieren
Beim Verarbeiten von Ereignissen vom Runner möchten Sie identifizieren, um welchen Ereignistyp es sich handelt:
Nach Autor
Das Feld author gibt an, wer das Ereignis erstellt hat:
match event.author.as_str() {
"user" => println!("Benutzereingabe"),
agent_name => println!("Antwort vom Agent: {}", agent_name),
}
Nach Inhaltstyp
Überprüfen Sie das Feld llm_response.content, um den Payload-Typ zu bestimmen:
if let Some(content) = &event.llm_response.content {
// Auf Textinhalt prüfen
let has_text = content.parts.iter().any(|part| {
matches!(part, Part::Text { .. })
});
// Auf Funktionsaufrufe (Tool-Anfragen) prüfen
let has_function_call = content.parts.iter().any(|part| {
matches!(part, Part::FunctionCall { .. })
});
// Auf Funktionsantworten (Tool-Ergebnisse) prüfen
let has_function_response = content.parts.iter().any(|part| {
matches!(part, Part::FunctionResponse { .. })
});
if has_text {
println!("Textnachricht");
} else if has_function_call {
println!("Tool-Aufrufanfrage");
} else if has_function_response {
println!("Tool-Ergebnis");
}
}
Nach Aktionen
Überprüfen Sie das Feld actions auf Steuerungssignale und Nebenwirkungen:
// Statusänderungen
if !event.actions.state_delta.is_empty() {
println!("Ereignis enthält Statusänderungen");
}
// Agenten-Transfer
if let Some(target) = &event.actions.transfer_to_agent {
println!("Transfer zum Agent: {}", target);
}
// Eskalationssignal
if event.actions.escalate {
println!("Eskalation angefordert");
}
// Zusammenfassung überspringen
if event.actions.skip_summarization {
println!("Dieses Ereignis in Zusammenfassungen überspringen");
}
Arbeiten mit Ereignisströmen
Beim Ausführen eines Agents erhalten Sie einen Ereignisstrom. So verarbeiten Sie diese effektiv:
Verarbeiten von Ereignissen vom Runner
use futures::StreamExt;
let mut stream = runner.run(
"user_123".to_string(),
"session_id".to_string(),
user_input,
).await?;
while let Some(event_result) = stream.next().await {
match event_result {
Ok(event) => {
// Ereignis verarbeiten
println!("Event from: {}", event.author);
// Textinhalt extrahieren
if let Some(content) = &event.llm_response.content {
for part in &content.parts {
if let Part::Text { text } = part {
print!("{}", text);
}
}
}
// Auf Zustandsänderungen prüfen
if !event.actions.state_delta.is_empty() {
println!("\nState updated: {:?}", event.actions.state_delta);
}
}
Err(e) => {
eprintln!("Error: {}", e);
break;
}
}
}
Extrahieren von Funktionsaufrufen
Wenn das LLM ein Tool anfordert, enthält das Ereignis Informationen zum Funktionsaufruf:
if let Some(content) = &event.llm_response.content {
for part in &content.parts {
if let Part::FunctionCall { name, args } = part {
println!("Tool requested: {}", name);
println!("Arguments: {}", args);
// Ihre Anwendung könnte hier die Tool-Ausführung auslösen
// basierend auf dem Tool-Namen und den Argumenten
}
}
}
Extrahieren von Funktionsantworten
Nachdem ein Tool ausgeführt wurde, wird das Ergebnis in einer Funktionsantwort zurückgegeben:
if let Some(content) = &event.llm_response.content {
for part in &content.parts {
if let Part::FunctionResponse { function_response, .. } = part {
println!("Tool result from: {}", function_response.name);
println!("Response: {}", function_response.response);
// Das Tool-Ergebnis verarbeiten
// Das LLM wird dies nutzen, um die Konversation fortzusetzen
}
}
}
Häufige Ereignismuster
Hier sind typische Ereignissequenzen, denen Sie begegnen werden:
Einfacher Textaustausch
[Event 1] author="user", content=Text("Hello")
[Event 2] author="assistant", content=Text("Hi! How can I help?")
Tool-Nutzungsfluss
[Event 1] author="user", content=Text("What's the weather?")
[Event 2] author="assistant", content=FunctionCall(name="get_weather", args={...})
[Event 3] author="assistant", content=FunctionResponse(name="get_weather", response={...})
[Event 4] author="assistant", content=Text("It's sunny and 72°F")
Zustandsaktualisierung
[Event 1] author="assistant", content=Text("I've saved your preference")
actions.state_delta={"user_theme": "dark"}
Agentenübertragung
[Event 1] author="router", content=Text("Transferring to specialist")
actions.transfer_to_agent=Some("specialist_agent")
[Event 2] author="specialist_agent", content=Text("I can help with that")
Ereignis-Metadaten und -Identifikatoren
Ereignis-ID
Jedes Ereignis hat eine eindeutige id (UUID) zur präzisen Identifizierung:
println!("Event ID: {}", event.id);
Aufruf-ID
Die invocation_id gruppiert alle Ereignisse von einer einzelnen Benutzeranfrage bis zur endgültigen Antwort:
// Alle Ereignisse in einer Interaktion teilen sich dieselbe invocation_id
println!("Invocation: {}", event.invocation_id);
// Dies für die Protokollierung und Nachverfolgung verwenden
log::info!("Processing event {} in invocation {}", event.id, event.invocation_id);
Zeitstempel
Ereignisse werden für die chronologische Reihenfolge mit einem Zeitstempel versehen:
println!("Event occurred at: {}", event.timestamp.format("%Y-%m-%d %H:%M:%S"));
Bewährte Praktiken
-
Event Immutability: Ereignisse sollten niemals nach ihrer Erstellung geändert werden. Sie bilden ein unveränderliches Prüfprotokoll.
-
State Management: Verwenden Sie
state_deltafür alle Zustandsänderungen, anstatt den Zustand direkt zu ändern. Dies stellt sicher, dass Änderungen im Ereignisprotokoll verfolgt werden. -
Meaningful Authors: Verwenden Sie klare, beschreibende Autorennamen, um Ereignisprotokolle leichter verständlich zu machen.
-
Selective Summarization: Verwenden Sie
skip_summarizationfür ausführliche oder interne Ereignisse, die den Konversationsverlauf überladen würden. -
Invocation Grouping: Behalten Sie dieselbe
invocation_idfür alle Ereignisse bei, die während einer einzelnen Agent-Aufrufs generiert werden, um eine logische Gruppierung aufrechtzuerhalten. -
Artifact Tracking: Aktualisieren Sie immer
artifact_delta, wenn Sie artifacts erstellen oder ändern, um die Konsistenz zu wahren. -
Stream Processing: Behandeln Sie immer Fehler bei der Verarbeitung von Ereignisströmen. Ereignisse können aufgrund von LLM-Fehlern, Tool-Fehlern oder Netzwerkproblemen fehlschlagen.
-
Content Checking: Überprüfen Sie immer, ob
llm_response.contentden WertSomehat, bevor Sie auf Parts zugreifen. Einige Ereignisse (wie reine Zustandsaktualisierungen) haben möglicherweise keinen Content. -
Pattern Matching: Verwenden Sie Rusts pattern matching, um verschiedene Ereignistypen und Content-Parts elegant zu behandeln.
-
Logging: Verwenden Sie
invocation_id, um alle Ereignisse innerhalb einer einzelnen Benutzerinteraktion für die Fehlersuche und Observability zu korrelieren.
Zugehörige Dokumentation
- Sessions - Sitzungsverwaltung und Lebenszyklus
- State Management - Arbeiten mit dem Sitzungszustand
- Artifacts - Verwalten von Binärdaten
- Multi-Agent Systems - Agent-Transfers und -Koordination
- Callbacks - Abfangen und Ändern von Ereignissen
Vorherige: ← Artifacts | Nächste: Telemetry →