إدارة الحالة

حالة الجلسة في ADK-Rust تسمح للوكلاء بتخزين واسترداد البيانات التي تستمر عبر أدوار المحادثة. يتم تنظيم الحالة باستخدام بادئات المفاتيح التي تحدد نطاق البيانات وعمرها.

نظرة عامة

يتم تخزين الحالة كأزواج مفتاح-قيمة حيث:

  • المفاتيح هي سلاسل نصية ذات بادئات اختيارية
  • القيم هي قيم JSON (serde_json::Value)

يتيح نظام البادئات مستويات نطاق مختلفة:

  • نطاق الجلسة: افتراضي، مرتبط بجلسة واحدة
  • نطاق المستخدم: مشترك عبر جميع الجلسات لمستخدم واحد
  • نطاق التطبيق: مشترك عبر جميع مستخدمي التطبيق
  • مؤقت: يتم مسحه بعد كل استدعاء

سمة الحالة

تحدد سمة 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>;
}

بادئات مفاتيح الحالة

يستخدم ADK-Rust ثلاث بادئات مفاتيح للتحكم في نطاق الحالة:

بادئةثابتالنطاق
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?;

سلوك تحديد نطاق الحالة

تتعامل خدمة الجلسة مع تحديد نطاق الحالة تلقائيًا:

عند إنشاء الجلسة

  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(())
}

قوالب التعليمات مع الحالة

يمكن حقن قيم الحالة في تعليمات Agent باستخدام صيغة {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()?;

عند تشغيل 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"

السابق: ← الجلسات | التالي: Callbacks →