Gerenciamento de Estado

O estado de sessão no ADK-Rust permite que os agents armazenem e recuperem dados que persistem entre as interações de uma conversa. O estado é organizado usando prefixos de chave que determinam o escopo e a vida útil dos dados.

Visão Geral

O estado é armazenado como pares chave-valor onde:

  • As chaves são strings com prefixos opcionais
  • Os valores são valores JSON (serde_json::Value)

O sistema de prefixos permite diferentes níveis de escopo:

  • Com escopo de sessão: Padrão, vinculado a uma única session
  • Com escopo de usuário: Compartilhado entre todas as sessions para um user
  • Com escopo de aplicativo: Compartilhado entre todos os users de um aplicativo
  • Temporário: Limpo após cada invocação

Trait State

O trait State define a interface para acesso ao estado:

use serde_json::Value;
use std::collections::HashMap;

pub trait State: Send + Sync {
    /// Get a value by key
    fn get(&self, key: &str) -> Option<Value>;
    
    /// Set a value
    fn set(&mut self, key: String, value: Value);
    
    /// Get all state as a map
    fn all(&self) -> HashMap<String, Value>;
}

Há também um trait ReadonlyState para acesso somente leitura:

pub trait ReadonlyState: Send + Sync {
    fn get(&self, key: &str) -> Option<Value>;
    fn all(&self) -> HashMap<String, Value>;
}

Prefixos de Chave de Estado

ADK-Rust usa três prefixos de chave para controlar o escopo do estado:

PrefixoConstanteEscopo
app:KEY_PREFIX_APPCompartilhado entre todos os users e sessions
user:KEY_PREFIX_USERCompartilhado entre todas as sessions para um user
temp:KEY_PREFIX_TEMPLimpo após cada invocação
(nenhum)-Com escopo de session (padrão)

app: - Estado de Aplicativo

Estado compartilhado entre todos os users e sessions de um aplicativo.

use adk_session::KEY_PREFIX_APP;

// KEY_PREFIX_APP = "app:"
let key = format!("{}settings", KEY_PREFIX_APP);  // "app:settings"

Casos de uso:

  • Configuração do aplicativo
  • Recursos compartilhados
  • Contadores ou estatísticas globais

user: - Estado de Usuário

Estado compartilhado entre todas as sessions para um user específico.

use adk_session::KEY_PREFIX_USER;

// KEY_PREFIX_USER = "user:"
let key = format!("{}preferences", KEY_PREFIX_USER);  // "user:preferences"

Casos de uso:

  • Preferências do user
  • Dados de perfil do user
  • Contexto do user entre sessions

temp: - Estado Temporário

Estado que é limpo após cada invocação. Não persistido.

use adk_session::KEY_PREFIX_TEMP;

// KEY_PREFIX_TEMP = "temp:"
let key = format!("{}current_step", KEY_PREFIX_TEMP);  // "temp:current_step"

Casos de uso:

  • Resultados de computação intermediária
  • Contexto da operação atual
  • Dados que não devem persistir

Sem Prefixo - Estado de Sessão

Chaves sem um prefixo têm escopo de session (comportamento padrão).

let key = "conversation_topic";  // Session-scoped

Casos de uso:

  • Contexto da conversa
  • Dados específicos da session
  • Estado por turno

Definindo o Estado Inicial

O estado pode ser inicializado ao criar uma session:

use adk_session::{InMemorySessionService, SessionService, CreateRequest, KEY_PREFIX_APP, KEY_PREFIX_USER};
use serde_json::json;
use std::collections::HashMap;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let mut initial_state = HashMap::new();

    // Estado com escopo de aplicativo
    initial_state.insert(
        format!("{}version", KEY_PREFIX_APP),
        json!("1.0.0")
    );

    // Estado com escopo de usuário
    initial_state.insert(
        format!("{}name", KEY_PREFIX_USER),
        json!("Alice")
    );

    // Estado com escopo de sessão
    initial_state.insert(
        "topic".to_string(),
        json!("Getting started")
    );

    let service = InMemorySessionService::new();
    let session = service.create(CreateRequest {
        app_name: "my_app".to_string(),
        user_id: "user_123".to_string(),
        session_id: None,
        state: initial_state,
    }).await?;
    
    Ok(())
}

Lendo o Estado

Acesse o estado através do método state() da session:

let state = session.state();

// Get a specific key
if let Some(value) = state.get("topic") {
    println!("Topic: {}", value);
}

// Get app-scoped state
if let Some(version) = state.get("app:version") {
    println!("App version: {}", version);
}

// Get all state
let all_state = state.all();
for (key, value) in all_state {
    println!("{}: {}", key, value);
}

Atualizações de Estado via Eventos

O estado é tipicamente atualizado através de ações de eventos. Quando um Event é anexado a uma session, seu state_delta é aplicado:

