状態管理
adk-rustにおけるセッション状態により、エージェントは会話のターンを超えて持続するデータを保存および取得できます。状態は、データのスコープと寿命を決定するキープレフィックスを使用して整理されます。
概要
状態はキーと値のペアとして保存されます。ここで:
- キーはオプションのプレフィックスを持つ文字列です。
- 値はJSON値(
serde_json::Value)です。
プレフィックスシステムにより、異なるスコープレベルが可能になります:
- セッションスコープ: デフォルト。単一のセッションに紐付けられます。
- ユーザースコープ: あるユーザーのすべてのセッションで共有されます。
- アプリスコープ: アプリケーションのすべてのユーザーで共有されます。
- 一時的: 各呼び出し後にクリアされます。
State トレイト
Stateトレイトは、状態アクセス用のインターフェースを定義します。
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>;
}
読み取り専用アクセス用のReadonlyStateトレイトもあります。
pub trait ReadonlyState: Send + Sync {
fn get(&self, key: &str) -> Option<Value>;
fn all(&self) -> HashMap<String, Value>;
}
State キープレフィックス
adk-rustは、状態のスコープを制御するために3つのキープレフィックスを使用します。
| プレフィックス | 定数 | スコープ |
|---|---|---|
app: | KEY_PREFIX_APP | すべてのユーザーとセッションで共有 |
user: | KEY_PREFIX_USER | あるユーザーのすべてのセッションで共有 |
temp: | KEY_PREFIX_TEMP | 各呼び出し後にクリアされる |
| (なし) | - | セッションスコープ(デフォルト) |
app: - アプリケーション状態
アプリケーションのすべてのユーザーとセッションで共有される状態です。
use adk_session::KEY_PREFIX_APP;
// KEY_PREFIX_APP = "app:"
let key = format!("{}settings", KEY_PREFIX_APP); // "app:settings"
使用例:
- アプリケーション構成
- 共有リソース
- グローバルなカウンターや統計
user: - ユーザー状態
特定のユーザーのすべてのセッションで共有される状態です。
use adk_session::KEY_PREFIX_USER;
// KEY_PREFIX_USER = "user:"
let key = format!("{}preferences", KEY_PREFIX_USER); // "user:preferences"
使用例:
- ユーザー設定
- ユーザープロファイルデータ
- セッションをまたがるユーザーコンテキスト
temp: - 一時的な状態
各呼び出し後にクリアされる状態です。永続化されません。
use adk_session::KEY_PREFIX_TEMP;
// KEY_PREFIX_TEMP = "temp:"
let key = format!("{}current_step", KEY_PREFIX_TEMP); // "temp:current_step"
使用例:
- 中間的な計算結果
- 現在の操作コンテキスト
- 永続化すべきではないデータ
プレフィックスなし - セッション状態
プレフィックスのないキーはセッションスコープです(デフォルトの動作)。
let key = "conversation_topic"; // Session-scoped
使用例:
- 会話コンテキスト
- セッション固有のデータ
- ターンごとの状態
初期状態の設定
状態はセッション作成時に初期化できます。
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(())
}
状態の読み取り
セッションの state() メソッドを通じて状態にアクセスします。
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);
}
イベントによる状態の更新
状態は通常、イベントのアクションを通じて更新されます。イベントがセッションに追加されると、その 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()
};
// When this event is appended, state is updated
service.append_event(session.id(), event).await?;
状態スコープの動作
SessionService は状態スコープを自動的に処理します。
セッション作成時
app:プレフィックス付きキーを抽出 → アプリ状態に保存user:プレフィックス付きキーを抽出 → ユーザー状態に保存- 残りのキー (
temp:を除く) → セッション状態に保存 - 返されるセッションのためにすべてのスコープをマージ
セッション取得時
- アプリケーションのアプリ状態をロード
- ユーザーのユーザー状態をロード
- セッション状態をロード
- すべてのスコープをマージ (アプリ → ユーザー → セッション)
イベント追加時
- イベントから状態デルタを抽出
temp:キーをフィルター処理 (永続化されない)app:デルタをアプリ状態に適用user:デルタをユーザー状態に適用- 残りのデルタをセッション状態に適用
完全な例
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(())
}
状態を用いた指示のテンプレート化
状態の値は、{key} 構文を使用して Agent の指示に注入できます。
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()?;
Agent が実行されると、{user:name}、{topic}、および {user:language} はセッション状態の値に置き換えられます。
ベストプラクティス
1. 適切なスコープを使用する
// ✅ 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. 中間データには一時的なステートを使用する
// ✅ 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. ステートキーの一貫性を保つ
// ✅ Good: Consistent naming convention
"user:preferences.theme"
"user:preferences.language"
// ❌ Bad: Inconsistent naming
"user:theme"
"userLanguage"
"user-timezone"
関連
前へ: ← Sessions | 次へ: コールバック →