Mémoire

Mémoire sémantique à long terme pour les agents IA utilisant adk-memory.

Aperçu

Le système de mémoire offre un stockage persistant et consultable pour les conversations d'agents. Contrairement à l'état de Session (qui est éphémère), la mémoire persiste à travers les Sessions et permet aux agents de se rappeler le contexte pertinent des interactions passées.

Installation

[dependencies]
adk-memory = "2.0.0"

Concepts fondamentaux

MemoryEntry

Un enregistrement de mémoire unique avec du contenu, un auteur et un horodatage :

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

Trait MemoryService

Le trait principal pour les backends de mémoire :

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

Paramètres de requête pour la recherche de mémoire :

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

Implémentation simple en mémoire pour le développement et les 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(())
}

Isolation de la mémoire

Les mémoires sont isolées par :

  • app_name : Différentes applications ont des espaces de mémoire distincts
  • user_id : Les mémoires de chaque utilisateur sont privées
  • project_id (facultatif) : Les entrées peuvent être scopeées à un projet au sein d'un utilisateur
// 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
};

Mémoire scopeée au projet

Les mémoires peuvent être scopeées à un projet au sein d'un utilisateur. La clé d'isolation devient (app_name, user_id, project_id?) :

  • Entrées globales (project_id = None) : visibles dans tous les contextes de projet et dans les recherches uniquement globales.
  • Entrées de projet (project_id = Some(id)) : visibles uniquement lors de la recherche dans ce projet spécifique.
  • Recherche de projet (project_id = Some(id)) : renvoie les entrées globales + les entrées pour ce projet.
  • Recherche globale (project_id = None) : renvoie uniquement les entrées globales.

Stockage des entrées scopeées au projet

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

Recherche avec un scope de projet

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

Suppression scopeée au projet

// 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 avec un scope de projet

Le MemoryServiceAdapter fait le pont entre MemoryService et adk_core::Memory. Utilisez with_project_id() pour délimiter toutes les opérations :

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

Validation de l'ID de projet

Les identifiants de projet sont validés sur toutes les opérations d'écriture :

  • Ne doit pas être vide
  • Ne doit pas dépasser 256 caractères
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

Matrice des sémantiques de recherche

SearchRequest.project_idRetourne les entrées globalesRetourne les entrées de projet
None✅ correspondant à la requête❌ aucune
Some("A")✅ correspondant à la requête✅ uniquement les entrées du projet "A" correspondant à la requête

Matrice des sémantiques de suppression

OpérationPortée
delete_entries (aucun projet)Uniquement les entrées globales correspondant à la requête
delete_entries_in_project("A")Uniquement les entrées du projet "A" correspondant à la requête
delete_project("A")Toutes les entrées pour le projet "A"
delete_userToutes les entrées (globales + tous les projets)

Comportement de recherche

Le InMemoryMemoryService utilise la correspondance basée sur les mots :

  1. La requête est tokenisée en mots (minuscules)
  2. Le contenu de chaque mémoire est tokenisé
  3. Les mémoires avec des mots correspondants sont retournées
// Query: "rust programming"
// Matches memories containing "rust" OR "programming"

Backend de mémoire personnalisé

Implémentez MemoryService pour un stockage personnalisé (par exemple, une base de données vectorielle) :

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

Intégration avec les Agents

La mémoire s'intègre avec 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()?;

Lorsque la mémoire est configurée :

  1. Avant chaque tour, les mémoires pertinentes sont recherchées
  2. Les mémoires correspondantes sont injectées dans le contexte
  3. Après chaque session, la conversation est stockée sous forme de mémoires

Architecture

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

Bonnes Pratiques

PratiqueDescription
Utiliser une base de données vectorielle en productionInMemory est uniquement pour le développement/test
Définir la portée par utilisateurToujours inclure l'id utilisateur pour la confidentialité
Limiter les résultatsPlafonner les souvenirs retournés pour éviter le débordement de contexte
Nettoyer les anciens souvenirsImplémenter une durée de vie (TTL) ou un archivage pour les données obsolètes
Intégrer stratégiquementStocker des résumés, pas des conversations brutes

Comparaison avec les Sessions

CaractéristiqueÉtat de SessionMemory
PersistanceDurée de vie de la SessionPermanent
PortéeSession uniqueInter-session
RechercheRecherche par clé-valeurRecherche sémantique
Cas d'utilisationContexte actuelRappel à long terme

Précédent: ← Garde-fous | Suivant: Studio →

Mémoire - Documentation ADK-Rust | ADK-Rust