Zustandsverwaltung
Der Session-Zustand in ADK-Rust ermöglicht es Agents, Daten zu speichern und abzurufen, die über mehrere Konversationsrunden hinweg bestehen bleiben. Der Zustand wird mithilfe von Schlüsselpräfixen organisiert, die den Gültigkeitsbereich und die Lebensdauer der Daten bestimmen.
Überblick
Der Zustand wird als Schlüssel-Wert-Paare gespeichert, wobei:
- Schlüssel Strings mit optionalen Präfixen sind
- Werte JSON-Werte (
serde_json::Value) sind
Das Präfixsystem ermöglicht verschiedene Gültigkeitsbereiche:
- Session-scoped: Standard, an eine einzelne Session gebunden
- User-scoped: Über alle Sessions eines Benutzers hinweg geteilt
- App-scoped: Über alle Benutzer einer Anwendung hinweg geteilt
- Temporary: Nach jeder Ausführung gelöscht
State Trait
Das State trait definiert die Schnittstelle für den State-Zugriff:
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>;
}
Es gibt auch ein ReadonlyState trait für den schreibgeschützten Zugriff:
pub trait ReadonlyState: Send + Sync {
fn get(&self, key: &str) -> Option<Value>;
fn all(&self) -> HashMap<String, Value>;
}
State-Schlüsselpräfixe
ADK-Rust verwendet drei Schlüsselpräfixe, um den State-Gültigkeitsbereich zu steuern:
| Präfix | Konstante | Gültigkeitsbereich |
|---|---|---|
app: | KEY_PREFIX_APP | Geteilt über alle Benutzer und Sessions |
user: | KEY_PREFIX_USER | Geteilt über alle Sessions für einen Benutzer |
temp: | KEY_PREFIX_TEMP | Nach jeder Ausführung gelöscht |
| (keine) | - | Session-bezogen (Standard) |
app: - Anwendungszustand
Der Zustand wird über alle Benutzer und Sessions einer Anwendung hinweg geteilt.
use adk_session::KEY_PREFIX_APP;
// KEY_PREFIX_APP = "app:"
let key = format!("{}settings", KEY_PREFIX_APP); // "app:settings"
Anwendungsfälle:
- Anwendungskonfiguration
- Geteilte Ressourcen
- Globale Zähler oder Statistiken
user: - Benutzerzustand
Der Zustand wird über alle Sessions für einen bestimmten Benutzer hinweg geteilt.
use adk_session::KEY_PREFIX_USER;
// KEY_PREFIX_USER = "user:"
let key = format!("{}preferences", KEY_PREFIX_USER); // "user:preferences"
Anwendungsfälle:
- Benutzereinstellungen
- Benutzerprofildaten
- Benutzerkontext über mehrere Sessions hinweg
temp: - Temporärer Zustand
Der Zustand, der nach jeder Ausführung gelöscht wird. Wird nicht gespeichert.
use adk_session::KEY_PREFIX_TEMP;
// KEY_PREFIX_TEMP = "temp:"
let key = format!("{}current_step", KEY_PREFIX_TEMP); // "temp:current_step"
Anwendungsfälle:
- Zwischenergebnisse von Berechnungen
- Kontext der aktuellen Operation
- Daten, die nicht dauerhaft gespeichert werden sollen
Kein Präfix - Session-Zustand
Schlüssel ohne Präfix sind Session-bezogen (Standardverhalten).
let key = "conversation_topic"; // Session-scoped
Anwendungsfälle:
- Konversationskontext
- Session-spezifische Daten
- Zustand von Runde zu Runde
Initialen Zustand festlegen
Der Zustand kann bei der Erstellung einer Session initialisiert werden:
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(())
}
Status lesen
Greifen Sie über die state()-Methode der Session auf den Status zu:
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);
}
Statusaktualisierungen über Events
Der Status wird typischerweise über Event-Aktionen aktualisiert. Wenn ein Event an eine Session angehängt wird, wird sein state_delta angewendet:
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?;
Verhalten der Statusbereichsdefinition (State Scoping)
Der Session Service handhabt die Statusbereichsdefinition automatisch:
Bei der Session-Erstellung
- Extrahieren Sie mit
app:präfixierte Schlüssel → Speichern Sie im App-Status - Extrahieren Sie mit
user:präfixierte Schlüssel → Speichern Sie im Benutzer-Status - Verbleibende Schlüssel (außer
temp:) → Speichern Sie im Session-Status - Führen Sie alle Bereiche für die zurückgegebene Session zusammen
Beim Abrufen einer Session
- Laden Sie den App-Status für die Anwendung
- Laden Sie den Benutzer-Status für den Benutzer
- Laden Sie den Session-Status
- Führen Sie alle Bereiche zusammen (App → Benutzer → Session)
Beim Anhängen eines Events
- Extrahieren Sie das State-Delta aus dem Event
- Filtern Sie
temp:Schlüssel heraus (nicht persistent) - Wenden Sie
app:Deltas auf den App-Status an - Wenden Sie
user:Deltas auf den Benutzer-Status an - Wenden Sie die verbleibenden Deltas auf den Session-Status an
Vollständiges Beispiel
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(())
}
Anweisungs-Templating mit State
Statuswerte können unter Verwendung der {key}-Syntax in Agent-Anweisungen eingefügt werden:
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()?;
Wenn der Agent ausgeführt wird, werden {user:name}, {topic} und {user:language} durch Werte aus dem Session-Status ersetzt.
Best Practices
1. Geeignete Scopes verwenden
// ✅ Gut: Benutzereinstellungen im Benutzer-Scope
"user:theme"
"user:timezone"
// ✅ Gut: Session-spezifischer Kontext ohne Präfix
"current_task"
"conversation_summary"
// ✅ Gut: App-weite Einstellungen im App-Scope
"app:model_version"
"app:feature_flags"
// ❌ Schlecht: Benutzerdaten im Session-Scope (gehen zwischen Sessions verloren)
"user_preferences" // Sollte "user:preferences" sein
2. Temporären Zustand für Zwischendaten verwenden
// ✅ Gut: Zwischenergebnisse im temporären Scope
"temp:search_results"
"temp:current_step"
// ❌ Schlecht: Zwischendaten unnötig persistent gespeichert
"search_results" // Wird in der Datenbank gespeichert
3. Zustandsschlüssel konsistent halten
// ✅ Gut: Konsistente Namenskonvention
"user:preferences.theme"
"user:preferences.language"
// ❌ Schlecht: Inkonsistente Benennung
"user:theme"
"userLanguage"
"user-timezone"
Verwandt
- Sessions - Übersicht über das Session-Management
- Events - Event-Struktur und state_delta
- LlmAgent - Anweisungs-Templating
Zurück: ← Sessions | Weiter: Callbacks →