Speicher

Langfristiger semantischer Speicher für KI-Agenten mit adk-memory.

Übersicht

Das Speichersystem stellt eine persistente, durchsuchbare Speicherung für Agentenunterhaltungen bereit. Im Gegensatz zum Sitzungsstatus (der flüchtig ist) bleibt der Speicher über Sitzungen hinweg erhalten und ermöglicht es Agenten, relevanten Kontext aus vergangenen Interaktionen abzurufen.

Installation

[dependencies]
adk-memory = "2.1.0"

Grundkonzepte

MemoryEntry

Ein einzelner Speichereintrag mit Inhalt, Autor und Zeitstempel:

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 Trait

Das zentrale Trait für Speicher-Backends:

#[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

Abfrageparameter für die Speichersuche:

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

Einfache In-Memory-Implementierung für Entwicklung und Tests:

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

Speicherisolierung

Speicherinhalte werden isoliert nach:

  • app_name: Verschiedene Anwendungen verfügen über separate Speicherbereiche
  • user_id: Die Speicherinhalte jedes Benutzers sind privat
  • project_id (optional): Einträge können auf ein Projekt innerhalb eines Benutzers begrenzt werden
// 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
};

Projektspezifischer Speicher

Speicherinhalte können auf ein Projekt innerhalb eines Benutzers begrenzt werden. Der Isolationsschlüssel wird zu (app_name, user_id, project_id?):

  • Globale Einträge (project_id = None): in allen Projektkontexten und bei Suchen, die nur globale Einträge berücksichtigen, sichtbar.
  • Projekteinträge (project_id = Some(id)): nur bei der Suche innerhalb dieses bestimmten Projekts sichtbar.
  • Projektsuche (project_id = Some(id)): liefert globale Einträge plus Einträge für dieses Projekt.
  • Globale Suche (project_id = None): liefert nur globale Einträge.

Speichern projektspezifischer Einträge

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?;

Suchen mit Projektbereich

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?;

Löschen projektspezifischer Einträge

// 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 mit Projektbereich

MemoryServiceAdapter verbindet MemoryService mit adk_core::Memory. Verwenden Sie with_project_id(), um alle Vorgänge zu begrenzen:

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?;

Validierung der Projekt-ID

Projektbezeichner werden bei allen Schreibvorgängen validiert:

  • Darf nicht leer sein
  • Darf 256 Zeichen nicht überschreiten
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

Matrix der Suchsemantik

SearchRequest.project_idGibt globale Einträge zurückGibt Projekt-Einträge zurück
None✅ passende Abfrage❌ keine
Some("A")✅ passende Abfrage✅ nur Einträge des Projekts „A“, die der Abfrage entsprechen

Matrix zur Löschsemantik

VorgangGeltungsbereich
delete_entries (kein Projekt)Nur globale Einträge, die der Abfrage entsprechen
delete_entries_in_project("A")Nur Einträge des Projekts „A“, die der Abfrage entsprechen
delete_project("A")Alle Einträge für das Projekt „A“
delete_userAlle Einträge (global + alle Projekte)

Suchverhalten

InMemoryMemoryService verwendet eine wortbasierte Übereinstimmung:

  1. Die Abfrage wird in Wörter (Kleinschreibung) zerlegt
  2. Der Inhalt jeder Erinnerung wird zerlegt
  3. Erinnerungen mit beliebigen übereinstimmenden Wörtern werden zurückgegeben
// Query: "rust programming"
// Matches memories containing "rust" OR "programming"

Benutzerdefiniertes Speicher-Backend

Implementieren Sie MemoryService für benutzerdefinierten Speicher (z. B. eine Vektordatenbank):

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

Integration mit Agents

Der Speicher ist in LlmAgentBuilder integriert:

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()?;

Wenn der Speicher konfiguriert ist:

  1. Vor jedem Durchlauf werden relevante Erinnerungen gesucht
  2. Übereinstimmende Erinnerungen werden in den Kontext eingefügt
  3. Nach jeder Sitzung wird die Unterhaltung als Erinnerungen gespeichert

Architektur

┌─────────────────────────────────────────────────────────────┐
│                      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)                               │
└─────────────────────────────────────────────────────────────┘

Bewährte Vorgehensweisen

VorgehensweiseBeschreibung
Vektordatenbank in der Produktion verwendenInMemory ist nur für Entwicklungstests vorgesehen
Nach Benutzer begrenzenuser_id aus Datenschutzgründen immer einschließen
Ergebnisse begrenzenBegrenze die zurückgegebenen Erinnerungen, um einen Kontextüberlauf zu vermeiden
Alte Erinnerungen bereinigenImplementiere TTL oder archiviere veraltete Daten
Strategisch einbettenSpeichere Zusammenfassungen statt roher Konversationen

Vergleich mit Sessions

FunktionSitzungsstatusSpeicher
PersistenzLebensdauer der SitzungPermanent
GeltungsbereichEinzelne SitzungSitzungsübergreifend
SucheSchlüssel-Wert-SucheSemantische Suche
AnwendungsfallAktueller KontextLangzeitgedächtnis

Zurück: ← Guardrails | Weiter: Studio →

Speicher - ADK-Rust Dokumentation | ADK-Rust