use adk_session::{Event, EventActions};
use serde_json::json;
use std::collections::HashMap;

let mut state_delta = HashMap::new();
state_delta.insert("counter".to_string(), json!(42));
state_delta.insert("user:last_seen".to_string(), json!("2024-01-15"));

let mut event = Event::new("invocation_123");
event.actions = EventActions {
    state_delta,
    ..Default::default()
};

// Quando este event é anexado, o estado é atualizado
service.append_event(session.id(), event).await?;

Comportamento de Escopo do Estado

O session service lida com o escopo do estado automaticamente:

Na Criação da Session

  1. Extrai chaves com prefixo app: → Armazena no estado do app
  2. Extrai chaves com prefixo user: → Armazena no estado do user
  3. Chaves restantes (exceto temp:) → Armazena no estado da session
  4. Mescla todos os escopos para a session retornada

Na Recuperação da Session

  1. Carrega o estado do app para a aplicação
  2. Carrega o estado do user para o user
  3. Carrega o estado da session
  4. Mescla todos os escopos (app → user → session)

No Anexo do Event

  1. Extrai o state delta do event
  2. Filtra chaves temp: (não persistidas)
  3. Aplica deltas app: ao estado do app
  4. Aplica deltas user: ao estado do user
  5. Aplica os deltas restantes ao estado da session

Exemplo Completo

use adk_session::{
    InMemorySessionService, SessionService, CreateRequest, GetRequest,
    KEY_PREFIX_APP, KEY_PREFIX_USER,
};
use serde_json::json;
use std::collections::HashMap;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let service = InMemorySessionService::new();
    
    // Cria a primeira session com estado inicial
    let mut state1 = HashMap::new();
    state1.insert(format!("{}theme", KEY_PREFIX_APP), json!("dark"));
    state1.insert(format!("{}language", KEY_PREFIX_USER), json!("en"));
    state1.insert("context".to_string(), json!("session1"));
    
    let session1 = service.create(CreateRequest {
        app_name: "my_app".to_string(),
        user_id: "alice".to_string(),
        session_id: Some("s1".to_string()),
        state: state1,
    }).await?;
    
    // Cria a segunda session para o mesmo user
    let mut state2 = HashMap::new();
    state2.insert("context".to_string(), json!("session2"));
    
    let session2 = service.create(CreateRequest {
        app_name: "my_app".to_string(),
        user_id: "alice".to_string(),
        session_id: Some("s2".to_string()),
        state: state2,
    }).await?;
    
    // A Session 2 herda o estado do app e do user
    let s2_state = session2.state();
    
    // O estado do app é compartilhado
    assert_eq!(s2_state.get("app:theme"), Some(json!("dark")));
    
    // O estado do user é compartilhado
    assert_eq!(s2_state.get("user:language"), Some(json!("en")));
    
    // O estado da session é separado
    assert_eq!(s2_state.get("context"), Some(json!("session2")));
    
    println!("O escopo do estado funciona corretamente!");
    Ok(())
}

Template de Instruções com Estado

Valores de estado podem ser injetados nas instruções do Agent usando a sintaxe {key}:

use adk_rust::prelude::*;
use std::sync::Arc;

let agent = LlmAgentBuilder::new("personalized_assistant")
    .instruction("You are helping {user:name} with {topic}. Their preferred language is {user:language}.")
    .model(Arc::new(model))
    .build()?;

Quando o Agent é executado, {user:name}, {topic} e {user:language} são substituídos por valores do estado da session.

Melhores Práticas

1. Use Escopos Apropriados

// ✅ Bom: Preferências do usuário no escopo user
"user:theme"
"user:timezone"

// ✅ Bom: Contexto específico da sessão sem prefixo
"current_task"
"conversation_summary"

// ✅ Bom: Configurações de todo o aplicativo no escopo app
"app:model_version"
"app:feature_flags"

// ❌ Ruim: Dados do usuário no escopo da sessão (perdidos entre as sessões)
"user_preferences"  // Deveria ser "user:preferences"

2. Use Estado Temporário para Dados Intermediários

// ✅ Bom: Resultados intermediários no escopo temp
"temp:search_results"
"temp:current_step"

// ❌ Ruim: Dados intermediários persistidos desnecessariamente
"search_results"  // Será salvo no banco de dados

3. Mantenha as Chaves de Estado Consistentes

// ✅ Bom: Convenção de nomenclatura consistente
"user:preferences.theme"
"user:preferences.language"

// ❌ Ruim: Nomenclatura inconsistente
"user:theme"
"userLanguage"
"user-timezone"

Relacionado

  • Sessions - Visão geral do gerenciamento de sessão
  • Events - Estrutura de eventos e state_delta
  • LlmAgent - Modelagem de instruções

Anterior: ← Sessions | Próximo: Callbacks →