메모리 개념

메모리의 semantic-store 쪽은 몇 가지 작은 타입으로 구성됩니다. 이 네 가지를 익히면 나머지 섹션을 따라가기 쉽습니다.

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

Entry는 저장의 단위이자 회상의 단위입니다 — search는 쿼리와 가장 관련성이 높은 entry들을 반환합니다.

MemoryService trait

모든 backend는 하나의 trait을 구현합니다. 두 개의 메서드는 필수이며, 나머지는 backend가 재정의할 수 있는 기본 구현을 가집니다:

#[async_trait]
pub trait MemoryService: Send + Sync {
    // Required
    async fn add_session(&self, app: &str, user: &str, session: &str,
                         entries: Vec<MemoryEntry>) -> Result<()>;
    async fn search(&self, req: SearchRequest) -> Result<SearchResponse>;

    // Optional (default: "not implemented")
    async fn delete_user(&self, app: &str, user: &str) -> Result<()>;          // GDPR
    async fn delete_session(&self, app: &str, user: &str, session: &str) -> Result<()>;
    async fn add_entry(&self, app: &str, user: &str, entry: MemoryEntry) -> Result<()>;
    async fn delete_entries(&self, app: &str, user: &str, query: &str) -> Result<u64>;
    // …plus a health check
}
  • **add_session**는 완료된 대화 분량의 entry들을 받아들입니다. memory가 연결되어 있으면 Runner가 이를 대신 호출합니다.
  • **search**는 회상입니다. backend는 쿼리를 keyword, embedding similarity, 또는 graph token-scoring 방식으로 다르게 해석하지만, 계약은 동일합니다.

SearchRequest / SearchResponse

use adk_memory::SearchRequest;

let req = SearchRequest {
    app_name: "support".into(),
    user_id:  "alice".into(),
    query:    "contact preference".into(),
    ..Default::default()      // top_k, project scoping, etc.
};

let resp = memory.search(req).await?;   // resp.entries: Vec<MemoryEntry>

세션 상태 vs. memory

이것들은 서로 다른 도구입니다 — 혼동하지 마세요:

세션 상태메모리
수명하나의 대화대화 전반
APIadk-session (SessionService, 상태 맵)adk-memory (MemoryService)
보유실시간 대화록 + 임시 상태나중에 기억할 만한 지속 가능한 사실
읽기항상 컨텍스트에 포함됨필요할 때, search_memory를 통해

일반적인 흐름은 다음과 같습니다: 대화는 세션 상태에 유지되고; 끝나면(또는 각 턴마다) 중요한 부분이 메모리에 기록됩니다; 다음 세션은 컨텍스트를 다시 불러오기 위해 메모리를 검색합니다. Sessions & State를 참조하세요.

격리: 앱, 사용자, 프로젝트

메모리는 항상 (app_name, user_id)로 키가 지정되므로, 사용자는 서로의 메모리를 볼 수 없습니다. 사용자 안에서 세 번째 수준인 — 프로젝트 — 를 범위로 둘 수 있습니다:

  • 전역 항목(project_id = None) — 모든 컨텍스트에서 보입니다.
  • 프로젝트 항목(project_id = Some(id)) — 해당 프로젝트 내에서만 보입니다.
  • 프로젝트 검색은 전역 + 일치하는 프로젝트 항목을 반환합니다; 전역 검색은 전역 항목만 반환합니다.
use adk_memory::{MemoryServiceAdapter, InMemoryMemoryService};
use std::sync::Arc;

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

// store within a project
service.add_session_to_project("app", "user", "sess", "acme-project", entries).await?;

// an adapter scoped to that project
let adapter = MemoryServiceAdapter::new(service, "app", "user")
    .with_project_id("acme-project");

프로젝트를 사용해, 예를 들어 사용자의 업무용개인용 어시스턴트가 메모리를 공유하지 않도록 하면서도 정말로 전역적인 사실은 계속 공유할 수 있습니다.

모든 백엔드가 프로젝트 범위를 구현하는 것은 아닙니다

프로젝트 격리는 백엔드 기능이며, trait가 보장할 수 있는 것이 아닙니다. 의존하기 전에 확인하세요:

if service.supports_project_scoping() {
    service.add_entry_to_project("app", "user", "acme-project", entry).await?;
} else {
    // Decide explicitly: refuse, or store globally on purpose.
}
백엔드프로젝트 범위 지정
InMemoryMemoryService
SqliteMemoryService
PostgresMemoryService
RedisMemoryService
MongoMemoryService
Neo4jMemoryService
GraphMemoryService아니요 — 프로젝트 메서드가 오류를 반환합니다

프로젝트 지원이 없는 백엔드는 호출을 조용히 전역으로 쓰거나 삭제하는 대신 실패합니다. 쓰기 범위를 넓히면 한 프로젝트를 위해 의도된 데이터가 같은 앱과 사용자 아래의 모든 항목에 보이게 되고, 삭제 범위를 넓히면 지정된 프로젝트 밖의 항목이 제거됩니다. 두 경우 모두 호출자가 반환값만으로는 이를 감지할 수 없습니다. MemoryServiceAdapter::supports_project_scoping은 해당 백엔드가 지원하는 범위를 보고합니다.

GDPR 삭제

delete_user(app, user)는 모든 프로젝트에 걸쳐 사용자의 모든 memory(entries and embeddings)를 제거합니다 — right-to-erasure primitive입니다. 영구적으로 저장하는 backends는 이를 구현해야 하며, account-deletion 경로에서 호출하세요.

memory.delete_user("support", "alice").await?;

다음: Backends →