メモリ

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. 一致する単語を1つでも含むメモリを返します
// 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 またはアーカイブを実装する
戦略的に埋め込む生の会話ではなく、要約を保存する

セッションとの比較

機能セッション状態メモリ
永続性セッションの存続期間永続的
スコープ単一セッションセッション間
検索キー・バリュー検索セマンティック検索
用途現在のコンテキスト長期記憶

前へ: ← ガードレール | 次へ: Studio →