메모리

AI 에이전트를 위한 장기 의미론적 메모리 (adk-memory 사용).

개요

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

설치

[dependencies]
adk-memory = "2.0.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?)이 됩니다:

  • Global entries (project_id = None): 모든 프로젝트 컨텍스트 및 전역 전용 검색에서 볼 수 있습니다.
  • Project entries (project_id = Some(id)): 해당 특정 프로젝트 내에서 검색할 때만 볼 수 있습니다.
  • Project search (project_id = Some(id)): 전역 항목 + 해당 프로젝트의 항목을 반환합니다.
  • Global search (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 프로젝트 범위 사용

MemoryServiceAdapterMemoryServiceadk_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![] })
    }
}

Agent와의 통합

메모리는 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 또는 아카이빙을 구현하세요
전략적으로 임베딩원시 대화가 아닌 요약을 저장하세요

세션과의 비교

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

이전: ← Guardrails | 다음: Studio →

메모리 - ADK-Rust 문서 | ADK-Rust