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:
| Prefixo | Constante | Escopo |
|---|---|---|
app: | KEY_PREFIX_APP | Compartilhado entre todos os users e sessions |
user: | KEY_PREFIX_USER | Compartilhado entre todas as sessions para um user |
temp: | KEY_PREFIX_TEMP | Limpo 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
- Extrai chaves com prefixo
app:→ Armazena no estado do app - Extrai chaves com prefixo
user:→ Armazena no estado do user - Chaves restantes (exceto
temp:) → Armazena no estado da session - Mescla todos os escopos para a session retornada
Na Recuperação da Session
- Carrega o estado do app para a aplicação
- Carrega o estado do user para o user
- Carrega o estado da session
- Mescla todos os escopos (app → user → session)
No Anexo do Event
- Extrai o state delta do event
- Filtra chaves
temp:(não persistidas) - Aplica deltas
app:ao estado do app - Aplica deltas
user:ao estado do user - 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 →