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 →