Memória

Memória semântica de longo prazo para agentes de IA usando adk-memory.

Visão Geral

O sistema de memória fornece armazenamento persistente e pesquisável para conversas de agentes. Ao contrário do estado de sessão (que é efêmero), a memória persiste entre as sessões e permite que os agentes recuperem contexto relevante de interações passadas.

Instalação

[dependencies]
adk-memory = "2.0.0"

Conceitos Essenciais

MemoryEntry

Um único registro de memória com conteúdo, autor e carimbo de data/hora:

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

O trait principal para backends de memória:

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

Parâmetros de consulta para busca de memória:

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

Implementação simples em memória para desenvolvimento e teste:

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

Isolamento de Memória

As memórias são isoladas por:

  • app_name: Diferentes aplicações têm espaços de memória separados
  • user_id: As memórias de cada usuário são privadas
  • project_id (opcional): As entradas podem ser delimitadas a um projeto dentro de um usuário
// 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
};

Memória Delimitada por Projeto

As memórias podem ser delimitadas a um projeto dentro de um usuário. A chave de isolamento torna-se (app_name, user_id, project_id?):

  • Entradas globais (project_id = None): visíveis em todos os contextos de projeto e em buscas somente globais.
  • Entradas de projeto (project_id = Some(id)): visíveis apenas ao buscar dentro daquele projeto específico.
  • Busca de projeto (project_id = Some(id)): retorna entradas globais + entradas para aquele projeto.
  • Busca global (project_id = None): retorna apenas entradas globais.

Armazenando entradas delimitadas por projeto

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

Buscando com escopo de projeto

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

Exclusão delimitada por projeto

// 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 com escopo de projeto

A MemoryServiceAdapter faz a ponte entre MemoryService e adk_core::Memory. Use with_project_id() para definir o escopo de todas as operações:

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

Validação do ID do Projeto

Os identificadores de projeto são validados em todas as operações de escrita:

  • Não deve ser vazio
  • Não deve exceder 256 caracteres
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

Matriz de semântica de busca

SearchRequest.project_idRetorna Entradas GlobaisRetorna Entradas de Projeto
None✅ consulta correspondente❌ nenhum
Some("A")✅ consulta correspondente✅ apenas entradas do projeto "A" que correspondem à consulta

Matriz de semântica de exclusão

OperaçãoEscopo
delete_entries (sem projeto)Entradas globais correspondendo apenas à consulta
delete_entries_in_project("A")Entradas do Projeto "A" correspondentes apenas à consulta
delete_project("A")Todas as entradas para o projeto "A"
delete_userTodas as entradas (global + todos os projetos)

Comportamento de Busca

O InMemoryMemoryService usa correspondência baseada em palavras:

  1. A consulta é tokenizada em palavras (minúsculas)
  2. O conteúdo de cada memória é tokenizado
  3. Memórias com quaisquer palavras correspondentes são retornadas
// Query: "rust programming"
// Matches memories containing "rust" OR "programming"

Back-end de Memória Personalizado

Implemente MemoryService para armazenamento personalizado (por exemplo, banco de dados vetorial):

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

Integração com Agentes

A memória se integra com 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()?;

Quando a memória é configurada:

  1. Antes de cada turno, memórias relevantes são buscadas
  2. Memórias correspondentes são injetadas no contexto
  3. Após cada sessão, a conversa é armazenada como memórias

Arquitetura

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

Melhores Práticas

PráticaDescrição
Use banco de dados vetorial em produçãoInMemory é apenas para desenvolvimento/teste
Escopo por usuárioSempre inclua user_id para privacidade
Limitar resultadosLimitar as memórias retornadas para evitar estouro de contexto
Limpar memórias antigasImplementar TTL ou arquivamento para dados obsoletos
Incorpore estrategicamenteArmazene resumos, não conversas brutas

Comparação com Sessions

FuncionalidadeEstado da SessãoMemória
PersistênciaTempo de vida da sessãoPermanente
EscopoSessão únicaEntre sessões
PesquisaConsulta chave-valorPesquisa semântica
Caso de usoContexto atualMemória de longo prazo

Anterior: ← Guardrails | Próximo: Studio →