Gestión de Estado
El estado de sesión en adk-rust permite a los Agent almacenar y recuperar datos que persisten a través de los turnos de conversación. El estado se organiza utilizando prefijos de clave que determinan el ámbito y la vida útil de los datos.
Resumen
El estado se almacena como pares clave-valor donde:
- Las claves son strings con prefijos opcionales
- Los valores son valores JSON (
serde_json::Value)
El sistema de prefijos permite diferentes niveles de alcance:
- Ámbito de sesión: Predeterminado, vinculado a una única Session
- Ámbito de usuario: Compartido en todas las Sessiones para un User
- Ámbito de aplicación: Compartido en todos los User de una aplicación
- Temporal: Se borra después de cada invocación
Trait State
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 de clave para controlar el alcance del estado:
| Prefix | Constant | Scope |
|---|---|---|
app: | KEY_PREFIX_APP | Compartido entre todos los User y Sessiones |
user: | KEY_PREFIX_USER | Compartido entre todas las Sessiones para un User |
temp: | KEY_PREFIX_TEMP | Se borra después de cada invocación |
| (none) | - | Ámbito de sesión (predeterminado) |
app: - Estado de la Aplicación
Estado compartido entre todos los User y Sessiones 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 Sessiones para un 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:
- Preferencias de User
- Datos de perfil de User
- Contexto de User entre Sessiones
temp: - Estado Temporal
Estado que se borra después de cada invocación. No persiste.
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 la operación actual
- Datos que no deben persistir
Sin Prefijo - Estado de Sesión
Las claves sin prefijo tienen un alcance de Session (comportamiento predeterminado).
let key = "conversation_topic"; // Session-scoped
Casos de uso:
- Contexto de la conversación
- Datos específicos de la Session
- Estado de turno por turno
Establecer el Estado Inicial
El estado se puede inicializar al crear una 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();
// 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(())
}
Lectura del estado
Acceda al estado a través del método state() de la sesión:
let state = session.state();
// Obtener una clave específica
if let Some(value) = state.get("topic") {
println!("Topic: {}", value);
}
// Obtener estado con alcance de aplicación
if let Some(version) = state.get("app:version") {
println!("App version: {}", version);
}
// Obtener todo el estado
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 se añade un evento 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()
};
// Cuando se añade este evento, el estado se actualiza
service.append_event(session.id(), event).await?;
Comportamiento del alcance del estado
El servicio de sesión gestiona automáticamente el alcance del estado:
Al crear una sesión
- Extraer claves con prefijo
app:→ Almacenar en el estado de la aplicación - Extraer claves con prefijo
user:→ Almacenar en el estado del usuario - Claves restantes (excepto
temp:) → Almacenar en el estado de la sesión - Fusionar todos los alcances para la sesión devuelta
Al recuperar una sesión
- Cargar el estado de la aplicación
- Cargar el estado del usuario
- Cargar el estado de la sesión
- Fusionar todos los alcances (aplicación → usuario → sesión)
Al añadir un evento
- Extraer el
state_deltadel evento - Filtrar claves
temp:(no se persisten) - Aplicar deltas
app:al estado de la aplicación - Aplicar deltas
user:al estado del usuario - 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();
// Crear la primera sesión con 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?;
// Crear la segunda sesión para el mismo usuario
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?;
// La Sesión 2 hereda el estado de la aplicación y del usuario
let s2_state = session2.state();
// El estado de la aplicación se comparte
assert_eq!(s2_state.get("app:theme"), Some(json!("dark")));
// El estado del usuario se comparte
assert_eq!(s2_state.get("user:language"), Some(json!("en")));
// El estado de la sesión es independiente
assert_eq!(s2_state.get("context"), Some(json!("session2")));
println!("State scoping works correctly!");
Ok(())
}
Plantillas de instrucciones con estado
Los valores de estado se pueden inyectar en las instrucciones del Agent 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 se ejecuta el Agent, {user:name}, {topic} y {user:language} se reemplazan con valores del estado de la sesión.
Mejores Prácticas
1. Usar Ámbitos Apropiados
// ✅ Bien: Preferencias de usuario en el ámbito de usuario
"user:theme"
"user:timezone"
// ✅ Bien: Contexto específico de la sesión sin prefijo
"current_task"
"conversation_summary"
// ✅ Bien: Configuración de la aplicación en el ámbito de la aplicación
"app:model_version"
"app:feature_flags"
// ❌ Mal: Datos de usuario en el ámbito de la sesión (se pierden entre sesiones)
"user_preferences" // Debería ser "user:preferences"
2. Usar Estado Temporal para Datos Intermedios
// ✅ Bien: Resultados intermedios en el ámbito temporal
"temp:search_results"
"temp:current_step"
// ❌ Mal: Datos intermedios persistidos innecesariamente
"search_results" // Se guardará en la base de datos
3. Mantener las Claves de Estado Consistentes
// ✅ Bien: Convención de nomenclatura consistente
"user:preferences.theme"
"user:preferences.language"
// ❌ Mal: Nomenclatura inconsistente
"user:theme"
"userLanguage"
"user-timezone"
Relacionado
- Sessions - Visión general de la gestión de sesiones
- Events - Estructura de eventos y state_delta
- LlmAgent - Plantillas de instrucciones
Anterior: ← Sessions | Siguiente: Callbacks →