Eventos
Eventos são os blocos de construção fundamentais do histórico de conversas no ADK-Rust. Cada interação com um Agent — seja uma mensagem de usuário, uma resposta do Agent ou uma execução de Tool — é registrada como um Event. Eventos formam um log imutável que captura o rastreamento de execução completo de uma Session do Agent.
Visão Geral
O sistema de Eventos serve a vários propósitos críticos:
- Histórico da Conversa: Eventos formam o registro cronológico de todas as interações em uma Session
- Gerenciamento de Estado: Eventos carregam mudanças de estado através do campo
state_delta - Rastreamento de Artefatos: Eventos registram operações de artefatos através do campo
artifact_delta - Coordenação de Agentes: Eventos permitem transferências e escalonamentos de Agent
- Depuração e Observabilidade: Eventos fornecem uma trilha de auditoria completa do comportamento do Agent
Estrutura do Evento
Um Event representa uma única interação em uma conversa. O ADK-Rust usa um tipo de Event unificado que incorpora LlmResponse, combinando com o padrão de design usado no ADK-Go:
pub struct Event {
pub id: String, // Identificador único do evento (UUID)
pub timestamp: DateTime<Utc>, // Quando o evento ocorreu
pub invocation_id: String, // Vincula eventos relacionados em uma única invocação
pub branch: String, // Para futuro suporte a ramificação
pub author: String, // Quem criou este evento (usuário, nome do agente, nome da ferramenta)
pub llm_response: LlmResponse, // Contém o conteúdo e metadados do LLM
pub actions: EventActions, // Efeitos colaterais e metadados
pub long_running_tool_ids: Vec<String>, // IDs de ferramentas de longa duração
}
A struct LlmResponse contém:
pub struct LlmResponse {
pub content: Option<Content>, // O conteúdo da mensagem (texto, partes, etc.)
pub usage_metadata: Option<UsageMetadata>,
pub finish_reason: Option<FinishReason>,
pub partial: bool, // Verdadeiro para respostas parciais de streaming
pub turn_complete: bool, // Verdadeiro quando o turno está completo
pub interrupted: bool, // Verdadeiro se a geração foi interrompida
pub error_code: Option<String>,
pub error_message: Option<String>,
}
O Content é acessado consistentemente 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);
}
}
}
Campos Chave
-
id: Um UUID único identificando este Event específico. Usado para recuperação e ordenação de Eventos.
-
timestamp: O timestamp UTC de quando o Event foi criado. Os Eventos são ordenados cronologicamente na Session.
-
invocation_id: Agrupa Eventos que pertencem à mesma invocação de Agent. Quando um Agent processa uma mensagem, todos os Eventos gerados (resposta do Agent, chamadas de Tool, chamadas de sub-Agent) compartilham o mesmo invocation_id.
-
branch: Reservado para funcionalidade futura de ramificação. Atualmente não utilizado, mas permite ramificação de conversa em futuras versões.
-
author: Identifica quem criou o Event:
- Mensagens de usuário: tipicamente "user" ou um identificador de usuário
- Respostas de Agent: o nome do Agent
- Execuções de Tool: o nome da Tool
- Eventos de sistema: "system"
-
llm_response: Contém o Content da mensagem e metadados do LLM. Acesse o Content via
event.llm_response.content. O tipoContentpode conter texto, partes multimodais (imagens, áudio) ou dados estruturados. Alguns Eventos (como atualizações puras de estado) podem tercontent: None.
EventActions
A struct EventActions contém metadados e efeitos colaterais associados a um evento:
pub struct EventActions {
pub state_delta: HashMap<String, Value>, // Alterações de estado a serem aplicadas
pub artifact_delta: HashMap<String, i64>, // Alterações de versão do artefato
pub skip_summarization: bool, // Ignorar este evento em resumos
pub transfer_to_agent: Option<String>, // Transferir controle para outro Agent
pub escalate: bool, // Escalonar para humano ou supervisor
}
state_delta
O campo state_delta contém pares chave-valor representando alterações no estado da Session. Quando um evento é anexado a uma Session, essas alterações são mescladas no estado da Session.
As chaves de estado podem usar prefixos para controlar o escopo:
app:key- Estado com escopo de aplicação (compartilhado entre todos os usuários)user:key- Estado com escopo de usuário (compartilhado entre todas as Sessions para um usuário)temp:key- Estado temporário (limpo entre invocações)- Sem prefixo - Estado com escopo de Session (padrão)
Exemplo:
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
O campo artifact_delta rastreia alterações em artefatos. As chaves são nomes de artefatos e os valores são números de versão. Isso permite que o sistema rastreie quais artefatos foram criados ou modificados durante um evento.
Exemplo:
actions.artifact_delta.insert("report.pdf".to_string(), 1);
actions.artifact_delta.insert("chart.png".to_string(), 2);
skip_summarization
Quando true, este evento será excluído dos resumos de conversas. Útil para eventos internos, informações de depuração ou saídas de Tool verbosas que não deveriam fazer parte do fluxo principal da conversa.
transfer_to_agent
Quando definido com um nome de Agent, o controle é transferido para esse Agent. Isso permite fluxos de trabalho multi-Agent onde um Agent pode passar a tarefa para outro. O Agent de destino deve ser configurado como um sub-Agent.
Exemplo:
actions.transfer_to_agent = Some("specialist_agent".to_string());
escalate
Quando true, sinaliza que a conversa deve ser escalonada para um operador humano ou um Agent supervisor. O comportamento de escalonamento específico depende da implementação da sua aplicação.
Formação do Histórico de Conversa
Eventos formam o histórico da conversa acumulando-se em ordem cronológica dentro de uma Session. Quando um Agent processa uma requisição:
- Evento de Mensagem do Usuário: Um novo evento é criado com a entrada do usuário
- Processamento pelo Agent: O Agent recebe o histórico da conversa (todos os eventos anteriores)
- Evento de Resposta do Agent: A resposta do Agent é registrada como um novo evento
- Eventos de Execução de Tool: Cada chamada de Tool pode gerar eventos adicionais
- Atualizações de Estado:
state_deltade todos os eventos são mesclados no estado da Session
O histórico da conversa é construído por:
- Recuperar todos os eventos da Session em ordem cronológica
- Converter o conteúdo de cada evento para o formato apropriado para o LLM
- Incluir informações de estado de
state_deltaacumulados - Filtrar eventos marcados com
skip_summarizationquando apropriado
Exemplo de Fluxo de Eventos
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
Cada evento se baseia nos anteriores, criando um registro de auditoria completo da conversa.
Trabalhando com Eventos
Acessando Eventos de uma Session
use adk_rust::session::{SessionService, GetRequest};
// Recuperar uma session com seus 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, // Obter todos os eventos
after: None,
}).await?;
// Acessar os eventos
let events = session.events();
println!("Total events: {}", events.len());
// Iterar pelos eventos (nota: eventos da session usam 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
);
}
}
Inspecionando Detalhes do Evento
// Obter um evento específico da session (usa llm_response.content)
if let Some(event) = events.at(0) {
// Verificar o autor
println!("Author: {}", event.author);
// Verificar conteúdo (eventos da session usam 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 alterações de estado
if !event.actions.state_delta.is_empty() {
println!("State changes:");
for (key, value) in &event.actions.state_delta {
println!(" {} = {}", key, value);
}
}
// Verificar transferências de Agent
if let Some(target) = &event.actions.transfer_to_agent {
println!("Transfers to: {}", target);
}
// Verificar artefatos
if !event.actions.artifact_delta.is_empty() {
println!("Artifacts modified:");
for (name, version) in &event.actions.artifact_delta {
println!(" {} (v{})", name, version);
}
}
}
Limitando o Histórico de Eventos
Para conversas longas, você pode querer recuperar apenas eventos recentes:
// Obter apenas os ú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?;
Como os Eventos Fluem: Geração e Processamento
Compreender como os eventos são criados e processados ajuda a esclarecer como o framework gerencia ações e histórico.
Fontes de Geração
Os eventos são criados em diferentes pontos do ciclo de vida de execução do agent:
- Entrada do Usuário: O Runner encapsula mensagens do usuário em um Event com
author = "user" - Respostas do Agent: Agents retornam (yield) objetos Event (definindo
author = agent.name()) para comunicar respostas - Saída do LLM: A camada de integração do modelo traduz a saída do LLM (texto, chamadas de função) em objetos Event
- Resultados da Tool: Após a execução da tool, o framework gera um Event contendo a resposta da tool
Fluxo de Processamento
Quando um evento é gerado, ele segue este caminho de processamento:
- Geração: Um evento é criado e retornado (yielded) por sua fonte (agent, tool, ou manipulador de entrada do usuário)
- Runner Recebe: O Runner executando o agent recebe o evento
- Processamento do SessionService: O Runner envia o evento para o SessionService, que:
- Aplica Deltas: Mescla
state_deltano estado da sessão e atualiza os registros de artefatos - Finaliza Metadados: Atribui um
idúnico se não presente, definetimestamp - Persiste no Histórico: Adiciona o evento a
session.events
- Aplica Deltas: Mescla
- Saída de Stream: O Runner retorna (yields) o evento processado para a aplicação chamadora
Este fluxo garante que as mudanças de estado e o histórico sejam consistentemente registrados juntamente com o conteúdo da comunicação.
// Fluxo conceitual
User Input → Runner → Agent → LLM → Event Generated
↓
SessionService
- Apply state_delta
- Record in history
↓
Event Stream → Application
Identificando Tipos de Eventos
Ao processar eventos do Runner, você vai querer identificar o tipo de evento com o qual está lidando:
Por Autor
O campo author informa quem criou o evento:
match event.author.as_str() {
"user" => println!("Entrada do usuário"),
agent_name => println!("Resposta do agent: {}", agent_name),
}
Por Tipo de Conteúdo
Verifique o campo llm_response.content para determinar o 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!("Mensagem de texto");
} else if has_function_call {
println!("Requisição de chamada de tool");
} else if has_function_response {
println!("Resultado da tool");
}
}
Por Ações
Verifique o campo actions para sinais de controle e efeitos colaterais:
// Mudanças de estado
if !event.actions.state_delta.is_empty() {
println!("Evento contém mudanças de estado");
}
// Transferência de agent
if let Some(target) = &event.actions.transfer_to_agent {
println!("Transferir para agent: {}", target);
}
// Sinal de escalonamento
if event.actions.escalate {
println!("Escalonamento solicitado");
}
// Pular sumarização
if event.actions.skip_summarization {
println!("Pular este evento nos resumos");
}
Trabalhando com Fluxos de Eventos
Ao executar um Agent, você recebe um fluxo de eventos. Veja como processá-los de forma eficaz:
Processando Eventos do 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) => {
// Processa o evento
println!("Event from: {}", event.author);
// Extrai o conteúdo de texto
if let Some(content) = &event.llm_response.content {
for part in &content.parts {
if let Part::Text { text } = part {
print!("{}", text);
}
}
}
// Verifica por mudanças de estado
if !event.actions.state_delta.is_empty() {
println!("\nState updated: {:?}", event.actions.state_delta);
}
}
Err(e) => {
eprintln!("Error: {}", e);
break;
}
}
}
Extraindo Chamadas de Função
Quando o LLM solicita uma Tool, o evento contém informações de FunctionCall:
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);
// Sua aplicação pode despachar a execução da Tool aqui
// com base no nome e argumentos da Tool
}
}
}
Extraindo Respostas de Função
Após a execução de uma Tool, o resultado é retornado em uma FunctionResponse:
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);
// Processa o resultado da Tool
// O LLM usará isso para continuar a conversa
}
}
}
Padrões Comuns de Eventos
Aqui estão sequências típicas de eventos que você encontrará:
Troca Simples de Texto
[Event 1] author="user", content=Text("Hello")
[Event 2] author="assistant", content=Text("Hi! How can I help?")
Fluxo de Uso 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")
Atualização de Estado
[Event 1] author="assistant", content=Text("I've saved your preference")
actions.state_delta={"user_theme": "dark"}
Transferência 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")
Metadados e Identificadores de Eventos
ID do Evento
Cada evento possui um id (UUID) único para identificação precisa:
println!("Event ID: {}", event.id);
ID de Invocação
O invocation_id agrupa todos os eventos de uma única solicitação do usuário até a resposta final:
// Todos os eventos em uma interação compartilham o mesmo invocation_id
println!("Invocation: {}", event.invocation_id);
// Use isso para registro (logging) e rastreamento (tracing)
log::info!("Processing event {} in invocation {}", event.id, event.invocation_id);
Timestamp
Os eventos são carimbados com um timestamp para ordenação cronológica:
println!("Event occurred at: {}", event.timestamp.format("%Y-%m-%d %H:%M:%S"));
Melhores Práticas
-
Imutabilidade de Eventos: Eventos nunca devem ser modificados após a criação. Eles formam um registro de auditoria imutável.
-
Gerenciamento de Estado: Use
state_deltapara todas as mudanças de estado, em vez de modificar o estado diretamente. Isso garante que as mudanças sejam rastreadas no log de eventos. -
Autores Significativos: Defina nomes de autor claros e descritivos para facilitar a compreensão dos logs de eventos.
-
Resumo Seletivo: Use
skip_summarizationpara eventos verbosos ou internos que poluiriam o histórico da conversa. -
Agrupamento de Invocação: Mantenha o mesmo
invocation_idpara todos os eventos gerados durante uma única invocação de agent para manter o agrupamento lógico. -
Rastreamento de Artifacts: Sempre atualize
artifact_deltaao criar ou modificar artifacts para manter a consistência. -
Processamento de Fluxo: Sempre trate erros ao processar fluxos de eventos. Eventos podem falhar devido a erros do LLM, falhas de tool ou problemas de rede.
-
Verificação de Content: Sempre verifique se
llm_response.contentéSomeantes de acessar partes. Alguns eventos (como atualizações de estado puras) podem não ter content. -
Pattern Matching: Use o pattern matching do Rust para lidar elegantemente com diferentes tipos de eventos e partes de content.
-
Logging: Use
invocation_idpara correlacionar todos os eventos dentro de uma única interação do usuário para depuração e observabilidade.
Documentação Relacionada
- Sessions - Gerenciamento e ciclo de vida de session
- State Management - Trabalhando com o estado de session
- Artifacts - Gerenciamento de dados binários
- Multi-Agent Systems - Transferências e coordenação de agent
- Callbacks - Interceptando e modificando eventos
Anterior: ← Artifacts | Próximo: Telemetry →