ステート管理

ADK-Rustにおけるセッションステートは、エージェントが会話のターンをまたいで永続するデータを保存および取得することを可能にします。ステートは、データのスコープとライフタイムを決定するキープレフィックスを使用して整理されます。

概要

ステートはキーと値のペアとして保存されます。ここで:

  • キーはオプションのプレフィックスを持つ文字列です
  • 値はJSON値(serde_json::Value)です

プレフィックスシステムは、異なるスコープレベルを可能にします:

  • セッションスコープ: デフォルトで、単一のセッションに紐付けられます
  • ユーザースコープ: ユーザーのすべてのセッションで共有されます
  • アプリスコープ: アプリケーションのすべてのユーザーで共有されます
  • 一時的: 各呼び出し後にクリアされます

State Trait

State traitは、ステートアクセス用のインターフェースを定義します:

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 traitもあります:

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

状態キーのプレフィックス

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"

ユースケース:

  • 中間計算結果
  • 現在の操作コンテキスト
  • 永続化すべきでないデータ

No Prefix - セッション状態

プレフィックスのないキーはセッションスコープです(デフォルトの動作)。

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 は状態スコープを自動的に処理します:

セッション作成時

  1. app: プレフィックス付きキーを抽出 → アプリケーション状態に保存
  2. user: プレフィックス付きキーを抽出 → ユーザー状態に保存
  3. 残りのキー(temp: を除く) → セッション状態に保存
  4. 返されるセッションのためにすべてのスコープをマージ

セッション取得時

  1. アプリケーションのアプリケーション状態をロード
  2. ユーザーのユーザー状態をロード
  3. セッション状態をロード
  4. すべてのスコープをマージ(アプリケーション → ユーザー → セッション)

イベント追加時

  1. イベントから状態の差分を抽出
  2. temp: キーを除外(永続化されないため)
  3. app: の差分をアプリケーション状態に適用
  4. user: の差分をユーザー状態に適用
  5. 残りの差分をセッション状態に適用

完全な例

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 - セッション管理の概要
  • Events - イベント構造と state_delta
  • LlmAgent - 命令のテンプレート化

前へ: ← Sessions | 次へ: Callbacks →

ステート管理 - ADK-Rust ドキュメント | ADK-Rust