Gestion de l'état
L'état de Session dans adk-rust permet aux Agent de stocker et de récupérer des données qui persistent au fil des tours de conversation. L'état est organisé à l'aide de préfixes de clés qui déterminent la portée et la durée de vie des données.
Aperçu
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 Session : Par défaut, liée à une seule Session
- Portée Utilisateur : Partagée entre toutes les Session pour un utilisateur
- Portée Application : Partagée entre tous les utilisateurs d'une application
- Temporaire : Effacée après chaque invocation
Trait State
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éfixe | Constante | Portée |
|---|---|---|
app: | KEY_PREFIX_APP | Partagée entre tous les utilisateurs et Sessions |
user: | KEY_PREFIX_USER | Partagée entre toutes les Sessions pour un utilisateur |
temp: | KEY_PREFIX_TEMP | Effacée après chaque invocation |
| (aucun) | - | Portée Session (par défaut) |
app: - État de l'application
État partagé entre tous les utilisateurs et 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-Session
temp: - État temporaire
État qui est effacé après chaque invocation. Non persisté.
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 calcul intermédiaires
- Contexte d'opération actuel
- Données qui ne devraient pas persister
Aucun préfixe - État de Session
Les clés sans préfixe ont une portée 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 les 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()
};
// Lorsque cet événement est ajouté, l'état est mis à jour
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 :
À la Création de Session
- Extrait les clés préfixées par
app:→ Stocke dans l'état de l'application - Extrait les clés préfixées par
user:→ Stocke dans l'état de l'utilisateur - Les clés restantes (sauf
temp:) → Stocke dans l'état de la session - Fusionne toutes les portées pour la session renvoyée
À la Récupération de Session
- Charge l'état de l'application
- Charge l'état de l'utilisateur
- Charge l'état de la session
- Fusionne toutes les portées (application → utilisateur → session)
À l'Ajout d'Événement
- Extrait le delta d'état de l'événement
- Filtre les clés
temp:(non persistées) - Applique les deltas
app:à l'état de l'application - Applique les deltas
user:à l'état de l'utilisateur - Applique les deltas restants à l'état de la 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();
// Crée la première session avec un état initial
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?;
// Crée une deuxième session pour le même utilisateur
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 Session 2 hérite de l'état de l'application et de l'utilisateur
let s2_state = session2.state();
// L'état de l'application est partagé
assert_eq!(s2_state.get("app:theme"), Some(json!("dark")));
// L'état de l'utilisateur est partagé
assert_eq!(s2_state.get("user:language"), Some(json!("en")));
// L'état de la session est séparé
assert_eq!(s2_state.get("context"), Some(json!("session2")));
println!("La portée de l'état fonctionne correctement !");
Ok(())
}
Templating d'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 les valeurs de l'état de la session.
Bonnes Pratiques
1. Utiliser des Portées Appropriées
// ✅ Bien : Préférences utilisateur dans la portée utilisateur
"user:theme"
"user:timezone"
// ✅ Bien : Contexte spécifique à la session sans préfixe
"current_task"
"conversation_summary"
// ✅ Bien : Paramètres globaux de l'application dans la portée de l'application
"app:model_version"
"app:feature_flags"
// ❌ Mauvais : Données utilisateur dans la portée de la session (perdues entre les sessions)
"user_preferences" // Devrait être "user:preferences"
2. Utiliser un État Temporaire pour les Données Intermédiaires
// ✅ Bien : Résultats intermédiaires dans la portée temporaire
"temp:search_results"
"temp:current_step"
// ❌ Mauvais : Données intermédiaires persistées inutilement
"search_results" // Seront sauvegardées dans la base de données
3. Maintenir la Cohérence des Clés d'État
// ✅ Bien : Convention de nommage cohérente
"user:preferences.theme"
"user:preferences.language"
// ❌ Mauvais : Nommage incohérent
"user:theme"
"userLanguage"
"user-timezone"
Liens Utiles
- Sessions - Vue d'ensemble de la gestion des sessions
- Événements - Structure des événements et state_delta
- LlmAgent - Modélisation des instructions
Précédent: ← Sessions | Suivant: Callbacks →