메모리

adk-memory을 사용하는 AI 에이전트를 위한 장기 의미 메모리입니다.

개요

메모리 시스템은 에이전트 대화를 위한 영구적이고 검색 가능한 저장소를 제공합니다. 일시적인 세션 상태와 달리 메모리는 세션 간에 유지되며, 에이전트가 이전 상호작용의 관련 컨텍스트를 떠올릴 수 있도록 합니다.

설치

[dependencies]
adk-memory = "2.1.0"

핵심 개념

MemoryEntry

콘텐츠, 작성자 및 타임스탬프가 포함된 단일 메모리 레코드입니다.

use adk_memory::MemoryEntry;
use adk_core::Content;
use chrono::Utc;

let entry = MemoryEntry {
    content: Content::new("user").with_text("I prefer dark mode"),
    author: "user".to_string(),
    timestamp: Utc::now(),
};

MemoryService 트레이트

메모리 백엔드를 위한 핵심 트레이트입니다.

#[async_trait]
pub trait MemoryService: Send + Sync {
    /// Store session memories for a user
    async fn add_session(
        &self,
        app_name: &str,
        user_id: &str,
        session_id: &str,
        entries: Vec<MemoryEntry>,
    ) -> Result<()>;

    /// Search memories by query
    async fn search(&self, req: SearchRequest) -> Result<SearchResponse>;
}

SearchRequest

메모리 검색을 위한 쿼리 매개변수입니다.

use adk_memory::SearchRequest;

let request = SearchRequest {
    query: "user preferences".to_string(),
    user_id: "user-123".to_string(),
    app_name: "my_app".to_string(),
    limit: None,
    min_score: None,
    project_id: None, // None = global only, Some("id") = global + project
};

InMemoryMemoryService

개발 및 테스트를 위한 간단한 인메모리 구현입니다.

use adk_memory::{InMemoryMemoryService, MemoryService, MemoryEntry, SearchRequest};
use adk_core::Content;
use chrono::Utc;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let memory = InMemoryMemoryService::new();

    // Store memories from a session
    let entries = vec![
        MemoryEntry {
            content: Content::new("user").with_text("I like Rust programming"),
            author: "user".to_string(),
            timestamp: Utc::now(),
        },
        MemoryEntry {
            content: Content::new("assistant").with_text("Rust is great for systems programming"),
            author: "assistant".to_string(),
            timestamp: Utc::now(),
        },
    ];

    memory.add_session("my_app", "user-123", "session-1", entries).await?;

    // Search memories
    let request = SearchRequest {
        query: "Rust".to_string(),
        user_id: "user-123".to_string(),
        app_name: "my_app".to_string(),
        limit: None,
        min_score: None,
        project_id: None,
    };

    let response = memory.search(request).await?;
    println!("Found {} memories", response.memories.len());

    Ok(())
}

메모리 격리

메모리는 다음 기준으로 격리됩니다.

  • app_name: 서로 다른 애플리케이션은 별도의 메모리 공간을 가집니다.
  • user_id: 각 사용자의 메모리는 비공개입니다.
  • project_id (선택 사항): 항목의 범위를 사용자 내 프로젝트로 지정할 수 있습니다.
// User A's memories
memory.add_session("app", "user-a", "sess-1", entries_a).await?;

// User B's memories (separate)
memory.add_session("app", "user-b", "sess-1", entries_b).await?;

// Search only returns user-a's memories
let request = SearchRequest {
    query: "topic".to_string(),
    user_id: "user-a".to_string(),
    app_name: "app".to_string(),
    limit: None,
    min_score: None,
    project_id: None, // None = global entries only
};

프로젝트 범위 메모리

메모리의 범위를 사용자 내 프로젝트로 지정할 수 있습니다. 격리 키는 (app_name, user_id, project_id?)이 됩니다.

  • 글로벌 항목 (project_id = None): 모든 프로젝트 컨텍스트와 글로벌 전용 검색에서 표시됩니다.
  • 프로젝트 항목 (project_id = Some(id)): 해당 특정 프로젝트 내에서 검색할 때만 표시됩니다.
  • 프로젝트 검색 (project_id = Some(id)): 글로벌 항목과 해당 프로젝트의 항목을 반환합니다.
  • 글로벌 검색 (project_id = None): 글로벌 항목만 반환합니다.

