Mémoire

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

Présentation

Le système de mémoire fournit un stockage persistant et interrogeable pour les conversations des agents. Contrairement à l’état de session (qui est éphémère), la mémoire persiste entre les sessions et permet aux agents de retrouver le contexte pertinent des interactions passées.

Installation

[dependencies]
adk-memory = "2.1.0"

Concepts fondamentaux

MemoryEntry

Un seul enregistrement de mémoire avec son contenu, son auteur et son 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(),
};

MemoryService Trait

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 en 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 : les différentes applications disposent d’espaces mémoire distincts
  • user_id : les mémoires de chaque utilisateur sont privées
  • project_id (facultatif) : les entrées peuvent être limité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 limitée à un projet

Les mémoires peuvent être limité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 lors des recherches globales uniquement.
  • Entrées de projet (project_id = Some(id)) : visibles uniquement lors d’une recherche dans ce projet spécifique.
  • Recherche de projet (project_id = Some(id)) : renvoie les entrées globales ainsi que les entrées de ce projet.
  • Recherche globale (project_id = None) : renvoie uniquement les entrées globales.

Stockage d’entrées limitées à un 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 une portée 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 limitée à un 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 une portée de projet

Le MemoryServiceAdapter relie MemoryService à adk_core::Memory. Utilisez with_project_id() pour définir la portée de 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’identifiant du projet

Les identifiants de projet sont validés lors de toutes les opérations d’écriture :

  • Ne doivent pas être vides
  • Ne doivent 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 de la sémantique de recherche

SearchRequest.project_idRenvoie les entrées globalesRenvoie les entrées du 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 de la sémantique de suppression

OpérationPortée
delete_entries (sans projet)Entrées globales correspondant uniquement à la requête
delete_entries_in_project("A")Entrées du projet « A » correspondant uniquement à la requête
delete_project("A")Toutes les entrées du projet « A »
delete_userToutes les entrées (globales + tous les projets)

Comportement de recherche

Le InMemoryMemoryService utilise une mise en correspondance basée sur les mots :

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

Backend de mémoire personnalisé

Implémentez MemoryService pour utiliser 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 à 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 destiné uniquement au développement et aux tests
Définir la portée par utilisateurIncluez toujours user_id pour garantir la confidentialité
Limiter les résultatsLimiter les souvenirs renvoyés afin d’éviter le dépassement du contexte
Nettoyer les anciens souvenirsImplémenter TTL ou l’archivage des données obsolètes
Intégrer stratégiquementStocker des résumés, et non des conversations brutes

Comparaison avec les sessions

FonctionnalitéÉtat de la sessionMémoire
PersistanceDurée de vie de la sessionPermanente
PortéeSession uniqueInter-sessions
RechercheRecherche clé-valeurRecherche sémantique
Cas d’utilisationContexte actuelRappel à long terme

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

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