상태 관리

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>;
}

상태 키 접두사

adk-rust는 상태 범위를 제어하기 위해 세 가지 키 접두사를 사용합니다:

PrefixConstantScope
app:KEY_PREFIX_APP모든 사용자와 세션에서 공유됨
user:KEY_PREFIX_USER사용자의 모든 세션에서 공유됨
temp:KEY_PREFIX_TEMP각 호출 후 지워짐
(none)-세션 범위 (기본값)

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

// 이 이벤트가 추가되면 상태가 업데이트됩니다.
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();
    
    // 초기 상태로 첫 번째 세션 생성
    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!("State scoping works correctly!");
    Ok(())
}

상태를 이용한 명령어 템플릿화

{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()?;

에이전트가 실행될 때, {user:name}, {topic}, {user:language}는 세션 상태의 값으로 대체됩니다.

모범 사례

1. 적절한 스코프 사용

// ✅ 좋음: 사용자 스코프의 사용자 환경설정
"user:theme"
"user:timezone"

// ✅ 좋음: 접두사 없는 세션별 컨텍스트
"current_task"
"conversation_summary"

// ✅ 좋음: 앱 스코프의 앱 전체 설정
"app:model_version"
"app:feature_flags"

// ❌ 나쁨: 세션 스코프의 사용자 데이터 (세션 간에 손실됨)
"user_preferences"  // "user:preferences"여야 함

2. 중간 데이터에 임시 상태 사용

// ✅ 좋음: temp 스코프의 중간 결과
"temp:search_results"
"temp:current_step"

// ❌ 나쁨: 불필요하게 영구 저장된 중간 데이터
"search_results"  // 데이터베이스에 저장될 것입니다

3. 상태 키를 일관되게 유지

// ✅ 좋음: 일관된 명명 규칙
"user:preferences.theme"
"user:preferences.language"

// ❌ 나쁨: 일관성 없는 명명
"user:theme"
"userLanguage"
"user-timezone"

관련 항목


이전: ← Sessions | 다음: Callbacks →