프로젝트 범위 항목 저장

use adk_memory::{InMemoryMemoryService, MemoryService, MemoryEntry};
use adk_core::Content;
use chrono::Utc;

let service = InMemoryMemoryService::new();

let entry = MemoryEntry {
    content: Content::new("user").with_text("Project uses microservices"),
    author: "user".to_string(),
    timestamp: Utc::now(),
};

// Global entry (no project scope)
service.add_session("app", "user-1", "sess-1", vec![entry.clone()]).await?;

// Project-scoped entry
service.add_session_to_project("app", "user-1", "sess-2", "my-project", vec![entry.clone()]).await?;

// Single entry to a project
service.add_entry_to_project("app", "user-1", "my-project", entry).await?;

프로젝트 범위로 검색

use adk_memory::SearchRequest;

// Global-only search — returns only global entries
let global = service.search(SearchRequest {
    query: "microservices".into(),
    user_id: "user-1".into(),
    app_name: "app".into(),
    limit: None,
    min_score: None,
    project_id: None,
}).await?;

// Project search — returns global + project entries
let project = service.search(SearchRequest {
    query: "microservices".into(),
    user_id: "user-1".into(),
    app_name: "app".into(),
    limit: None,
    min_score: None,
    project_id: Some("my-project".into()),
}).await?;

프로젝트 범위 삭제

// Delete entries matching a query within a project only
service.delete_entries_in_project("app", "user-1", "my-project", "microservices").await?;

// Delete ALL entries for a project
service.delete_project("app", "user-1", "my-project").await?;

// Global delete — only removes global entries, project entries are unaffected
service.delete_entries("app", "user-1", "microservices").await?;

// GDPR delete_user — removes everything (global + all projects)
service.delete_user("app", "user-1").await?;

프로젝트 범위로 MemoryServiceAdapter 사용

MemoryServiceAdapter은 MemoryService를 adk_core::Memory에 연결합니다. with_project_id()를 사용하여 모든 작업의 범위를 지정합니다.

use adk_memory::{InMemoryMemoryService, MemoryServiceAdapter};
use adk_core::Memory;
use std::sync::Arc;

let service = Arc::new(InMemoryMemoryService::new());

// Adapter without project — operates on global entries
let global_adapter = MemoryServiceAdapter::new(service.clone(), "app", "user-1");

// Adapter with project — all search/add/delete operations scoped to the project
let project_adapter = MemoryServiceAdapter::new(service.clone(), "app", "user-1")
    .with_project_id("my-project");

// Core Memory trait also supports ad-hoc project access
global_adapter.search_in_project("query", "other-project").await?;
global_adapter.add_to_project(entry, "other-project").await?;

프로젝트 ID 검증

프로젝트 식별자는 모든 쓰기 작업에서 검증됩니다:

  • 비어 있지 않아야 함
  • 256자를 초과하지 않아야 함
use adk_memory::validate_project_id;

validate_project_id("my-project")?;       // Ok
validate_project_id("")?;                  // Err: must not be empty
validate_project_id(&"x".repeat(257))?;   // Err: exceeds 256 chars

검색 의미 체계 매트릭스

SearchRequest.project_id전역 항목 반환프로젝트 항목 반환
None✅ 쿼리와 일치❌ 없음
Some("A")✅ 쿼리와 일치✅ 쿼리와 일치하는 프로젝트 "A" 항목만

삭제 의미 체계 매트릭스

작업범위
delete_entries (프로젝트 없음)쿼리와 일치하는 전역 항목만
delete_entries_in_project("A")쿼리와 일치하는 프로젝트 "A" 항목만
delete_project("A")"A" 프로젝트의 모든 항목
delete_user모든 항목(전역 + 모든 프로젝트)

검색 동작

