Gestion de l'état

L'état de session dans ADK-Rust permet aux agents de stocker et de récupérer des données qui persistent entre les tours de conversation. L'état est organisé à l'aide de préfixes de clé qui déterminent la portée et la durée de vie des données.

Vue d'ensemble

L'Ă©tat est stockĂ© sous forme de paires clĂ©-valeur oĂč :

  • Les clĂ©s sont des chaĂźnes de caractĂšres avec des prĂ©fixes optionnels
  • Les valeurs sont des valeurs JSON (serde_json::Value)

Le systÚme de préfixes permet différents niveaux de portée :

  • PortĂ©e de session : Par dĂ©faut, liĂ© Ă  une seule session
  • PortĂ©e utilisateur : PartagĂ© entre toutes les sessions pour un utilisateur
  • PortĂ©e application : PartagĂ© entre tous les utilisateurs d'une application
  • Temporaire : EffacĂ© aprĂšs chaque invocation

Trait d'état

Le trait State définit l'interface pour l'accÚs à l'état :

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

Il existe également un trait ReadonlyState pour l'accÚs en lecture seule :

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

Préfixes de clé d'état

ADK-Rust utilise trois préfixes de clé pour contrÎler la portée de l'état :

PréfixeConstantePortée
app:KEY_PREFIX_APPPartagé entre tous les utilisateurs et sessions
user:KEY_PREFIX_USERPartagé entre toutes les sessions pour un utilisateur
temp:KEY_PREFIX_TEMPEffacé aprÚs chaque invocation
(aucun)-Portée de session (par défaut)

app: - État de l'application

État partagĂ© entre tous les utilisateurs et toutes les sessions d'une application.

use adk_session::KEY_PREFIX_APP;

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

Cas d'utilisation :

  • Configuration de l'application
  • Ressources partagĂ©es
  • Compteurs ou statistiques globaux

user: - État de l'utilisateur

État partagĂ© entre toutes les sessions pour un utilisateur spĂ©cifique.

use adk_session::KEY_PREFIX_USER;

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

Cas d'utilisation :

  • PrĂ©fĂ©rences de l'utilisateur
  • DonnĂ©es de profil utilisateur
  • Contexte utilisateur inter-sessions

temp: - État temporaire

État qui est effacĂ© aprĂšs chaque invocation. Non persistant.

use adk_session::KEY_PREFIX_TEMP;

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

Cas d'utilisation :

  • RĂ©sultats de calculs intermĂ©diaires
  • Contexte d'opĂ©ration actuel
  • DonnĂ©es qui ne devraient pas persister

Pas de prĂ©fixe - État de session

Les clés sans préfixe sont limitées à la session (comportement par défaut).

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

Cas d'utilisation :

  • Contexte de conversation
  • DonnĂ©es spĂ©cifiques Ă  la session
  • État tour par tour

Définition de l'état initial

L'Ă©tat peut ĂȘtre initialisĂ© lors de la crĂ©ation d'une 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(())
}

Lecture de l'état

Accédez à l'état via la méthode state() de la 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);
}

Mises à jour de l'état via les événements

L'état est généralement mis à jour via des actions d'événement. Lorsqu'un événement est ajouté à une session, son state_delta est appliqué :

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?;

Comportement de la portée de l'état

Le service de session gÚre automatiquement la portée de l'état :

Lors de la création de session

  1. Extrait les clĂ©s prĂ©fixĂ©es par app: → Stocke dans l'Ă©tat de l'application
  2. Extrait les clĂ©s prĂ©fixĂ©es par user: → Stocke dans l'Ă©tat utilisateur
  3. ClĂ©s restantes (sauf temp:) → Stocke dans l'Ă©tat de session
  4. Fusionne toutes les portées pour la session retournée

Lors de la récupération de session

  1. Charge l'état de l'application
  2. Charge l'état utilisateur
  3. Charge l'état de session
  4. Fusionne toutes les portĂ©es (application → utilisateur → session)

Lors de l'ajout d'événement

  1. Extrait le delta d'état de l'événement
  2. Filtre les clés temp: (non persistantes)
  3. Applique les deltas app: à l'état de l'application
  4. Applique les deltas user: à l'état utilisateur
  5. Applique les deltas restants à l'état de session

Exemple complet

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

Templatisation des instructions avec l'état

Les valeurs d'Ă©tat peuvent ĂȘtre injectĂ©es dans les instructions de l'agent en utilisant la syntaxe {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()?;

Lorsque l'agent s'exécute, {user:name}, {topic}, et {user:language} sont remplacés par des valeurs provenant de l'état de session.

Meilleures Pratiques

1. Utiliser des portées appropriées

// ✅ 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. Utiliser l'état temporaire pour les données intermédiaires

// ✅ 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. Maintenir la cohérence des clés d'état

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

// ❌ Bad: Inconsistent naming
"user:theme"
"userLanguage"
"user-timezone"
  • Sessions - Aperçu de la gestion de session
  • Events - Structure d'Ă©vĂ©nement et state_delta
  • LlmAgent - ModĂ©lisation des instructions

PrĂ©cĂ©dent: ← Sessions | Suivant: Callbacks →

Gestion de l'état - Documentation ADK-Rust | ADK-Rust