Eventos
Los Events son los bloques de construcción fundamentales del historial de conversación en ADK-Rust. Cada interacción con un Agent —ya sea un mensaje de usuario, una respuesta de Agent o una ejecución de Tool— se registra como un Event. Los Events forman un registro inmutable que captura el seguimiento completo de la ejecución de una Session de Agent.
Descripción general
El sistema de Events cumple varios propósitos críticos:
- Historial de conversación: Los Events forman el registro cronológico de todas las interacciones en una Session.
- Gestión de estado: Los Events transportan los cambios de estado a través del campo
state_delta. - Seguimiento de Artifacts: Los Events registran las operaciones de Artifacts a través del campo
artifact_delta. - Coordinación de Agents: Los Events permiten transferencias y escaladas de Agents.
- Depuración y observabilidad: Los Events proporcionan un registro de auditoría completo del comportamiento del Agent.
Estructura de Event
Un Event representa una única interacción en una conversación. ADK-Rust utiliza un tipo de Event unificado que incrusta LlmResponse, coincidiendo con el patrón de diseño utilizado en 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 estructura LlmResponse contiene:
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>,
}
Se accede a Content de forma consistente a través de 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);
}
}
}
Campos clave
-
id: Un UUID único que identifica este Event específico. Se utiliza para la recuperación y el ordenamiento de Events.
-
timestamp: La marca de tiempo UTC en que se creó el Event. Los Events se ordenan cronológicamente en la Session.
-
invocation_id: Agrupa los Events que pertenecen a la misma invocación de Agent. Cuando un Agent procesa un mensaje, todos los Events generados (respuesta del Agent, llamadas a Tool, llamadas a sub-Agent) comparten el mismo
invocation_id. -
branch: Reservado para futuras funcionalidades de ramificación. Actualmente no se utiliza, pero permite la ramificación de conversaciones en futuras versiones.
-
author: Identifica quién creó el Event:
- Mensajes de usuario: típicamente "user" o un identificador de usuario.
- Respuestas de Agent: el nombre del Agent.
- Ejecuciones de Tool: el nombre de la Tool.
- Events del sistema: "system".
-
llm_response: Contiene el
Contentdel mensaje y los metadatos de LLM. Acceda alContenta través deevent.llm_response.content. El tipoContentpuede contener texto, partes multimodales (imágenes, audio) o datos estructurados. Algunos Events (como las actualizaciones de estado puro) pueden tenercontent: None.
Acciones de Evento
La estructura EventActions contiene metadatos y efectos secundarios asociados con un evento:
pub struct EventActions {
pub state_delta: HashMap<String, Value>, // Cambios de estado a aplicar
pub artifact_delta: HashMap<String, i64>, // Cambios de versión de artefacto
pub skip_summarization: bool, // Omitir este evento en los resúmenes
pub transfer_to_agent: Option<String>, // Transferir el control a otro agent
pub escalate: bool, // Escalar a un humano o supervisor
}
state_delta
El campo state_delta contiene pares clave-valor que representan cambios en el estado de la sesión. Cuando un evento se añade a una sesión, estos cambios se fusionan en el estado de la sesión.
Las claves de estado pueden usar prefijos para controlar el alcance:
app:key- Estado con alcance de aplicación (compartido entre todos los usuarios)user:key- Estado con alcance de usuario (compartido entre todas las sesiones para un usuario)temp:key- Estado temporal (borrado entre invocaciones)- Sin prefijo - Estado con alcance de sesión (predeterminado)
Ejemplo:
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
El campo artifact_delta rastrea los cambios en los artefactos. Las claves son nombres de artefactos y los valores son números de versión. Esto permite al sistema rastrear qué artefactos fueron creados o modificados durante un evento.
Ejemplo:
actions.artifact_delta.insert("report.pdf".to_string(), 1);
actions.artifact_delta.insert("chart.png".to_string(), 2);
skip_summarization
Cuando es true, este evento será excluido de los resúmenes de conversación. Útil para eventos internos, información de depuración o salidas verbosas de Tool que no deberían ser parte del flujo principal de la conversación.
transfer_to_agent
Cuando se establece en un nombre de agent, el control se transfiere a ese agent. Esto permite flujos de trabajo multi-agent donde un agent puede pasar el control a otro. El agent objetivo debe estar configurado como un sub-agent.
Ejemplo:
actions.transfer_to_agent = Some("specialist_agent".to_string());
escalate
Cuando es true, indica que la conversación debe ser escalada a un operador humano o a un agent supervisor. El comportamiento específico de escalada depende de la implementación de tu aplicación.
Formación del Historial de Conversación
Los eventos forman el historial de conversación acumulándose en orden cronológico dentro de una sesión. Cuando un agent procesa una solicitud:
- Evento de Mensaje de Usuario: Se crea un nuevo evento con la entrada del usuario
- Procesamiento del Agent: El agent recibe el historial de conversación (todos los eventos previos)
- Evento de Respuesta del Agent: La respuesta del agent se registra como un nuevo evento
- Eventos de Ejecución de Tool: Cada llamada a Tool puede generar eventos adicionales
- Actualizaciones de Estado: Los deltas de estado de todos los eventos se fusionan en el estado de la sesión
El historial de conversación se construye mediante:
- Recuperando todos los eventos de la sesión en orden cronológico
- Convirtiendo el contenido de cada evento al formato apropiado para el LLM
- Incluyendo información de estado de los deltas de estado acumulados
- Filtrando eventos marcados con
skip_summarizationcuando sea apropiado
Ejemplo de Flujo de Eventos
Inicio de Sesión
↓
[Event 1] Usuario: "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."
↓
Estado de Sesión Actualizado
Cada evento se basa en los anteriores, creando un rastro de auditoría completo de la conversación.
Trabajando con Eventos
Accediendo a Eventos desde una Sesión
use adk_rust::session::{SessionService, GetRequest};
// Recuperar una sesión con sus eventos
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, // Obtener todos los eventos
after: None,
}).await?;
// Acceder a los eventos
let events = session.events();
println!("Total events: {}", events.len());
// Iterar a través de los eventos (nota: los eventos de sesión usan 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
);
}
}
Inspeccionando los Detalles del Evento
// Obtener un evento específico de la sesión (usa llm_response.content)
if let Some(event) = events.at(0) {
// Verificar el autor
println!("Author: {}", event.author);
// Verificar el contenido (los eventos de sesión usan 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);
}
}
}
// Verificar cambios de estado
if !event.actions.state_delta.is_empty() {
println!("State changes:");
for (key, value) in &event.actions.state_delta {
println!(" {} = {}", key, value);
}
}
// Verificar transferencias de Agent
if let Some(target) = &event.actions.transfer_to_agent {
println!("Transfers to: {}", target);
}
// Verificar artefactos
if !event.actions.artifact_delta.is_empty() {
println!("Artifacts modified:");
for (name, version) in &event.actions.artifact_delta {
println!(" {} (v{})", name, version);
}
}
}
Limitando el Historial de Eventos
Para conversaciones largas, es posible que desee recuperar solo los eventos recientes:
// Obtener solo los últimos 10 eventos
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?;
Cómo Fluyen los Eventos: Generación y Procesamiento
Comprender cómo se crean y procesan los eventos ayuda a clarificar cómo el framework gestiona las acciones y el historial.
Fuentes de Generación
Los eventos se crean en diferentes puntos del ciclo de vida de ejecución del Agent:
- Entrada de Usuario: el Runner envuelve los mensajes del usuario en un Event con
author = "user" - Respuestas del Agent: los Agents producen objetos Event (estableciendo
author = agent.name()) para comunicar respuestas - Salida del LLM: la capa de integración del modelo traduce la salida del LLM (texto, llamadas a funciones) en objetos Event
- Resultados de Tool: Después de la ejecución de la Tool, el framework genera un Event que contiene la respuesta de la Tool
Flujo de Procesamiento
Cuando se genera un evento, sigue esta ruta de procesamiento:
- Generación: Un evento es creado y producido por su fuente (Agent, Tool o manejador de entrada de usuario)
- Runner Recibe: el Runner que ejecuta el Agent recibe el evento
- Procesamiento de SessionService: el Runner envía el evento al SessionService, que:
- Aplica Deltas: Fusiona
state_deltaen el estado de la Session y actualiza los registros de artefactos - Finaliza Metadatos: Asigna un
idúnico si no está presente, establecetimestamp - Persiste en el Historial: Añade el evento a
session.events
- Aplica Deltas: Fusiona
- Salida del Stream: el Runner produce el evento procesado a la aplicación que realiza la llamada
Este flujo asegura que los cambios de estado y el historial se registren consistentemente junto con el contenido de la comunicación.
// Conceptual flow
User Input → Runner → Agent → LLM → Event Generated
↓
SessionService
- Apply state_delta
- Record in history
↓
Event Stream → Application
Identificando Tipos de Eventos
Al procesar eventos del Runner, querrás identificar con qué tipo de evento estás tratando:
Por Autor
El campo author te dice quién creó el evento:
match event.author.as_str() {
"user" => println!("User input"),
agent_name => println!("Response from agent: {}", agent_name),
}
Por Tipo de Contenido
Verifica el campo llm_response.content para determinar el tipo de payload:
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");
}
}
Por Acciones
Verifica el campo actions en busca de señales de control y efectos secundarios:
// 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");
}
Trabajar con flujos de eventos
Al ejecutar un Agent, recibes un flujo de eventos. Así es como puedes procesarlos eficazmente:
Procesamiento de eventos desde 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;
}
}
}
Extracción de llamadas a funciones
Cuando el LLM solicita una Tool, el evento contiene información de la llamada a la función:
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
}
}
}
Extracción de respuestas de funciones
Después de que una Tool se ejecuta, el resultado se devuelve en una respuesta de función:
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
}
}
}
Patrones de eventos comunes
Aquí están las secuencias de eventos típicas que encontrarás:
Intercambio de texto simple
[Event 1] author="user", content=Text("Hello")
[Event 2] author="assistant", content=Text("Hi! How can I help?")
Flujo de uso de Tools
[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")
Actualización de estado
[Event 1] author="assistant", content=Text("I've saved your preference")
actions.state_delta={"user_theme": "dark"}
Transferencia de 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")
Metadatos e identificadores de eventos
Event ID
Cada evento tiene un id único (UUID) para una identificación precisa:
println!("Event ID: {}", event.id);
Invocation ID
El invocation_id agrupa todos los eventos de una única solicitud de usuario hasta la respuesta final:
// 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);
Timestamp
Los eventos están marcados con un timestamp para un orden cronológico:
println!("Event occurred at: {}", event.timestamp.format("%Y-%m-%d %H:%M:%S"));
Mejores Prácticas
-
Inmutabilidad de Eventos: Los eventos nunca deben modificarse después de su creación. Forman un registro de auditoría inmutable.
-
Gestión de Estado: Use
state_deltapara todos los cambios de estado en lugar de modificar el estado directamente. Esto asegura que los cambios se registren en el log de eventos. -
Autores Significativos: Establezca nombres de autor claros y descriptivos para facilitar la comprensión de los logs de eventos.
-
Resumen Selectivo: Use
skip_summarizationpara eventos detallados o internos que saturarían el historial de conversación. -
Agrupación de Invocaciones: Mantenga el mismo
invocation_idpara todos los eventos generados durante una única invocación del Agent para mantener la agrupación lógica. -
Seguimiento de Artifacts: Siempre actualice
artifact_deltaal crear o modificar Artifacts para mantener la consistencia. -
Procesamiento de Flujos: Siempre maneje los errores al procesar flujos de eventos. Los eventos pueden fallar debido a errores del LLM, fallos de Tool o problemas de red.
-
Verificación de Contenido: Siempre verifique si
llm_response.contentesSomeantes de acceder a los Parts. Algunos eventos (como actualizaciones de estado puras) pueden no tener contenido. -
Coincidencia de Patrones: Use la coincidencia de patrones de Rust para manejar elegantemente diferentes tipos de eventos y Parts de contenido.
-
Registro: Use
invocation_idpara correlacionar todos los eventos dentro de una única interacción de usuario para la depuración y la observabilidad.
Documentación Relacionada
- Sessions - gestión y ciclo de vida de Session
- Gestión de Estado - Trabajo con el estado de Session
- Artifacts - Gestión de datos binarios
- Sistemas Multi-Agent - transferencias y coordinación de Agent
- Retrollamadas - Intercepción y modificación de eventos
Anterior: ← Artifacts | Siguiente: Telemetría →