메모리 개념
메모리의 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
이것들은 서로 다른 도구입니다 — 혼동하지 마세요:
| 세션 상태 | 메모리 | |
|---|---|---|
| 수명 | 하나의 대화 | 대화 전반 |
| API | adk-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 →