Gestión del Estado

El estado de sesión en ADK-Rust permite a los agentes almacenar y recuperar datos que persisten a lo largo de los turnos de conversación. El estado se organiza utilizando prefijos de clave que determinan el alcance y la vida útil de los datos.

Descripción General

El estado se almacena como pares clave-valor donde:

  • Las claves son cadenas con prefijos opcionales
  • Los valores son valores JSON (serde_json::Value)

El sistema de prefijos permite diferentes niveles de alcance:

  • Con alcance de sesión: Predeterminado, vinculado a una única sesión
  • Con alcance de usuario: Compartido entre todas las sesiones para un usuario
  • Con alcance de aplicación: Compartido entre todos los usuarios de una aplicación
  • Temporal: Se borra después de cada invocación

Trait de Estado

El trait State define la interfaz para el acceso al 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>;
}

También existe un trait ReadonlyState para acceso de solo lectura:

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

Prefijos de Clave de Estado

ADK-Rust utiliza tres prefijos clave para controlar el alcance del estado:

PrefijoConstanteAlcance
app:KEY_PREFIX_APPCompartido entre todos los usuarios y sesiones
user:KEY_PREFIX_USERCompartido entre todas las sesiones para un usuario
temp:KEY_PREFIX_TEMPBorrado después de cada invocación
(ninguno)-Con alcance de sesión (predeterminado)

app: - Estado de la Aplicación

Estado compartido entre todos los usuarios y sesiones de una aplicación.

use adk_session::KEY_PREFIX_APP;

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

Casos de uso:

  • Configuración de la aplicación
  • Recursos compartidos
  • Contadores o estadísticas globales

user: - Estado del Usuario

Estado compartido entre todas las sesiones para un usuario específico.

use adk_session::KEY_PREFIX_USER;

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

Casos de uso:

  • Preferencias del usuario
  • Datos del perfil de usuario
  • Contexto de usuario entre sesiones

temp: - Estado Temporal

Estado que se borra después de cada invocación. No 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 cómputos intermedios
  • Contexto de operación actual
  • Datos que no deben persistir

Sin Prefijo - Estado de Sesión

Las claves sin prefijo tienen alcance de sesión (comportamiento predeterminado).

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

Casos de uso:

  • Contexto de conversación
  • Datos específicos de la sesión
  • Estado turno a turno

Estableciendo el Estado Inicial

El estado puede inicializarse al crear una sesión:

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();

    // App-scoped state
    initial_state.insert(
        format!("{}version", KEY_PREFIX_APP),
        json!("1.0.0")
    );

    // User-scoped state
    initial_state.insert(
        format!("{}name", KEY_PREFIX_USER),
        json!("Alice")
    );

    // Session-scoped state
    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(())
}

Leyendo el Estado

Acceda al estado a través del método state() de la sesión:

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);
}

Actualizaciones de Estado a través de Eventos

El estado se actualiza típicamente a través de acciones de eventos. Cuando un evento se añade a una sesión, se aplica su state_delta:

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()
};

// When this event is appended, state is updated
service.append_event(session.id(), event).await?;

Comportamiento del Alcance del Estado

El servicio de sesión gestiona automáticamente el alcance del estado:

En la Creación de Sesión

  1. Extraer claves con prefijo app: → Almacenar en el estado de la aplicación
  2. Extraer claves con prefijo user: → Almacenar en el estado del usuario
  3. Claves restantes (excepto temp:) → Almacenar en el estado de la sesión
  4. Fusionar todos los alcances para la sesión devuelta

En la Recuperación de Sesión

  1. Cargar el estado de la aplicación
  2. Cargar el estado del usuario
  3. Cargar el estado de la sesión
  4. Fusionar todos los alcances (aplicación → usuario → sesión)

Al Añadir Eventos

  1. Extraer el delta de estado del evento
  2. Filtrar claves temp: (no persistidas)
  3. Aplicar deltas app: al estado de la aplicación
  4. Aplicar deltas user: al estado del usuario
  5. Aplicar deltas restantes al estado de la sesión

Ejemplo 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();
    
    // Create first session with initial state
    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?;
    
    // Create second session for same 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?;
    
    // Session 2 inherits app and user state
    let s2_state = session2.state();
    
    // App state is shared
    assert_eq!(s2_state.get("app:theme"), Some(json!("dark")));
    
    // User state is shared
    assert_eq!(s2_state.get("user:language"), Some(json!("en")));
    
    // Session state is separate
    assert_eq!(s2_state.get("context"), Some(json!("session2")));
    
    println!("State scoping works correctly!");
    Ok(())
}

Plantillas de Instrucción con Estado

Los valores de estado se pueden inyectar en las instrucciones del agente usando la sintaxis {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()?;

Cuando el agente se ejecuta, {user:name}, {topic} y {user:language} son reemplazados por valores del estado de la sesión.

Mejores prácticas

1. Usar ámbitos apropiados

// ✅ Good: User preferences in user scope
"user:theme"
"user:timezone"

// ✅ Good: Session-specific context without prefix
"current_task"
"conversation_summary"

// ✅ Good: App-wide settings in app scope
"app:model_version"
"app:feature_flags"

// ❌ Bad: User data in session scope (lost between sessions)
"user_preferences"  // Should be "user:preferences"

2. Usar estado temporal para datos intermedios

// ✅ Good: Intermediate results in temp scope
"temp:search_results"
"temp:current_step"

// ❌ Bad: Intermediate data persisted unnecessarily
"search_results"  // Will be saved to database

3. Mantener las claves de estado consistentes

// ✅ Good: Consistent naming convention
"user:preferences.theme"
"user:preferences.language"

// ❌ Bad: Inconsistent naming
"user:theme"
"userLanguage"
"user-timezone"
  • Sesiones - Resumen de la gestión de sesiones
  • Eventos - Estructura de eventos y state_delta
  • LlmAgent - Plantillas de instrucciones

Anterior: ← Sesiones | Siguiente: Callbacks →

Gestión del Estado - Documentación ADK-Rust | ADK-Rust