Speicher

Langzeit-Semantikspeicher für KI-Agenten unter Verwendung von adk-memory.

Übersicht

Das Speichersystem bietet persistenten, durchsuchbaren Speicher für Agentenkonversationen. Im Gegensatz zum Sitzungsstatus (der vergänglich ist) bleibt der Speicher über Sitzungen hinweg bestehen und ermöglicht es Agenten, relevante Kontexte aus früheren Interaktionen abzurufen.

Installation

[dependencies]
adk-memory = "2.0.0"

Kernkonzepte

MemoryEntry

Ein einzelner Speicherdatensatz 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 Kern-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(())
}

Speicherisolation

Speicher sind isoliert durch:

  • app_name: Verschiedene Anwendungen haben separate Speicherbereiche
  • user_id: Die Speicher jedes Benutzers sind privat
  • project_id (optional): Einträge können auf ein Projekt innerhalb eines Benutzers beschränkt 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
};

Projektbezogener Speicher

Speicher können auf ein Projekt innerhalb eines Benutzers beschränkt werden. Der Isolationsschlüssel wird (app_name, user_id, project_id?):

  • Globale Einträge (project_id = None): sichtbar in allen Projektkontexten und bei rein globalen Suchen.
  • Projekteinträge (project_id = Some(id)): nur sichtbar bei der Suche innerhalb dieses spezifischen Projekts.
  • Projektsuche (project_id = Some(id)): gibt globale Einträge + Einträge für dieses Projekt zurück.
  • Globale Suche (project_id = None): gibt nur globale Einträge zurück.

Speichern projektbezogener 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 Projektumfang

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

Projektbezogene Löschung

// 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 Projektumfang

Die MemoryServiceAdapter überbrückt MemoryService zu adk_core::Memory. Verwenden Sie with_project_id(), um alle Operationen 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

Projekt-Identifikatoren werden bei allen Schreiboperationen 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

Suchsemantik-Matrix

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

Löschsemantik-Matrix

OperationUmfang
delete_entries (kein Projekt)Globale Einträge, die nur der Abfrage entsprechen
delete_entries_in_project("A")Einträge von Projekt "A", die nur der Abfrage entsprechen
delete_project("A")Alle Einträge für Projekt "A"
delete_userAlle Einträge (global + alle Projekte)

Suchverhalten

Das InMemoryMemoryService verwendet wortbasierte Übereinstimmung:

  1. Abfrage wird in Wörter (Kleinbuchstaben) tokenisiert
  2. Der Inhalt jedes Speichers wird tokenisiert
  3. Speicher mit ü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. 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 integriert sich mit 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()?;

Wenn der Speicher konfiguriert ist:

  1. Vor jeder Runde werden relevante Speicher durchsucht
  2. Übereinstimmende Speicher werden in den Kontext eingefügt
  3. Nach jeder Sitzung wird die Konversation als Speicher abgelegt

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 Verfahren

PraxisBeschreibung
Vektor-DB in Produktion verwendenInMemory ist nur für Entwicklung/Tests
Umfang nach BenutzerFügen Sie user_id immer aus Datenschutzgründen hinzu
Ergebnisse begrenzenZurückgegebene Erinnerungen begrenzen, um Kontextüberlauf zu vermeiden
Alte Erinnerungen bereinigenImplementierung von TTL oder Archivierung für veraltete Daten
Strategisch einbettenZusammenfassungen speichern, nicht Rohkonversationen

Vergleich mit Sessions

MerkmalSitzungsstatusSpeicher
PersistenzSitzungslebensdauerPermanent
UmfangEinzelne SitzungSitzungsübergreifend
SucheSchlüssel-Wert-SucheSemantische Suche
AnwendungsfallAktueller KontextLangzeitgedächtnis

Zurück: ← Guardrails | Weiter: Studio →

Speicher - ADK-Rust Dokumentation | ADK-Rust