Concepts de mémoire

La partie du magasin sémantique de la mémoire est construite à partir de quelques petits types. Apprenez ces quatre-là, et le reste de la section suit.

MemoryEntry

Un seul enregistrement de mémoire : un contenu, son auteur, et quand il a été créé.

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

Les entrées sont l’unité de stockage et l’unité de rappel — search renvoie les entrées les plus pertinentes pour une requête.

Le trait MemoryService

Chaque backend implémente un trait. Deux méthodes sont requises ; le reste a des implémentations par défaut qu’un backend peut remplacer :

#[async_trait]
pub trait MemoryService: Send + Sync {
    // Required
    async fn add_session(&self, app: &str, user: &str, session: &str,
                         entries: Vec<MemoryEntry>) -> Result<()>;
    async fn search(&self, req: SearchRequest) -> Result<SearchResponse>;

    // Optional (default: "not implemented")
    async fn delete_user(&self, app: &str, user: &str) -> Result<()>;          // GDPR
    async fn delete_session(&self, app: &str, user: &str, session: &str) -> Result<()>;
    async fn add_entry(&self, app: &str, user: &str, entry: MemoryEntry) -> Result<()>;
    async fn delete_entries(&self, app: &str, user: &str, query: &str) -> Result<u64>;
    // …plus a health check
}
  • add_session ingère la totalité des entrées d’une conversation terminée. Un Runner l’appelle pour vous lorsque la mémoire est attachée.
  • search est le rappel. Les backends interprètent la requête différemment — mots-clés, similarité d’embedding, ou notation de jetons de graphe — mais le contrat est le même.

SearchRequest / SearchResponse

use adk_memory::SearchRequest;

let req = SearchRequest {
    app_name: "support".into(),
    user_id:  "alice".into(),
    query:    "contact preference".into(),
    ..Default::default()      // top_k, project scoping, etc.
};

let resp = memory.search(req).await?;   // resp.entries: Vec<MemoryEntry>

État de session vs mémoire

Ce sont des outils différents — ne les confondez pas :

État de sessionMémoire
Durée de vieUne conversationÀ travers les conversations
APIadk-session (SessionService, carte d’état)adk-memory (MemoryService)
ContientLa transcription en direct + l'état temporaireDes faits durables dignes d'être rappelés plus tard
LectureToujours dans le contexteÀ la demande, via search_memory

Un flux typique : la conversation vit dans l’état de session ; lorsqu’elle se termine (ou à chaque tour), les éléments saillants sont écrits en mémoire ; la session suivante recherche la mémoire pour reconstituer le contexte. Voir Sessions & State.

Isolation : application, utilisateur et projet

La mémoire est toujours indexée par (app_name, user_id), donc les utilisateurs ne voient jamais les mémoires des autres. Vous pouvez définir un troisième niveau — projet — au sein d’un utilisateur :

  • Entrées globales (project_id = None) — visibles dans tous les contextes.
  • Entrées de projet (project_id = Some(id)) — visibles uniquement dans ce projet.
  • Recherche de projet renvoie les entrées globales + les entrées de projet correspondantes ; la recherche globale ne renvoie que les entrées globales.
use adk_memory::{MemoryServiceAdapter, InMemoryMemoryService};
use std::sync::Arc;

let service = Arc::new(InMemoryMemoryService::new());

// store within a project
service.add_session_to_project("app", "user", "sess", "acme-project", entries).await?;

// an adapter scoped to that project
let adapter = MemoryServiceAdapter::new(service, "app", "user")
    .with_project_id("acme-project");

Utilisez les projets pour éviter, par exemple, qu’un assistant professionnel et un assistant personnel d’un utilisateur partagent la mémoire, tout en partageant les faits réellement globaux.

Tous les backends n’implémentent pas la portée par projet

L’isolation par projet est une क्षमता du backend, pas quelque chose que le trait peut garantir. Vérifiez avant de vous y fier :

if service.supports_project_scoping() {
    service.add_entry_to_project("app", "user", "acme-project", entry).await?;
} else {
    // Decide explicitly: refuse, or store globally on purpose.
}
BackendDélimitation du projet
InMemoryMemoryServiceOui
SqliteMemoryServiceOui
PostgresMemoryServiceOui
RedisMemoryServiceOui
MongoMemoryServiceOui
Neo4jMemoryServiceOui
GraphMemoryServiceNon — les méthodes du projet renvoient une erreur

Un backend sans prise en charge des projets échoue l’appel plutôt que d’écrire ou de supprimer silencieusement à l’échelle globale. Étendre la portée d’une écriture rendrait des données prévues pour un projet visibles par tout ce qui se trouve sous la même application et le même utilisateur, et étendre une suppression retirerait des entrées en dehors du projet nommé — aucun de ces cas ne peut être détecté par l’appelant à partir de la valeur de retour. MemoryServiceAdapter::supports_project_scoping indique ce que son backend prend en charge.

Effacement RGPD

delete_user(app, user) supprime tous les souvenirs d’un utilisateur (entrées et embeddings) sur l’ensemble des projets — le primitive du droit à l’effacement. Les backends qui stockent de manière persistante l’implémentent ; appelez-le depuis votre chemin de suppression de compte.

memory.delete_user("support", "alice").await?;

Suivant : Backends →