Événements
Les événements sont les éléments constitutifs fondamentaux de l'historique de conversation dans adk-rust. Chaque interaction avec un agent — qu'il s'agisse d'un message utilisateur, d'une réponse d'agent ou d'une exécution de Tool — est enregistrée comme un événement. Les événements forment un journal immuable qui capture la trace d'exécution complète d'une Session d'agent.
Aperçu
Le système d'événements remplit plusieurs objectifs critiques :
- Historique de conversation : Les événements constituent l'enregistrement chronologique de toutes les interactions d'une Session
- Gestion de l'état : Les événements transportent les changements d'état via le champ
state_delta - Suivi des artefacts : Les événements enregistrent les opérations d'artefacts via le champ
artifact_delta - Coordination des Agents : Les événements permettent les transferts et les escalades d'Agent
- Débogage et Observabilité : Les événements fournissent une piste d'audit complète du comportement de l'Agent
Structure d'événement
Un Event représente une seule interaction dans une conversation. adk-rust utilise un type Event unifié qui intègre LlmResponse, correspondant au modèle de conception utilisé dans ADK-Go :
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
}
La structure LlmResponse contient :
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>,
}
Le Content est accédé de manière cohérente via event.llm_response.content :
if let Some(content) = &event.llm_response.content {
for part in &content.parts {
if let Part::Text { text } = part {
println!("{}", text);
}
}
}
Champs Clés
-
id : Un
UUIDunique identifiant cet événement spécifique. Utilisé pour la récupération et l'ordonnancement des événements. -
timestamp : L'horodatage
UTCde la création de l'événement. Les événements sont classés chronologiquement dans la Session. -
invocation_id : Regroupe les événements appartenant à la même invocation d'Agent. Lorsqu'un Agent traite un message, tous les événements générés (réponse de l'Agent, appels de Tool, appels de sous-Agent) partagent le même
invocation_id. -
branch : Réservé pour de futures fonctionnalités de branching. Actuellement inutilisé mais permet le branching de conversation dans les futures versions.
-
author : Identifie qui a créé l'événement :
- Messages utilisateur : typiquement "user" ou un identifiant utilisateur
- Réponses d'Agent : le nom de l'Agent
- Exécutions de Tool : le nom du Tool
- Événements système : "system"
-
llm_response : Contient le Content du message et les métadonnées
LLM. Accédez au Content viaevent.llm_response.content. Le typeContentpeut contenir du texte, des multimodal Parts (images, audio) ou des données structurées. Certains événements (comme les mises à jour d'état pures) peuvent avoircontent: None.
EventActions
La structure EventActions contient des métadonnées et des effets secondaires associés à un événement :
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
Le champ state_delta contient des paires clé-valeur représentant les modifications de l'état de la Session. Lorsqu'un événement est ajouté à une Session, ces modifications sont fusionnées dans l'état de la Session.
Les clés d'état peuvent utiliser des préfixes pour contrôler la portée :
app:key- État de portée application (partagé entre tous les utilisateurs)user:key- État de portée utilisateur (partagé entre toutes les Sessions pour un utilisateur)temp:key- État temporaire (effacé entre les invocations)- Pas de préfixe - État de portée Session (par défaut)
Exemple :
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
Le champ artifact_delta suit les modifications des artifacts. Les clés sont les noms des artifacts, et les valeurs sont les numéros de version. Cela permet au système de suivre quels artifacts ont été créés ou modifiés pendant un événement.
Exemple :
actions.artifact_delta.insert("report.pdf".to_string(), 1);
actions.artifact_delta.insert("chart.png".to_string(), 2);
skip_summarization
Lorsque true, cet événement sera exclu des résumés de conversation. Utile pour les événements internes, les informations de débogage ou les sorties d'Agent verbeuses qui ne devraient pas faire partie du flux de conversation principal.
transfer_to_agent
Lorsqu'il est défini sur un nom d'Agent, le contrôle est transféré à cet Agent. Cela permet des workflows multi-Agent où un Agent peut passer le relais à un autre. L'Agent cible doit être configuré comme un sous-Agent.
Exemple :
actions.transfer_to_agent = Some("specialist_agent".to_string());
escalate
Lorsque true, signale que la conversation doit être escaladée à un opérateur humain ou à un Agent superviseur. Le comportement d'escalade spécifique dépend de l'implémentation de votre application.
Formation de l'historique de conversation
Les événements forment l'historique de conversation en s'accumulant dans l'ordre chronologique au sein d'une Session. Lorsqu'un Agent traite une requête :
- Événement de message utilisateur : Un nouvel événement est créé avec l'entrée de l'utilisateur
- Traitement par l'Agent : L'Agent reçoit l'historique de conversation (tous les événements précédents)
- Événement de réponse de l'Agent : La réponse de l'Agent est enregistrée comme un nouvel événement
- Événements d'exécution de Tool : Chaque appel de Tool peut générer des événements supplémentaires
- Mises à jour d'état : Les deltas d'état de tous les événements sont fusionnés dans l'état de la Session
L'historique de conversation est construit par :
- La récupération de tous les événements de la Session dans l'ordre chronologique
- La conversion du contenu de chaque événement dans le format approprié pour le LLM
- L'inclusion des informations d'état des deltas d'état accumulés
- Le filtrage des événements marqués
skip_summarizationlorsque cela est approprié
Exemple de flux d'événements
Début de Session
↓
[Event 1] Utilisateur : "Quel temps fait-il à Tokyo ?"
↓
[Event 2] Agent : "Laissez-moi vérifier pour vous."
↓
[Event 3] Tool (weather_api) : {"temp": 22, "condition": "sunny"}
↓
[Event 4] Agent : "Il fait 22°C et ensoleillé à Tokyo."
↓
État de Session mis à jour
Chaque événement s'appuie sur les précédents, créant une piste d'audit complète de la conversation.
Travailler avec les événements
Accéder aux événements depuis une Session
use adk_rust::session::{SessionService, GetRequest};
// Récupérer une session avec ses événements
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, // Obtenir tous les événements
after: None,
}).await?;
// Accéder aux événements
let events = session.events();
println!("Total events: {}", events.len());
// Itérer sur les événements (note : les événements de session utilisent 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
);
}
}
Inspecter les détails d'un événement
// Obtenir un événement spécifique de la session (utilise llm_response.content)
if let Some(event) = events.at(0) {
// Vérifier l'auteur
println!("Author: {}", event.author);
// Vérifier le contenu (les événements de session utilisent 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);
}
}
}
// Vérifier les changements d'état
if !event.actions.state_delta.is_empty() {
println!("State changes:");
for (key, value) in &event.actions.state_delta {
println!(" {} = {}", key, value);
}
}
// Vérifier les transferts d'Agent
if let Some(target) = &event.actions.transfer_to_agent {
println!("Transfers to: {}", target);
}
// Vérifier les artefacts
if !event.actions.artifact_delta.is_empty() {
println!("Artifacts modified:");
for (name, version) in &event.actions.artifact_delta {
println!(" {} (v{})", name, version);
}
}
}
Limiter l'historique des événements
Pour les longues conversations, vous pouvez souhaiter récupérer uniquement les événements récents :
// Obtenir uniquement les 10 derniers événements
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?;
Comment les événements circulent : Génération et Traitement
Comprendre comment les événements sont créés et traités aide à clarifier la façon dont le framework gère les actions et l'historique.
Sources de Génération
Les événements sont créés à différents moments du cycle de vie de l'exécution d'un agent :
- Entrée Utilisateur : Le Runner enveloppe les messages utilisateur dans un Event avec
author = "user". - Réponses des Agents : Les Agents produisent des objets Event (en définissant
author = agent.name()) pour communiquer les réponses. - Sortie du LLM : La couche d'intégration du modèle traduit la sortie du LLM (texte, appels de fonction) en objets Event.
- Résultats d'Outil : Après l'exécution d'un outil, le framework génère un Event contenant la réponse de l'outil.
Flux de Traitement
Lorsqu'un événement est généré, il suit ce chemin de traitement :
- Génération : Un événement est créé et produit par sa source (agent, outil ou gestionnaire d'entrée utilisateur).
- Le Runner Reçoit : Le Runner exécutant l'agent reçoit l'événement.
- Traitement par le SessionService : Le Runner envoie l'événement au SessionService, qui :
- Applique les Deltas : Fusionne
state_deltadans l'état de la session et met à jour les enregistrements d'artefacts. - Finalise les Métadonnées : Attribue un
idunique s'il n'est pas présent, définit letimestamp. - Persiste dans l'Historique : Ajoute l'événement à
session.events.
- Applique les Deltas : Fusionne
- Sortie du Flux : Le Runner produit l'événement traité vers l'application appelante.
Ce flux garantit que les changements d'état et l'historique sont enregistrés de manière cohérente parallèlement au contenu de la communication.
// Conceptual flow
User Input → Runner → Agent → LLM → Event Generated
↓
SessionService
- Apply state_delta
- Record in history
↓
Event Stream → Application
Identification des Types d'Événements
Lorsque vous traitez des événements provenant du Runner, vous voudrez identifier le type d'événement auquel vous avez affaire :
Par Auteur
Le champ author vous indique qui a créé l'événement :
match event.author.as_str() {
"user" => println!("User input"),
agent_name => println!("Response from agent: {}", agent_name),
}
Par Type de Contenu
Vérifiez le champ llm_response.content pour déterminer le type de charge utile :
if let Some(content) = &event.llm_response.content {
// Check for text content
let has_text = content.parts.iter().any(|part| {
matches!(part, Part::Text { .. })
});
// Check for function calls (tool requests)
let has_function_call = content.parts.iter().any(|part| {
matches!(part, Part::FunctionCall { .. })
});
// Check for function responses (tool results)
let has_function_response = content.parts.iter().any(|part| {
matches!(part, Part::FunctionResponse { .. })
});
if has_text {
println!("Text message");
} else if has_function_call {
println!("Tool call request");
} else if has_function_response {
println!("Tool result");
}
}
Par Actions
Vérifiez le champ actions pour les signaux de contrôle et les effets secondaires :
// State changes
if !event.actions.state_delta.is_empty() {
println!("Event contains state changes");
}
// Agent transfer
if let Some(target) = &event.actions.transfer_to_agent {
println!("Transfer to agent: {}", target);
}
// Escalation signal
if event.actions.escalate {
println!("Escalation requested");
}
// Skip summarization
if event.actions.skip_summarization {
println!("Skip this event in summaries");
}
Travailler avec des flux d'événements
Lors de l'exécution d'un Agent, vous recevez un flux d'événements. Voici comment les traiter efficacement :
Traitement des événements depuis le 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) => {
// Process the event
println!("Event from: {}", event.author);
// Extract text content
if let Some(content) = &event.llm_response.content {
for part in &content.parts {
if let Part::Text { text } = part {
print!("{}", text);
}
}
}
// Check for state changes
if !event.actions.state_delta.is_empty() {
println!("\nState updated: {:?}", event.actions.state_delta);
}
}
Err(e) => {
eprintln!("Error: {}", e);
break;
}
}
}
Extraction des appels de fonctions
Lorsque le LLM demande un Tool, l'événement contient des informations d'appel de fonction :
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);
// Your application might dispatch tool execution here
// based on the tool name and arguments
}
}
}
Extraction des réponses de fonction
Après l'exécution d'un Tool, le résultat est renvoyé dans une réponse de fonction :
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);
// Process the tool result
// The LLM will use this to continue the conversation
}
}
}
Modèles d'événements courants
Voici les séquences d'événements typiques que vous rencontrerez :
Échange de texte simple
[Event 1] author="user", content=Text("Hello")
[Event 2] author="assistant", content=Text("Hi! How can I help?")
Flux d'utilisation de Tool
[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")
Mise à jour d'état
[Event 1] author="assistant", content=Text("I've saved your preference")
actions.state_delta={"user_theme": "dark"}
Transfert d'Agent
[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")
Métadonnées et identifiants d'événement
ID d'événement
Chaque événement possède un id unique (UUID) pour une identification précise :
println!("Event ID: {}", event.id);
ID d'invocation
L'invocation_id regroupe tous les événements, d'une seule requête utilisateur à la réponse finale :
// All events in one interaction share the same invocation_id
println!("Invocation: {}", event.invocation_id);
// Use this for logging and tracing
log::info!("Processing event {} in invocation {}", event.id, event.invocation_id);
Horodatage
Les événements sont horodatés pour un classement chronologique :
println!("Event occurred at: {}", event.timestamp.format("%Y-%m-%d %H:%M:%S"));
Bonnes Pratiques
-
Immuabilité des événements : Les événements ne doivent jamais être modifiés après leur création. Ils constituent un journal d'audit immuable.
-
Gestion de l'état : Utilisez
state_deltapour toutes les modifications d'état plutôt que de modifier l'état directement. Cela garantit que les changements sont suivis dans le journal des événements. -
Auteurs significatifs : Définissez des noms d'auteur clairs et descriptifs pour faciliter la compréhension des journaux d'événements.
-
Synthèse sélective : Utilisez
skip_summarizationpour les événements verbeux ou internes qui encombreraient l'historique de la conversation. -
Regroupement des invocations : Conservez le même
invocation_idpour tous les événements générés lors d'une seule invocation d'agent afin de maintenir un regroupement logique. -
Suivi des artifacts : Mettez toujours à jour
artifact_deltalors de la création ou de la modification d'artifacts pour maintenir la cohérence. -
Traitement des flux : Gérez toujours les erreurs lors du traitement des flux d'événements. Les événements peuvent échouer en raison d'erreurs de LLM, de défaillances de tools ou de problèmes réseau.
-
Vérification du contenu : Vérifiez toujours si
llm_response.contentestSomeavant d'accéder aux parts. Certains événements (comme les mises à jour d'état pures) peuvent ne pas avoir de contenu. -
Correspondance de motifs : Utilisez la correspondance de motifs de Rust pour gérer élégamment différents types d'événements et de parts de contenu.
-
Journalisation : Utilisez
invocation_idpour corréler tous les événements au sein d'une seule interaction utilisateur pour le débogage et l'observabilité.
Documentation connexe
- Sessions - Gestion et cycle de vie des sessions
- State Management - Travailler avec l'état de session
- Artifacts - Gestion des données binaires
- Multi-Agent Systems - Transferts et coordination d'agents
- Callbacks - Interception et modification des événements
Précédent : ← Artifacts | Suivant : Télémétrie →