InMemoryMemoryService는 단어 기반 일치를 사용합니다.

  1. 쿼리가 단어로 토큰화됩니다(소문자).
  2. 각 메모리의 콘텐츠가 토큰화됩니다.
  3. 일치하는 단어가 하나라도 있는 메모리가 반환됩니다.
// Query: "rust programming"
// Matches memories containing "rust" OR "programming"

사용자 지정 메모리 백엔드

사용자 지정 저장소(예: 벡터 데이터베이스)를 위해 MemoryService를 구현합니다.

use adk_memory::{MemoryService, MemoryEntry, SearchRequest, SearchResponse};
use adk_core::Result;
use async_trait::async_trait;

pub struct VectorMemoryService {
    // Your vector DB client
}

#[async_trait]
impl MemoryService for VectorMemoryService {
    async fn add_session(
        &self,
        app_name: &str,
        user_id: &str,
        session_id: &str,
        entries: Vec<MemoryEntry>,
    ) -> Result<()> {
        // 1. Generate embeddings for each entry
        // 2. Store in vector database with metadata
        Ok(())
    }

    async fn search(&self, req: SearchRequest) -> Result<SearchResponse> {
        // 1. Generate embedding for query
        // 2. Perform similarity search
        // 3. Return top-k results
        Ok(SearchResponse { memories: vec![] })
    }
}

에이전트와의 통합

메모리는 LlmAgentBuilder와 통합됩니다.

use adk_agent::LlmAgentBuilder;
use adk_memory::InMemoryMemoryService;
use std::sync::Arc;

let memory = Arc::new(InMemoryMemoryService::new());

let agent = LlmAgentBuilder::new("assistant")
    .model(model)
    .instruction("You are a helpful assistant with memory.")
    .memory(memory)
    .build()?;

메모리가 구성된 경우:

  1. 각 턴 전에 관련 메모리가 검색됩니다.
  2. 일치하는 메모리가 컨텍스트에 삽입됩니다.
  3. 각 세션 후 대화가 메모리로 저장됩니다.

아키텍처

┌─────────────────────────────────────────────────────────────┐
│                      Agent Request                          │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                    Memory Search                            │
│                                                             │
│   SearchRequest { query, user_id, app_name, project_id }   │
│                         │                                   │
│                         ▼                                   │
│   ┌─────────────────────────────────────────────────────┐  │
│   │              MemoryService                          │  │
│   │  ┌─────────┐ ┌──────────┐ ┌────────┐ ┌──────────┐  │  │
│   │  │InMemory │ │ SQLite   │ │Postgres│ │  Redis   │  │  │
│   │  │(dev)    │ │          │ │pgvector│ │          │  │  │
│   │  └─────────┘ └──────────┘ └────────┘ └──────────┘  │  │
│   │  ┌─────────┐ ┌──────────┐                           │  │
│   │  │MongoDB  │ │  Neo4j   │                           │  │
│   │  └─────────┘ └──────────┘                           │  │
│   └─────────────────────────────────────────────────────┘  │
│                         │                                   │
│                         ▼                                   │
│   SearchResponse { memories: Vec<MemoryEntry> }            │
│   (filtered by project scope)                              │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│              Context Injection                              │
│                                                             │
│   Relevant memories added to agent context                 │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                    Agent Execution                          │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                   Memory Storage                            │
│                                                             │
│   Session conversation stored for future recall            │
│   (global or project-scoped)                               │
└─────────────────────────────────────────────────────────────┘

모범 사례

실행 지침설명
프로덕션에서 벡터 DB 사용InMemory은 개발/테스트 전용입니다
사용자별로 범위 지정개인정보 보호를 위해 항상 user_id을 포함합니다
결과 제한컨텍스트 오버플로를 방지하도록 반환되는 메모리 수 제한
오래된 메모리 정리오래된 데이터를 위해 TTL 또는 보관 기능 구현
전략적으로 임베딩원시 대화가 아닌 요약 저장

세션과의 비교

기능세션 상태메모리
지속성세션 수명영구적
범위단일 세션세션 간
검색키-값 조회의미 검색
사용 사례현재 컨텍스트장기 기억

이전: ← 가드레일 | 다음: 스튜디오 →