状态管理

adk-rust 中的 Session 状态允许 Agent 存储和检索在对话轮次之间持久存在的数据。状态通过键前缀进行组织,这些前缀决定了数据的范围和生命周期。

概述

状态以键值对的形式存储,其中:

  • 键是带有可选前缀的字符串
  • 值是 JSON 值 (serde_json::Value)

前缀系统实现了不同的作用域级别:

  • Session 作用域: 默认,与单个 Session 绑定
  • 用户作用域: 在用户的所有 Session 之间共享
  • 应用作用域: 在应用程序的所有用户之间共享
  • 临时作用域: 在每次调用后清除

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 使用三个键前缀来控制状态作用域:

前缀常量作用域
app:KEY_PREFIX_APP在所有用户和 Session 之间共享
user:KEY_PREFIX_USER在特定用户的所有 Session 之间共享
temp:KEY_PREFIX_TEMP在每次调用后清除
(无)-Session 作用域 (默认)

app: - 应用状态

在应用程序的所有用户和 Session 之间共享的状态。

use adk_session::KEY_PREFIX_APP;

// KEY_PREFIX_APP = "app:"
let key = format!("{}settings", KEY_PREFIX_APP);  // "app:settings"

用例:

  • 应用程序配置
  • 共享资源
  • 全局计数器或统计数据

user: - 用户状态

在特定用户的所有 Session 之间共享的状态。

use adk_session::KEY_PREFIX_USER;

// KEY_PREFIX_USER = "user:"
let key = format!("{}preferences", KEY_PREFIX_USER);  // "user:preferences"

用例:

  • 用户偏好设置
  • 用户资料数据
  • Session 用户上下文

temp: - 临时状态

每次调用后清除且不持久化的状态。

use adk_session::KEY_PREFIX_TEMP;

// KEY_PREFIX_TEMP = "temp:"
let key = format!("{}current_step", KEY_PREFIX_TEMP);  // "temp:current_step"

用例:

  • 中间计算结果
  • 当前操作上下文
  • 不应持久化的数据

无前缀 - Session 状态

没有前缀的键是 Session 作用域的(默认行为)。

let key = "conversation_topic";  // Session-scoped

用例:

  • 对话上下文
  • Session 特定的数据
  • 逐轮状态

设置初始状态

可以在创建 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();

    // 应用作用域状态
    initial_state.insert(
        format!("{}version", KEY_PREFIX_APP),
        json!("1.0.0")
    );

    // 用户作用域状态
    initial_state.insert(
        format!("{}name", KEY_PREFIX_USER),
        json!("Alice")
    );

    // Session 作用域状态
    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

通过 sessionstate() 方法访问 state:

let state = session.state();

// 获取特定键
if let Some(value) = state.get("topic") {
    println!("Topic: {}", value);
}

// 获取应用范围的状态
if let Some(version) = state.get("app:version") {
    println!("App version: {}", version);
}

// 获取所有状态
let all_state = state.all();
for (key, value) in all_state {
    println!("{}: {}", key, value);
}

通过 Events 更新 State

State 通常通过 event actions 进行更新。当一个 event 被附加到 session 时,其 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()
};

// 当此事件被附加时,状态会更新
service.append_event(session.id(), event).await?;

State 作用域行为

session service 会自动处理 state 作用域:

在 Session 创建时

  1. 提取 app: 前缀的键 → 存储到 app state
  2. 提取 user: 前缀的键 → 存储到 user state
  3. 剩余的键(除了 temp:)→ 存储到 session state
  4. 合并所有作用域以返回 session

在 Session 检索时

  1. 为应用程序加载 app state
  2. 为用户加载 user state
  3. 加载 session state
  4. 合并所有作用域 (app → user → session)

在 Event 附加时

  1. 从 event 中提取 state delta
  2. 过滤掉 temp: 键(不持久化)
  3. app: delta 应用到 app state
  4. user: delta 应用到 user state
  5. 将剩余的 delta 应用到 session state

完整示例

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();
    
    // 创建第一个会话并带有初始状态
    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?;
    
    // 为同一用户创建第二个会话
    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?;
    
    // 会话 2 继承了应用和用户状态
    let s2_state = session2.state();
    
    // 应用状态是共享的
    assert_eq!(s2_state.get("app:theme"), Some(json!("dark")));
    
    // 用户状态是共享的
    assert_eq!(s2_state.get("user:language"), Some(json!("en")));
    
    // 会话状态是独立的
    assert_eq!(s2_state.get("context"), Some(json!("session2")));
    
    println!("状态作用域工作正常!");
    Ok(())
}

使用 State 进行指令模板化

可以使用 {key} 语法将 state 值注入 agent instructions:

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} 会被替换为 session state 中的值。

最佳实践

1. 使用适当的作用域

// ✅ 良好:用户偏好在用户作用域
"user:theme"
"user:timezone"

// ✅ 良好:会话特定上下文无需前缀
"current_task"
"conversation_summary"

// ✅ 良好:应用范围设置在应用作用域
"app:model_version"
"app:feature_flags"

// ❌ 不佳:会话作用域中的用户数据(会在会话之间丢失)
"user_preferences"  // 应该使用 "user:preferences"

2. 对中间数据使用临时状态

// ✅ 良好:中间结果在临时作用域
"temp:search_results"
"temp:current_step"

// ❌ 不佳:中间数据不必要地持久化
"search_results"  // 将保存到数据库

3. 保持状态键一致

// ✅ 良好:一致的命名约定
"user:preferences.theme"
"user:preferences.language"

// ❌ 不佳:不一致的命名
"user:theme"
"userLanguage"
"user-timezone"

相关


上一页: ← Sessions | 下一页: Callbacks →