Files
OmniRoute/docs/i18n/it/docs/frameworks/MEMORY.md
Diego Rodrigues de Sa e Souza 8feea123bb feat(docs): mirror every docs/ page in all 65 locales (#14106)
* feat(docs): mirror every docs/ page in all 65 locales

Extends the documentation mirrors from the 22-page core set (#13940) to
every Markdown page under docs/: 152 sources x 65 locales = 9,880 mirrors
(6,208 new), language bars rewritten for the full locale list, state
adopted so the blocking drift gate now covers all 152 pages.

run-translation.mjs: an oversized block made only of table rows or list
items (PROVIDER_REFERENCE.md 244-row table, FREE_TIERS.md 71-item list) is
cut at item boundaries and rejoined without a blank line — the single
16-40 KB request outlived the backend socket for verbose scripts. 48
older mirrors whose tables had lost rows were retranslated with --force.

* docs(i18n): refresh mirrors for the sources the base changed since the branch cut

Section-level retranslation of the 29 docs (and README.md) whose source
or mirrors moved on release/v3.8.51 during the run, then state adoption;
the drift gate is green again on the merged tree.
2026-09-18 13:16:46 -03:00

74 KiB
Raw Blame History

Memory System (Italiano)

🌐 Languages: 🇺🇸 English · 🇪🇹 am · 🇸🇦 ar · 🇦🇿 az · 🇧🇬 bg · 🇧🇩 bn · 🇨🇿 cs · 🇩🇰 da · 🇩🇪 de · 🇬🇷 el · 🇪🇸 es · 🇪🇪 et · 🇮🇷 fa · 🇫🇮 fi · 🇫🇷 fr · 🇮🇪 ga · 🇮🇳 gu · 🇳🇬 ha · 🇮🇱 he · 🇮🇳 hi · 🇭🇷 hr · 🇭🇺 hu · 🇦🇲 hy · 🇮🇩 id · 🇳🇬 ig · 🇯🇵 ja · 🇬🇪 ka · 🇰🇭 km · 🇮🇳 kn · 🇰🇷 ko · 🇱🇹 lt · 🇱🇻 lv · 🇮🇳 ml · 🇮🇳 mr · 🇲🇾 ms · 🇲🇹 mt · 🇲🇲 my · 🇳🇵 ne · 🇳🇱 nl · 🇳🇴 no · 🇮🇳 or · 🇮🇳 pa · 🇵🇭 phi · 🇵🇱 pl · 🇵🇹 pt · 🇧🇷 pt-BR · 🇷🇴 ro · 🇷🇺 ru · 🇱🇰 si · 🇸🇰 sk · 🇸🇮 sl · 🇷🇸 sr · 🇸🇪 sv · 🇰🇪 sw · 🇮🇳 ta · 🇮🇳 te · 🇹🇭 th · 🇹🇷 tr · 🇺🇦 uk-UA · 🇵🇰 ur · 🇺🇿 uz · 🇻🇳 vi · 🇳🇬 yo · 🇨🇳 zh-CN · 🇹🇼 zh-TW


Fonte autorevole: src/lib/memory/ e src/app/api/memory/ Ultimo aggiornamento: 2026-06-28 — v3.8.40 (disattivata per impostazione predefinita + aggiornamento della quantizzazione int8)

OmniRoute fornisce una memoria conversazionale persistente associata alla chiave API (e facoltativamente all'ID di sessione). I ricordi vengono estratti automaticamente dalle risposte dell'LLM tramite un leggero sistema di corrispondenza basato su espressioni regolari e reinseriti nelle richieste successive come messaggio di sistema iniziale (o come primo messaggio utente per i provider che rifiutano il ruolo di sistema).

La memoria è DISATTIVATA per impostazione predefinita (v3.8.30+). DEFAULT_MEMORY_SETTINGS.enabled ora è false (src/lib/memory/settings.ts). L'abilitazione della memoria inserisce fino a maxTokens (~2k) di contesto recuperato in ogni richiesta di chat, con conseguenti costi — una spesa inaspettata per le nuove installazioni e per i client che gestiscono autonomamente il proprio contesto. Attivala esplicitamente in Impostazioni → Memoria (la scheda MemorySkillsTab mostra un avviso relativo al costo in token quando la memoria è abilitata). Un client può escludere dalla memoria una singola richiesta tramite l'header di richiesta x-omniroute-no-memory (true/1/yes) — consulta la tabella degli header di richiesta in API_REFERENCE.md. Una richiesta senza memoria imposta memoryOwnerId = null, disabilitando sia l'inserimento della memoria sia quello delle skill per tale richiesta (open-sse/handlers/chatCore/headers.ts::isNoMemoryRequested).

La memoria è circoscritta a ciascuna chiave API, non a ciascun utente: ogni richiesta autenticata con la stessa chiave API condivide lo stesso pool di memoria, con un'ulteriore segmentazione facoltativa tramite sessionId.

Architettura

Client → /v1/chat/completions (apiKeyInfo risolto a monte)
  → handleChatCore() [open-sse/handlers/chatCore.ts]
    → resolveMemoryOwnerId(apiKeyInfo)        # estrae l'ID
    → getMemorySettings()                     # impostazioni memorizzate nella cache
    → shouldInjectMemory(body, {enabled})     # controllo di accesso
    → retrieveMemories(apiKeyId, config)      # SQL + FTS5 + vettore facoltativo
    → injectMemory(body, memories, provider)  # messaggio di sistema o utente
  → chiamata al provider a monte
  → alla risposta: extractFacts(text, apiKeyId, sessionId)  # non bloccante
    → setImmediate → createMemory(fact) per ogni corrispondenza
                   → embed(content) + upsertVector(id, vec)

I punti di chiamata per l'inserimento e l'estrazione sono configurati in open-sse/handlers/chatCore.ts (cerca retrieveMemories, injectMemory ed extractFacts).

Architettura del motore (risoluzione a 3 livelli)

Il motore della memoria determina il percorso di recupero in fase di esecuzione in base all'infrastruttura disponibile e alle impostazioni. Esistono tre livelli, applicati in ordine di priorità:

  ┌─────────────────────────────────────────────────────────────┐
  │  LIVELLO 0 — Parole chiave (FTS5)                            │
  │  Disponibilità determinata tramite verifica: FTS5 quando     │
  │  la build di SQLite lo supporta (better-sqlite3 /             │
  │  node:sqlite / bun:sqlite); non disponibile nelle build      │
  │  prive di FTS5 (ad es. sql.js/WASM —                         │
  │  "no such module: fts5"). Utilizzato quando strategy =       │
  │  "exact" o come ripiego; il campo keyword dello stato del    │
  │  motore riflette il risultato della verifica.                │
  └──────────────────────────────────┬──────────────────────────┘
                                     │ strategy = semantic|hybrid?
                                     ▼
  ┌─────────────────────────────────────────────────────────────┐
  │  LIVELLO 1 — Vettore integrato (sqlite-vec)                  │
  │  sqlite-vec v0.1.9 caricato tramite db.loadExtension().      │
  │  KNN a forza bruta su vettori Float32. Attivo quando:        │
  │   • il caricamento di sqlite-vec tramite loadExtension riesce│
  │   • è disponibile una sorgente di embedding (remote |        │
  │     static | transformers) in grado di produrre un           │
  │     Float32Array                                              │
  │   • la tabella vec_memories esiste (creata alla prima        │
  │     esecuzione di ready())                                   │
  └──────────────────────────────────┬──────────────────────────┘
                                     │ qdrant.enabled?
                                     ▼
  ┌─────────────────────────────────────────────────────────────┐
  │  LIVELLO 2 — Qdrant (database vettoriale esterno opzionale)  │
  │  Quando abilitato, sostituisce sqlite-vec per la ricerca     │
  │  semantic/hybrid. Richiede un'istanza Qdrant in esecuzione   │
  │  e host/porta configurati.                                   │
  └─────────────────────────────────────────────────────────────┘

La degradazione è automatica e trasparente:

  • Se il caricamento di sqlite-vec non riesce, il livello 1 non è disponibile → viene utilizzato il livello 0.
  • Se la sorgente di embedding restituisce un errore, il livello 1 utilizza il livello 0 come ripiego.
  • Se Qdrant non è operativo, il livello 2 utilizza il livello 1 come ripiego (oppure il livello 0 se anche il livello 1 non è disponibile).

Fonti degli embedding

Il livello di embedding (src/lib/memory/embedding/) determina quale fonte utilizzare in base a MemorySettingsExtended.embeddingSource:

Fonte Descrizione Chiave richiesta Avvio a freddo
remote Utilizza l'API di embedding di un provider configurato (OpenAI, Cohere, ecc.) Nessuno
static Embedding locale tramite tabella di ricerca con potion-base-8M (WordPiece + mean pooling) No ~200ms
transformers Inferenza ONNX locale tramite @huggingface/transformers v4, all-MiniLM-L6-v2 No ~3s + ~400MB di RAM
auto Risoluzione in fase di esecuzione: remote (se esiste una chiave) → static → transformers → null Dipende Dipende

Ordine di risoluzione per auto:

  1. Trova il primo provider in listEmbeddingProviders() con hasKey === trueremote.
  2. Se settings.staticEnabled === truestatic.
  3. Se settings.transformersEnabled === truetransformers.
  4. Altrimenti → null (passa alla ricerca per parole chiave FTS5).

La cache degli embedding (src/lib/memory/embedding/cache.ts) utilizza una mappa LRU in memoria indicizzata da ${source}:${model}:${dim}:${sha256(text)}, limitata a MEMORY_EMBEDDING_CACHE_MAX voci (valore predefinito: 1000) con un TTL di MEMORY_EMBEDDING_CACHE_TTL_MS (valore predefinito: 5 min). È condivisa tra tutti i chiamanti per l'intero ciclo di vita del processo.

RRF ibrido (k=60)

Quando strategy = "hybrid" e l'archivio vettoriale è disponibile, il recupero utilizza Reciprocal Rank Fusion per unire i risultati FTS5 e vettoriali:

RRF(d) = Σ  1 / (k + rank_i(d))      dove k = 60 (configurabile tramite MEMORY_RRF_K)
          i

In concreto:

  1. Esegue la ricerca FTS5 → elenco ordinato R_fts (posizione 1..N).
  2. Esegue la ricerca vettoriale KNN → elenco ordinato R_vec (posizione 1..M).
  3. Per ogni memoryId univoco:
    rrf_score = 1/(60 + fts_rank) + 1/(60 + vec_rank) (0 se non è presente nell'elenco).
  4. Ordina per rrf_score in ordine DESC e applica la scansione del budget di token.

RRF è noto per essere efficace senza richiedere la normalizzazione dei punteggi tra sistemi di recupero eterogenei. Il valore predefinito k=60 proviene dall'articolo originale di Cormack et al. e funziona bene per corpus di piccole dimensioni (<10.000 memorie).

Backfill (differito + reindicizzazione)

Quando il modello di embedding cambia (rilevato tramite embedding_signature), l'archivio vettoriale viene ricostruito e tutte le memorie esistenti vengono contrassegnate con needs_reindex = 1 nella tabella memories.

Backfill differito: Al recupero successivo, qualsiasi memoria priva di una voce vettoriale viene convertita in embedding e inserita in vec_memories prima dell'esecuzione della ricerca. Questo ammortizza il costo del backfill sulle richieste reali senza bloccare l'avvio.

Reindicizzazione esplicita: La scheda Engine in /dashboard/memory fornisce un pulsante "Reindicizza ora" che chiama POST /api/memory/reindex. Il gestore chiama runReindexBatch() da src/lib/memory/reindex.ts, che elabora fino a limit voci in sospeso per richiesta. L'avanzamento può essere verificato tramite GET /api/memory/engine-status (vectorStore.needsReindex).

La tabella memory_vec_meta (migrazione 083_memory_vec.sql) memorizza:

  • active_dim — dimensione vettoriale corrente (null = non ancora calibrata).
  • embedding_signature${source}:${model}:${dim} utilizzata per rilevare le modifiche.
  • last_reset_at — timestamp dell'ultimo ripristino completo.
  • vec_loaded — flag 0/1 che indica se sqlite-vec è stato caricato correttamente.

Estensione delle impostazioni

Nove campi relativi agli embedding e ai vettori sono disponibili in MemorySettingsExtended in src/shared/schemas/memory.ts e vengono resi persistenti tramite src/lib/db/settings.ts:

Campo Tipo Valore predefinito Descrizione
embeddingSource "remote" | "static" | "transformers" | "auto" "auto" Sorgente di embedding da utilizzare
embeddingProviderModel string | null null Provider/modello nel formato provider/model
customBaseUrl string | null null URL di base dell'endpoint compatibile con OpenAI riservato a Memory
customModelId string | null null ID del modello inviato all'endpoint personalizzato
transformersEnabled boolean false Abilitazione esplicita di Transformers.js (MiniLM, ~400MB)
staticEnabled boolean false Abilitazione esplicita del modello locale statico potion-base-8M
rerankEnabled boolean false Abilita la fase di riordinamento (aggiunge +200-500ms/richiesta)
rerankProviderModel string | null null Provider/modello di riordinamento nel formato provider/model
vectorStore "sqlite-vec" | "qdrant" | "auto" "auto" Backend vettoriale da utilizzare

Questi campi sono esposti tramite GET /PUT /api/settings/memory (schema MemorySettingsExtendedSchema).

Per la sorgente remote, Memory accetta anche le impostazioni facoltative customBaseUrl e customModelId. Insieme selezionano un endpoint /embeddings compatibile con OpenAI e un modello senza modificare il registro globale degli embedding. L'endpoint viene normalizzato prima dell'uso e verificato in base alla politica del provider per gli URL in uscita: è richiesto HTTP(S), le credenziali incorporate e le stringhe di query vengono rifiutate e gli indirizzi dei metadati cloud rimangono bloccati. I valori vuoti mantengono il provider selezionato nel registro. Gli errori restituiti alla dashboard vengono sanificati e le credenziali dell'endpoint non vengono mai registrate nei log.

TODO (D20): L'ambito global (condivisione dei ricordi tra tutte le chiavi API) non è implementato in questa versione. Richiede modifiche allo schema e un percorso di recupero globale. Da gestire separatamente.

Livelli di archiviazione

Primario: SQLite (tabella memories)

Creata dalla migrazione 015_create_memories.sql:

Colonna Tipo Note
id TEXT PRIMARY KEY UUID generato tramite crypto.randomUUID()
api_key_id TEXT NOT NULL Chiave API proprietaria
session_id TEXT Ambito facoltativo per conversazione
type TEXT NOT NULL Uno tra factual, episodic, procedural, semantic
key TEXT Chiave di upsert stabile, ad es. preference:i_prefer_python
content TEXT NOT NULL Il testo effettivo del fatto
metadata TEXT Blob JSON (categoria, extractedAt, sorgente, ...)
created_at / updated_at TEXT Stringhe ISO 8601
expires_at TEXT Scadenza facoltativa; NULL indica permanente
memory_id INTEGER UNIQUE Aggiunto da 023_fix_memory_fts_uuid.sql per collegare gli UUID ↔ i rowid di FTS5

Indici: api_key_id, session_id, type, expires_at, oltre all'indice univoco memory_id.

Semantica di upsert: createMemory() cerca una riga esistente con la stessa combinazione (api_key_id, key) e, se la trova, la aggiorna sul posto (unendo metadata tramite uno spread superficiale). Ciò impedisce che la tabella cresca senza limiti in presenza di dichiarazioni di preferenza ripetute.

Ricerca full-text (tabella virtuale memory_fts)

022_add_memory_fts5.sql crea una tabella virtuale FTS5 su content e key. 023_fix_memory_fts_uuid.sql corregge un bug riscontrato nell'uso reale, per cui la chiave primaria UUID non poteva essere unita al rowid intero di FTS5: la migrazione aggiunge la colonna memory_id, ricrea la tabella FTS e configura i trigger (memory_fts_ai, memory_fts_ad, memory_fts_au) che mantengono FTS sincronizzato in caso di INSERT, DELETE e UPDATE.

Utilizzata da retrieval.ts per le strategie semantic e hybrid (vedere sotto). Il codice di recupero esegue un controllo con hasTable("memory_fts") e ripiega sull'ordine cronologico se la tabella FTS non è presente o la query FTS genera un errore.

Facoltativo: Qdrant (archivio vettoriale di livello 2)

src/lib/memory/qdrant.ts implementa un'integrazione facoltativa con Qdrant come archivio vettoriale di livello 2. Il recupero viene indirizzato a Qdrant solo quando il selettore del motore memoryVectorStore === "qdrant"; il valore predefinito "auto" (e "sqlite-vec") non seleziona mai Qdrant. L'interruttore nella scheda Engine imposta sia qdrantEnabled sia memoryVectorStore: l'abilitazione rende Qdrant l'archivio principale, mentre la disabilitazione ripristina "auto" (#5597 — prima di questa correzione, l'abilitazione non aveva effetto perché nulla scriveva nel selettore del motore). Se Qdrant non è raggiungibile o non restituisce alcun risultato, il recupero ripiega su sqlite-vec → FTS5.

  • upsertSemanticMemoryPoint() — incorpora key + content con il modello di embedding configurato, verifica che la raccolta esista (creando vettori con distanza del coseno al primo utilizzo) ed esegue l'upsert di un punto con payload {memoryId, apiKeyId, sessionId, key, content, metadata, createdAtUnix, expiresAtUnix}.
  • searchSemanticMemory(query, topK, scope) — incorpora la query, esegue la ricerca nella raccolta filtrando per kind = "omniroute_memory" e, facoltativamente, per apiKeyId / sessionId. Limita topK all'intervallo [1, 20].
  • deleteSemanticMemoryPoint(id) — elimina un singolo punto. Viene chiamata da deleteMemory() dopo la rimozione della riga SQLite (D15).
  • cleanupSemanticMemoryPoints({retentionDays}) — elimina in blocco i punti il cui expiresAtUnix è nel passato o il cui createdAtUnix è precedente alla soglia di conservazione. Prima li conta, in modo che la dashboard possa mostrare i numeri effettivi.
  • checkQdrantHealth() — controllo di integrità GET /readyz con latenza.

L'interfaccia utente delle impostazioni espone la configurazione di Qdrant, il controllo di integrità, il test della ricerca semantica e la pulizia nella scheda Engine di /dashboard/memory. Le route corrispondenti in src/app/api/settings/qdrant/ sono tutte collegate a partire dalla v3.8.6:

Route Metodo Descrizione
/api/settings/qdrant GET / PUT Legge / aggiorna le impostazioni Qdrant
/api/settings/qdrant/health GET Controllo di operatività + latenza
/api/settings/qdrant/search POST Test della ricerca semantica
/api/settings/qdrant/cleanup POST Rimuove i punti scaduti / obsoleti
/api/settings/qdrant/embedding-models GET Elenca i modelli di embedding disponibili

Note sul comportamento (cosa aspettarsi):

  • Selezione del motore — l'abilitazione di Qdrant nella scheda Engine lo rende l'archivio principale (imposta memoryVectorStore="qdrant"); la disabilitazione ripristina "auto" (#5597).
  • Nessun popolamento retroattivo — solo le memorie create/aggiornate dopo l'abilitazione di Qdrant vengono scritte al suo interno (doppia scrittura fire-and-forget). Le memorie SQLite preesistenti non vengono migrate; "Reindex Now" ricostruisce solo l'indice sqlite-vec, non Qdrant.
  • La dimensione dei vettori viene rilevata automaticamente dall'embedding effettivo al primo utilizzo: non è presente alcun campo per la dimensione da compilare. La modifica del modello di embedding dopo la creazione di una raccolta non viene gestita automaticamente: la raccolta esistente rimane invariata, le scritture/ricerche con dimensioni non corrispondenti non riescono e ricorrono a sqlite-vec. Per cambiare modello di embedding, ricrea la raccolta (con un nuovo nome oppure eliminandola in Qdrant).
  • Metrica di distanza — sempre Cosine (impostata direttamente nel codice alla creazione della raccolta; non configurabile).
  • Autenticazione — solo chiave API (inviata come header api-key; facoltativa per Docker locale senza autenticazione). JWT/RBAC non vengono utilizzati.
  • Campi di configurazione — l'interfaccia utente espone host, port, collection, embeddingModel, apiKey. vectorSize / hnswEfConstruct sono disponibili solo tramite ambiente/DB e vectorSize non viene utilizzato per la creazione della raccolta (la dimensione deriva dall'embedding).

Quantizzazione dei vettori (int8 — facoltativa, entrambi i backend)

Entrambi i backend vettoriali supportano la quantizzazione int8 facoltativa per ridurre l'occupazione in memoria dei vettori archiviati (~4 volte inferiore rispetto a Float32), al costo di una piccola riduzione del richiamo. Per impostazione predefinita è disattivata su entrambi: i vettori rimangono a precisione piena, a meno che non venga esplicitamente abilitata.

Backend Impostazione Tipo Predefinito Punto di lettura
Qdrant qdrantQuantization (chiave DB) "none" | "int8" | "binary" "none" src/lib/memory/qdrant.ts::normalizeQdrantConfig()
sqlite-vec MEMORY_VEC_QUANTIZATION (env) "none" | "int8" "none" src/lib/memory/vectorStore.ts::requestedVecQuantization()
  • Qdrant viene configurato per ciascuna istanza tramite la chiave di impostazione qdrantQuantization (esposta come campo quantization in PUT /api/settings/qdrant). Quando è impostata su "int8", buildQuantizationConfig() richiede la quantizzazione scalare (always_ram, quantile 0.99) e le ricerche abilitano rescore: true, in modo che i vettori a precisione piena perfezionino l'insieme di candidati int8.
  • La quantizzazione di sqlite-vec è disponibile solo tramite ambiente (non è un'impostazione DB): imposta MEMORY_VEC_QUANTIZATION=int8 per archiviare i vettori locali come colonna int8[dim] tramite vec_quantize_int8(?, 'unit'). La modalità scelta viene inclusa in embedding_signature (un suffisso :int8), pertanto il passaggio da una modalità all'altra attiva una reindicizzazione completa della tabella vec_memories, usando lo stesso percorso di popolamento differito utilizzato quando cambia il modello di embedding.

Tipi di memoria

MemoryType (src/lib/memory/types.ts):

Tipo Utilizzato per
factual Preferenze, fatti stabili sull'utente, modelli comportamentali
episodic Decisioni legate a un momento specifico ("Ho scelto Postgres")
procedural Memoria di flussi di lavoro / procedure (riservata; attualmente senza estrattore automatico)
semantic Riservata alle voci del vector store

La strategia di recupero di MemoryConfig è una tra exact, semantic o hybrid, mentre l'ambito è uno tra session, apiKey o global. L'ambito predefinito di getMemorySettings() è apiKey.

Estrazione dei fatti (extraction.ts)

L'estrazione è basata su espressioni regolari, non su LLM: viene eseguita nello stesso processo con setImmediate(), quindi non blocca mai il flusso della risposta:

  • Pattern di preferenzaMemoryType.FACTUAL (ad es. Preferisco …, Mi piace molto …, il mio preferito è …, Odio …)
  • Pattern decisionaliMemoryType.EPISODIC (ad es. Userò …, Ho scelto …, Ho optato per …, Adotterò …)
  • Pattern comportamentaliMemoryType.FACTUAL (ad es. Di solito …, Faccio sempre …, Tendo a …)

Ogni corrispondenza viene sanificata (trim, compressione degli spazi vuoti, limite di 500 caratteri), deduplicata all'interno del batch tramite una chiave stabile factKey(category, content) e archiviata tramite createMemory() con i metadati {category, extractedAt, source: "llm_response"}. Il testo di input è limitato a 64 KiB (MAX_EXTRACTION_TEXT_LENGTH): quando è più lungo, viene usata la parte finale del testo, in modo che il contenuto più recente dell'assistente venga sempre considerato.

extractFactsFromText(text) viene esportata per i test e restituisce i fatti strutturati senza archiviarli.

Recupero (retrieval.ts)

retrieveMemories(apiKeyId, config) è il punto di ingresso principale. La funzione:

  1. Normalizza e convalida la configurazione tramite MemoryConfigSchema.
  2. Restituisce immediatamente [] quando enabled è false o maxTokens <= 0.
  3. Limita maxTokens all'intervallo [1, 8000].
  4. Rileva se esiste la tabella moderna memories (in contrapposizione alla tabella legacy memory), affinché i database meno recenti continuino a funzionare.
  5. Costruisce la query di base con una condizione di scadenza (expires_at IS NULL OR datetime(expires_at) > datetime('now')), un ambito di sessione facoltativo e un limite temporale facoltativo basato su retentionDays.
  6. Si ramifica in base alla strategia:
    • exact (predefinita): ordinamento cronologico ORDER BY created_at DESC LIMIT 100.
    • semantic: se config.query è impostato ed esiste memory_fts, esegue la JOIN memory_fts MATCH ? e ordina per rango FTS; ripiega sull'ordinamento cronologico quando FTS restituisce 0 righe.
    • hybrid: unione dei risultati FTS (con rilevanza maggiore) e dell'insieme cronologico, con deduplicazione per id.
  7. Calcola un punteggio di rilevanza basato su parole chiave (getRelevanceScore) su content, key e sul JSON metadata quando viene fornita una query. Le righe con punteggio zero vengono escluse.
  8. Ordina prima per punteggio decrescente, quindi per createdAt decrescente.
  9. Scorre l'elenco ordinato e accetta le voci finché il conteggio progressivo di estimateTokens(content) (≈ length / 4) rimane entro il budget. Restituisce sempre almeno una voce quando esiste una corrispondenza.

estimateTokens viene esportata ed è utilizzata dal recupero, dalla riepilogazione e dallo strumento MCP omniroute_memory_search.

Iniezione (injection.ts)

injectMemory(request, memories, provider):

  1. Unisce tutti i contenuti delle memorie in un'unica stringa Memory context: ….
  2. Seleziona una strategia in base al nome del provider:
    • Messaggio di sistema (impostazione predefinita per OpenAI, Anthropic, Gemini, …) — antepone un elemento {role: "system", content: memoryText} a tutti i messaggi di sistema esistenti, in modo che i prompt di sistema dell'utente mantengano la precedenza.
    • Messaggio utente (fallback) — per i provider inclusi in PROVIDERS_WITHOUT_SYSTEM_MESSAGE: o1, o1-mini, o1-preview, glm, glmt, glm-cn, zai, qianfan. Questi rifiutano il ruolo di sistema e altrimenti restituirebbero un errore 400 (cfr. issue #1701 per GLM/Zhipu).
  3. Registra il conteggio, la strategia e il modello in memory.injection.injected.

providerSupportsSystemMessage(provider) viene esportata per i chiamanti che devono prendere autonomamente decisioni di instradamento. Per sicurezza, i provider sconosciuti hanno come valore predefinito true (ruolo di sistema consentito).

Impostazioni (settings.ts)

La configurazione della memoria è archiviata nella tabella delle impostazioni del DB, non nelle variabili di ambiente. getMemorySettings() legge da getSettings() e memorizza il risultato nella cache del processo; invalidateMemorySettingsCache() viene chiamata dalla route PUT delle impostazioni dopo le scritture.

Campi legacy (tutte le versioni)

Chiave DB Tipo Valore predefinito Controllo UI
memoryEnabled boolean false (disattivata per impostazione predefinita dalla v3.8.30) Attivazione/disattivazione della memoria
memoryMaxTokens integer 2000 (intervallo 016000) Budget di token per l'iniezione
memoryRetentionDays integer 30 (intervallo 1365) Finestra di conservazione
memoryStrategy enum "hybrid" (uno tra recent, semantic, hybrid) Strategia di recupero
skillsEnabled boolean false Attiva/disattiva l'iniezione delle competenze per chiave (vedere SKILLS.md)

Nota: la strategia UI "recent" viene mappata sulla strategia interna di recupero "exact" tramite toMemoryRetrievalConfig() (ordine cronologico).

Nuovi campi (v3.8.6, piano 21 D9)

Vedere anche la sezione "Estensione delle impostazioni" sopra per le descrizioni dei campi.

Chiave DB Campo API Valore predefinito
memoryEmbeddingSource embeddingSource "auto"
memoryEmbeddingModel embeddingProviderModel null
memoryTransformersEnabled transformersEnabled false
memoryStaticEnabled staticEnabled false
memoryRerankEnabled rerankEnabled false
memoryRerankModel rerankProviderModel null
memoryVectorStore vectorStore "auto"

Le chiavi DB relative a Qdrant (qdrantEnabled, qdrantHost, qdrantPort, qdrantApiKey, qdrantCollection con valore predefinito "omniroute_memory", qdrantEmbeddingModel con valore predefinito "openai/text-embedding-3-small") vengono lette da normalizeQdrantConfig() in qdrant.ts.

Variabili di ambiente (v3.8.6)

Sei variabili di ambiente facoltative regolano il comportamento del motore in fase di esecuzione (documentate in .env.example):

Variabile Valore predefinito Descrizione
MEMORY_EMBEDDING_CACHE_TTL_MS 300000 TTL della cache degli embedding (5 min)
MEMORY_EMBEDDING_CACHE_MAX 1000 Numero massimo di elementi nella cache LRU degli embedding
MEMORY_TRANSFORMERS_MODEL Xenova/all-MiniLM-L6-v2 Repository HF per il modello Transformers.js
MEMORY_STATIC_MODEL minishlab/potion-base-8M Repository HF per il modello potion statico
MEMORY_STATIC_CACHE_DIR <DATA_DIR>/embeddings Posizione in cui archiviare i modelli scaricati
MEMORY_VEC_TOP_K 20 Valore top-K predefinito per la ricerca vettoriale
MEMORY_RRF_K 60 Costante k RRF per la ricerca ibrida
MEMORY_VEC_QUANTIZATION none Impostare su int8 per archiviare i vettori sqlite-vec locali quantizzati (circa 4 volte più piccoli; funzionalità opt-in). La modifica della modalità forza una reindicizzazione.

Riepilogo (summarization.ts)

summarizeMemories(apiKeyId, sessionId?, maxTokens = 4000) compatta i contenuti meno recenti quando il totale progressivo dei token nelle memorie di una chiave supera il budget. Itera le righe in ordine DESC per created_at, conserva le righe che rientrano nel limite e, per le restanti, sostituisce content direttamente con le prime tre frasi dell'originale. tokensSaved è la differenza in estimateTokens tra il contenuto precedente e quello nuovo.

Questa routine è disponibile ma non viene chiamata automaticamente nella pipeline di chat attuale: richiamala da un cron, da un'azione amministrativa o tramite il codice di raccordo di MemoryConfig.autoSummarize se hai bisogno di una compattazione continua. La perdita di dati è irreversibile: il testo originale viene sovrascritto.

API REST

Tutti gli endpoint richiedono l'autenticazione di gestione (requireManagementAuth).

Endpoint principali delle memorie (esistenti + aggiornati)

Metodo Percorso Descrizione
GET /api/memory Elenco paginato con filtri: apiKeyId, type, sessionId, q, limit, page, offset. La risposta include stats.total, stats.tokensUsed, stats.hitRate, cacheStats
POST /api/memory Crea una voce (convalidata tramite Zod: content, key, type, sessionId, apiKeyId, metadata, expiresAt opzionali). Chiama createMemory(), che esegue un upsert su (apiKeyId, key)
GET /api/memory/[id] Recupera una singola voce tramite UUID
PUT /api/memory/[id] Aggiorna i campi della voce (type, key, content, metadata). Corpo: MemoryUpdatePutSchema. Sincronizza anche il vettore se è disponibile una sorgente di embedding.
DELETE /api/memory/[id] Elimina una voce; la elimina anche da vec_memories (D15) e, in modalità best effort, da Qdrant. Restituisce 404 se non esiste.
GET /api/memory/health Esegue verifyExtractionPipeline("health-check"): ciclo completo creazione→elenco→eliminazione. Restituisce {working, latencyMs, error?}

Nuovi endpoint del motore delle memorie (piano 21)

Metodo Percorso Descrizione
POST /api/memory/retrieve-preview Simulazione di retrieveMemories: restituisce risultati ordinati con punteggio, livello e token. Corpo: RetrievePreviewSchema. NON inserisce né modifica memorie.
GET /api/memory/embedding-providers Elenca i provider con i modelli di embedding, indicando quali dispongono di una chiave API configurata.
GET /api/memory/engine-status Restituisce lo stato completo del motore: livello delle parole chiave, risoluzione dell'embedding, statistiche dell'archivio vettoriale, integrità di Qdrant e configurazione di reranking. Struttura: MemoryEngineStatusSchema.
POST /api/memory/summarize Attiva manualmente la compattazione delle memorie. Corpo: MemorySummarizeSchema (olderThanDays, apiKeyId?, dryRun). Restituisce {candidates, tokensSaved}.
POST /api/memory/reindex Attiva la reindicizzazione vettoriale per le memorie con needs_reindex=1. Corpo: MemoryReindexSchema (force). Restituisce {started, pending}.

Endpoint delle impostazioni

Metodo Percorso Descrizione
GET /api/settings/memory Valore corrente normalizzato di MemorySettingsExtended (7 nuovi campi + campi legacy)
PUT /api/settings/memory Aggiorna qualsiasi campo di MemorySettingsExtendedSchema (12 campi totali)
GET /api/settings/qdrant Impostazioni correnti di Qdrant (QdrantSettingsSchema)
PUT /api/settings/qdrant Aggiorna le impostazioni di Qdrant. Corpo: QdrantSettingsUpdateSchema. apiKey = stringa vuota rimuove la chiave.
GET /api/settings/qdrant/health Sonda di vitalità sull'istanza Qdrant configurata. Restituisce QdrantHealthResultSchema.
POST /api/settings/qdrant/search Test di ricerca semantica su Qdrant. Corpo: QdrantSearchSchema (query, topK).
POST /api/settings/qdrant/cleanup Rimuove da Qdrant i punti relativi a memorie scadute / obsolete.
GET /api/settings/qdrant/embedding-models Elenca i modelli di embedding disponibili per Qdrant.

La query di elenco /api/memory supporta sia la paginazione basata su page (parsePaginationParams) sia il valore offset non elaborato: quando offset è presente, ha la precedenza e viene calcolato un valore page derivato per la struttura della risposta.

Strumenti MCP (open-sse/mcp-server/tools/memoryTools.ts)

Quando il server MCP è abilitato, vengono registrati tre strumenti di memoria:

  • omniroute_memory_search{apiKeyId, query?, type?, maxTokens?, limit?} → esegue il wrapping di retrieveMemories(). A partire dalla v3.8.6 (D16), strategy viene letta da getMemorySettings() anziché essere impostata direttamente su "exact". Se viene fornita query e strategy è semantic o hybrid, viene utilizzato l'archivio vettoriale, se disponibile.
  • omniroute_memory_add{apiKeyId, sessionId?, type, key, content, metadata?} → esegue il wrapping di createMemory(). Accetta solo i 4 tipi canonici: factual, episodic, procedural, semantic (D17).
  • omniroute_memory_clear{apiKeyId, type?, olderThan?} → elenca le voci corrispondenti, le filtra facoltativamente in base alla data di creazione antecedente al timestamp indicato, quindi elimina ciascuna voce tramite deleteMemory() (che rimuove anche i vettori da sqlite-vec + Qdrant).

Consultare MCP-SERVER.md per i dettagli sul trasporto e sull'ambito.

Dashboard (Memory Studio)

src/app/(dashboard)/dashboard/memory/page.tsx è ora uno Studio con 3 schede:

Scheda: Memorie

  • Scheda concettuale (spiegazione comprimibile "Come funziona").
  • Elenco, ricerca e paginazione in tempo reale (debounce di 300 ms).
  • Filtro per tipo (factual / episodic / procedural / semantic / tutti).
  • Finestra modale per aggiungere una memoria (chiave, contenuto, tipo).
  • Modifica in linea (pulsante con la matita → PUT /api/memory/[id]).
  • Eliminazione per riga (con finestra di dialogo di conferma).
  • Esportazione JSON della pagina corrente; importazione JSON tramite selettore di file.
  • Schede statistiche: totalEntries, tokensUsed, hitRate.
  • Pulsante "Compatta le vecchie" → POST /api/memory/summarize (la simulazione iniziale mostra prima il numero di candidate, quindi richiede conferma).
  • Un indicatore di integrità verde/rosso gestito da GET /api/memory/health.

Scheda: Playground

  • Campo di query + selettore della strategia (Esatta / Semantica / Ibrida) + budget di token.
  • "Simula" → POST /api/memory/retrieve-preview — mostra i risultati ordinati con score, tier, tokens, vecScore, ftsScore.
  • Pannello di risoluzione che mostra quale origine degli embedding / archivio vettoriale è stato utilizzato e se si è verificato un fallback.

Scheda: Motore

  • Pannello di stato del motore (indicatore FTS5 per parole chiave, indicatore degli embedding, indicatore dell'archivio vettoriale, indicatore di integrità di Qdrant, indicatore di riordinamento).
  • Pulsante "Reindicizza ora" → POST /api/memory/reindex.
  • Selettore dell'origine degli embedding (automatica / remota / statica / transformers + interruttori).
  • Scheda di configurazione di Qdrant (interruttore di abilitazione, host/porta/raccolta/chiave, test della connessione, test della ricerca semantica, pulizia).
  • Scheda di configurazione del riordinamento (interruttore di abilitazione, selettore provider/modello).

Le impostazioni di memoria e Qdrant sono disponibili anche in /dashboard/settings → Memory & Skills (MemorySkillsTab.tsx) per l'interfaccia delle impostazioni globale/legacy.

Cache

src/lib/memory/store.ts mantiene una cache interna al processo simile a LRU (MEMORY_CACHE_TTL = 1 min, MEMORY_MAX_CACHE_SIZE = 500, con rimozione del 20 % delle voci meno recenti) per le letture getMemory(id), oltre a un livello generico chiave/valore memoryCache (src/lib/memory/cache.ts) con i metodi get/set/invalidate utilizzati dai chiamanti che desiderano una propria cache con ambito specifico (LRU da 1 000 voci, TTL predefinito di 5 min).

Privacy e ciclo di vita

  • Il proprietario della memoria è l'id della chiave API (resolveMemoryOwnerId in chatCore.ts). Senza un apiKeyInfo.id non vengono eseguiti né il recupero, né l'inserimento, né l'estrazione.
  • Le voci con un valore futuro di expires_at vengono escluse dal recupero; le voci precedenti a retentionDays vengono escluse dalla clausola created_at >= cutoff in retrieveMemories.
  • Per l'eliminazione definitiva, usa DELETE /api/memory/[id] o omniroute_memory_clear.
  • L'estrazione è eseguita in modalità fire-and-forget tramite setImmediate; gli errori vengono registrati sotto memory.extraction.background.failed e non vengono mai mostrati al chiamante.
  • I round trip di verifica (verifyExtractionPipeline) eliminano le proprie voci di test in un blocco finally.

Vedi anche

  • SKILLS.md — l'impostazione skillsEnabled inserisce le definizioni degli strumenti insieme alla memoria.
  • MCP-SERVER.md — trasporto/ambiti MCP.
  • API_REFERENCE.md — panoramica più ampia dell'API.
  • Moduli sorgente:
    • src/lib/memory/types.ts, schemas.ts
    • src/lib/memory/store.ts, retrieval.ts, injection.ts, reindex.ts
    • src/lib/memory/extraction.ts, summarization.ts, verify.ts
    • src/lib/memory/settings.ts, qdrant.ts, cache.ts
    • src/lib/memory/vectorStore.ts — sqlite-vec + RRF ibrido
    • src/lib/memory/embedding/index.ts — livello di embedding multi-sorgente
    • src/lib/memory/embedding/types.ts, remote.ts, staticPotion.ts, transformersLocal.ts, cache.ts
    • src/shared/schemas/memory.ts — schemi Zod per tutti i body dell'API di memoria
    • src/shared/schemas/qdrant.ts — schemi Zod per impostazioni/operazioni Qdrant
    • src/lib/db/memoryVec.ts — CRUD per memory_vec_meta
    • src/lib/db/migrations/015_create_memories.sql, 022_add_memory_fts5.sql, 023_fix_memory_fts_uuid.sql, 083_memory_vec.sql
    • src/app/api/memory/route.ts, [id]/route.ts, health/route.ts
    • src/app/api/memory/retrieve-preview/route.ts
    • src/app/api/memory/engine-status/route.ts
    • src/app/api/memory/embedding-providers/route.ts
    • src/app/api/memory/summarize/route.ts
    • src/app/api/memory/reindex/route.ts
    • src/app/api/settings/memory/route.ts
    • src/app/api/settings/qdrant/route.ts + sotto-route
    • src/app/(dashboard)/dashboard/memory/ — interfaccia utente di Studio (pagina + componenti + schede + hook)
    • open-sse/handlers/chatCore.ts (collegamento di inserimento/estrazione)
    • open-sse/mcp-server/tools/memoryTools.ts

Scelta di un provider di embedding (v3.8.16+)

Il motore di memoria di OmniRoute supporta quattro sorgenti di embedding (src/lib/memory/embedding/). Ognuna presenta compromessi diversi in termini di latenza, costo, qualità del modello e complessità di configurazione.

Le sorgenti di embedding

Provider Sorgente Latenza Costo Qualità Configurazione
transformers Modello ONNX locale (Xenova/all-MiniLM-L6-v2) ~50-150ms (CPU) Gratuito Buona Solo npm install
static Vettori pre-calcolati (in cache) <1ms Gratuito N/D (dipende dall'hit della cache) Nessuna
remote API OpenAI / Cohere / Voyage ~100-300ms $0.02-0.10/1M token Eccellente Chiave API
auto Seleziona la migliore sorgente disponibile in fase di esecuzione Come la sorgente selezionata Gratuito Come la sorgente selezionata Nessuna
(cache) Livello LRU in memoria sopra qualsiasi sorgente <1ms (hit), latenza completa (miss) Gratuito Come il livello sottostante Sempre attiva (non selezionabile come sorgente)

Albero decisionale

                  Qual è il contesto di deployment?
                  │
      ┌───────────┼───────────┬──────────────┐
      │           │           │              │
  SVILUPPO/TEST PROD. RIDOTTA PROD. ESTESA  EDGE / OFFLINE
      │           │           │              │
      ▼           ▼           ▼              ▼
  transformers transformers remote (Qdrant) transformers
  (gratis, senza API)        (qualità migliore) (senza internet)
      │           │           │              │
      └────────┬──┴───────────┴──────────────┘
               │
               ▼
            Aggiungi SEMPRE il livello `cache`
            (LruCache racchiude qualsiasi provider)

Configurazione del database e dell'API

Le opzioni di embedding della memoria vengono configurate tramite l'API/interfaccia utente delle Impostazioni, non tramite variabili d'ambiente. Le chiavi pertinenti del database delle impostazioni sotto Impostazioni (normalizeMemorySettings in src/lib/memory/settings.ts) sono:

  • memoryEmbeddingSource: "transformers" (locale), "remote" (basata su API, ad es. OpenAI), "static" (archivio esterno) o "auto"
  • memoryEmbeddingProviderModel: identificatore del modello per le sorgenti remote/statiche (ad es. "text-embedding-3-small")
  • memoryTransformersEnabled: true | false
  • memoryStaticEnabled: true | false
  • memoryVectorStore: "sqlite-vec", "qdrant" o "auto"

Modello locale (transformers)

Utilizza internamente transformers.js per eseguire modelli locali:

# Variabili d'ambiente lette nel codice (src/lib/memory/embedding/index.ts):
MEMORY_TRANSFORMERS_MODEL=Xenova/all-MiniLM-L6-v2  # Repository del modello HF
MEMORY_STATIC_MODEL=minishlab/potion-base-8M       # Modello potion statico HF
MEMORY_STATIC_CACHE_DIR=<DATA_DIR>/embeddings      # Directory della cache

Cache LRU degli embedding

La cache è sempre attiva per impostazione predefinita e viene configurata tramite variabili d'ambiente:

MEMORY_EMBEDDING_CACHE_MAX=1000                    # Numero massimo di elementi nella cache
MEMORY_EMBEDDING_CACHE_TTL_MS=300000               # TTL (5 min)

Dati sulle prestazioni

Benchmark su un tipico server x86 a 4 core (testi di circa 100 token ciascuno):

Provider p50 p95 p99 Costo / 1M di embedding
transformers (CPU) 80ms 180ms 350ms Gratuito
remote (OpenAI) 120ms 220ms 400ms ~$0.02 (ada-002) / $0.13 (3-large)
static (Qdrant) 15ms 30ms 60ms Dipende dall'hosting di Qdrant
cache (hit) <1ms <1ms 2ms Gratuito

Pattern di estrazione dei fatti (v3.8.16+)

Il modulo extraction.ts (src/lib/memory/extraction.ts) utilizza la corrispondenza tramite espressioni regolari per estrarre fatti strutturati dai messaggi delle conversazioni. Comprendere questi pattern aiuta a ottimizzare la qualità dell'estrazione per il proprio caso d'uso.

Categorie di pattern predefinite

Categoria Pattern di esempio Dati acquisiti
PREFERENCE_PATTERNS "Preferisco <X>", "Mi piace <X>", "Odio <X>" Preferenze dell'utente
DECISION_PATTERNS "Userò <X>", "Ho deciso di <X>", "Ho scelto <X>" Decisioni dell'utente (episodiche)
PATTERN_PATTERNS "Di solito <X>", "Faccio sempre <X>", "Non faccio mai <X>" Pattern comportamentali persistenti

Pattern di esempio (semplificati)

// Da src/lib/memory/extraction.ts
const PREFERENCE_PATTERNS = [
  /\bI\s+(?:really\s+)?prefer\s+([^.,\n]+)/gi,
  /\bI\s+(?:really\s+)?like\s+([^.,\n]+)/gi,
  /\bI\s+(?:hate|dislike|avoid)\s+([^.,\n]+)/gi,
];
const DECISION_PATTERNS = [
  /\bI'?(?:ll|will)\s+use\s+([^.,\n]+)/gi,
  /\bI\s+(?:have\s+)?decided\s+(?:to\s+)?([^.,\n]+)/gi,
];
const PATTERN_PATTERNS = [/\bI\s+usually\s+([^.,\n]+)/gi, /\bI\s+always\s+([^.,\n]+)/gi];

Cosa viene estratto

Quando un utente dice:

"Preferisco TypeScript. Userò Postgres per questo progetto. Eseguo sempre il commit prima del push. Python non mi piace." L'estrazione produce 4 ricordi:

Chiave Categoria Tipo Contenuto
preference:typescript preferenza fattuale "TypeScript"
decision:postgres_for_this_project decisione episodico "Postgres per questo progetto"
pattern:commit_before_pushing pattern fattuale "commit prima del push"
preference:python preferenza fattuale "Python"

Limiti dell'estrazione

Per evitare un'estrazione incontrollata, si applicano i seguenti limiti:

| Lunghezza minima del contenuto | 3 caratteri | | Lunghezza massima del contenuto| 500 caratteri |

Quando disabilitare l'estrazione

L'estrazione viene eseguita automaticamente ogni volta che la memoria è abilitata; non esiste un'opzione separata per la sola estrazione. Per disattivarla, disabilitare completamente la memoria (enabled: false tramite PUT /api/settings/memory). È opportuno farlo quando:

  • Il volume dei messaggi è elevato e il costo dell'estrazione non è trascurabile
  • Le conversazioni sono prevalentemente transitorie (chat, debug) e non hanno valore a lungo termine
  • Il contesto viene già acquisito tramite plugin personalizzati

Ottimizzazione RRF ibrida (v3.8.16+)

L'algoritmo Reciprocal Rank Fusion (RRF) combina i risultati FTS5 (parole chiave) e vettoriali (semantici). Il parametro k controlla il peso attribuito ai risultati con una posizione più bassa in classifica.

La formula

Per ogni ricordo candidato, il punteggio RRF è:

RRF(d) = Σ  1 / (k + rank_i(d))

Dove:

  • k è la costante (valore predefinito: 60)
  • rank_i(d) è la posizione del documento d nell'i-esimo sistema di recupero (FTS, vettoriale)
  • La somma viene calcolata su tutti i sistemi di recupero

Come k influisce sui risultati

Valore di k Effetto Ideale per
k=0 Fusione pura delle classifiche (senza attenuazione) Riferimento teorico
k=10-30 Attribuisce molto peso ai risultati migliori; quelli in posizioni basse contribuiscono pochissimo Quando i primi 3 risultati sono solitamente corretti
k=60 (predefinito) Bilanciato: i primi 10 risultati contribuiscono tutti in modo significativo Recupero per uso generico
k=100+ Più uniforme: anche i risultati in posizioni basse possono prevalere se compaiono in più sistemi Quando il richiamo > precisione è fondamentale

Ottimizzazione pratica di k

# Valore predefinito
MEMORY_RRF_K=60

# Precisione aggressiva (memoria piccola, pochi documenti)
MEMORY_RRF_K=20

# Richiamo massimo (memoria grande, query diversificate)
MEMORY_RRF_K=120

Esempio con k=20:

  • Posizione FTS 1 → contributo 1/21 = 0.048
  • Posizione FTS 10 → contributo 1/30 = 0.033
  • Posizione vettoriale 1 → contributo 0.048
  • Massimo combinato: 0.096

Esempio con k=60:

  • Posizione FTS 1 → contributo 1/61 = 0.016
  • Posizione FTS 10 → contributo 1/70 = 0.014
  • Posizione vettoriale 1 → contributo 0.016
  • Massimo combinato: 0.033

Con un valore di k più elevato, la differenza relativa tra il primo e il decimo risultato è minore, pertanto l'algoritmo si basa più sul consenso tra i sistemi di recupero che sull'affidabilità della prima posizione.

Quando modificare k

Sintomo Tentativo
Il primo risultato prevale sempre, ma è errato Ridurre k (ad es., 20): l'affidabilità della prima posizione conta di più
La risposta corretta è tra le prime 5 ma non è prima Aumentare k (ad es., 100): un punteggio più uniforme premia il consenso
Il richiamo è elevato ma la precisione è bassa Ridurre k: rendere più netta la classifica
Il richiamo è basso (mancano documenti pertinenti) Aumentare k: offrire una possibilità ai documenti in posizioni inferiori

Ponderazione RRF

La fusione reciproca delle classifiche utilizza pesi uguali per la posizione vettoriale semantica e per la posizione nella ricerca full-text:

RRF(d) = 1/(k + rank_vector) + 1/(k + rank_fts)

Non esistono variabili d'ambiente per regolare i singoli pesi (MEMORY_RRF_VECTOR_WEIGHT/MEMORY_RRF_FTS_WEIGHT non esistono).


Strategia di riepilogo (v3.8.16+)

Il modulo summarization.ts (src/lib/memory/summarization.ts) comprime i ricordi meno recenti per mantenere ridotto l'insieme attivo, preservando al contempo la capacità di recuperarli.

Evento di attivazione Soglia (predefinita)
Attivazione manuale tramite API n/d

Cosa viene riepilogato

Da summarization.ts vengono esportati due punti di ingresso:

  • summarizeMemories(apiKeyId, sessionId?, maxTokens = 4000) — condensa i ricordi di una sessione in un unico testo riepilogativo limitato da un budget di token.
  • summarizeMemoriesOlderThan(apiKeyId, days, dryRun) — la compattazione basata sull'età utilizzata dall'API: seleziona ogni ricordo più vecchio di days, crea a partire da essi un singolo ricordo riepilogativo condensato e, quando dryRun è false, elimina gli originali. Passa dryRun: true per visualizzare in anteprima l'insieme dei candidati e il totale dei token senza modificare nulla.

Non viene eseguito alcun raggruppamento per tag/chiave né alcuna valutazione per singolo ricordo tra "essenziale e riepilogabile" — la selezione si basa esclusivamente sul limite di età e il testo riepilogativo è costituito da una riga condensata, preceduta dal tipo, per ogni candidato.

Il riepilogo è manuale / facoltativo — l'impostazione autoSummarize è false per impostazione predefinita, quindi nulla viene compattato automaticamente. Attivalo tramite l'API:

curl -X POST http://localhost:20128/api/memory/summarize \
  -H "Authorization: Bearer $OMNIROUTE_KEY"

Per mantenerlo disattivato, lascia semplicemente autoSummarize sul valore predefinito (false).

  • Visualizza prima un'anteprima con dryRunsummarizeMemoriesOlderThan(..., true) restituisce l'elenco dei candidati e il numero totale di token, così puoi verificare cosa verrebbe unito prima di eliminare gli originali.
  • Esegui il riepilogo durante le ore di minor traffico se disponi di un corpus di ricordi di grandi dimensioni — la chiamata all'LLM è la parte più lenta
# Stile cron: esegui il riepilogo ogni giorno alle 3:00
0 3 * * * curl -X POST http://localhost:20128/api/memory/summarize \
  -H "Authorization: Bearer $OMNIROUTE_KEY"

Pattern del provider MemoryBackend

Fonte attendibile: src/lib/memory/backend.ts, src/lib/memory/genericBackend.ts, src/lib/memory/manager.ts Test: src/lib/memory/__tests__/generic-backend.test.ts

Il pattern del provider MemoryBackend introduce un livello di astrazione collegabile per i backend sopra il motore di memoria esistente. Invece di essere vincolato a un'unica implementazione di archiviazione, il sistema di memoria ora supporta più backend (SQLite, Obsidian, Notion, backend HTTP personalizzati) con un instradamento primario/di fallback configurabile.

Architettura

┌──────────────────────────────────────────────────────────┐
│                    Route API                              │
│            (src/app/api/memory/route.ts)                  │
└──────────────────────┬───────────────────────────────────┘
                       │
┌──────────────────────▼───────────────────────────────────┐
│                   MemoryManager                           │
│       Orchestratore singleton (manager.ts)                │
│                                                          │
│  Primario ──► Backend A  (ad es. SQLite)                 │
│  Fallback ──► Backend B  (ad es. Obsidian)               │
│              Backend C  (ad es. Notion tramite            │
│                          GenericBackend)                   │
└──────────────────────┬───────────────────────────────────┘
                       │
        ┌──────────────┼──────────────┐
        ▼              ▼              ▼
┌────────────┐ ┌────────────┐ ┌──────────────────┐
│ Backend    │ │ Backend    │ │ Backend          │
│ SQLite     │ │ Obsidian   │ │ GenericMemory    │
│            │ │            │ │ (HTTP)           │
└────────────┘ └────────────┘ └──────────────────┘

Interfaccia principale (backend.ts)

Ogni backend deve implementare l'interfaccia MemoryBackend:

interface MemoryBackend {
  readonly id: string;
  readonly displayName: string;

  // CRUD
  create(input: CreateMemoryInput): Promise<Memory>;
  get(id: string): Promise<Memory | null>;
  update(id: string, updates: Partial<...>): Promise<boolean>;
  delete(id: string): Promise<boolean>;
  list(filter: MemoryFilter): Promise<{ data: Memory[]; total: number; byType: Record<string, number> }>;

  // Ricerca
  search(config: SearchConfig): Promise<Memory[]>;

  // Stato
  health(): Promise<HealthCheckResult>;

  // Ciclo di vita (facoltativo)
  initialize?(): Promise<void>;
  shutdown?(): Promise<void>;
}

MemoryManager (manager.ts)

Orchestratore singleton che:

  • Registra i backend tramite register(backend) — chiamato all'avvio da index.ts
  • Configura il backend primario e quelli di fallback tramite configure(primary, fallbacks)
  • Instrada le operazioni CRUD e di ricerca verso il backend primario, con una catena di fallback in caso di errore
  • Verifica lo stato di tutti i backend periodicamente

Comportamento di fallback:

Operazione Primario Fallback
create Solo primario
get Prova prima il primario Fallback se null
update Solo primario Sincronizzazione asincrona
delete Solo primario Sincronizzazione asincrona
list Solo primario
search Prima il primario Fallback in caso di errore

GenericMemoryBackend (genericBackend.ts)

Un connettore HTTP generico che adatta qualsiasi API REST a un MemoryBackend. Utile per:

  • Notion — connessione tramite Notion API
  • Obsidian — connessione tramite Obsidian Local REST API
  • Backend personalizzati — qualsiasi servizio che esponga un'API RESTful per la memoria

Configurazione:

interface GenericBackendConfig {
  baseUrl: string;           // URL di base dell'API backend
  apiKey?: string;           // Token Bearer per l'autenticazione
  headers?: Record<string, string>;  // Header HTTP personalizzati
  timeout?: number;          // Timeout della richiesta (predefinito: 30000ms)
  backendType?: string;      // Per il logging

  // Override degli endpoint (i valori predefiniti seguono le convenzioni REST)
  endpoints?: {
    search?: string;   // predefinito: "/memories/search"
    create?: string;   // predefinito: "/memories"
    list?: string;     // predefinito: "/memories"
    get?: string;      // predefinito: "/memories/{id}"
    update?: string;   // predefinito: "/memories/{id}"
    delete?: string;   // predefinito: "/memories/{id}"
    health?: string;   // predefinito: "/health"
  };

  // Mappature dei nomi dei parametri di query
  queryParams?: {
    query?/apiKeyId?/limit?/offset?/strategy?/maxTokens?/type?/sessionId?/orderBy?/orderDir?/options?
  };

  // Mappature dei nomi dei parametri del percorso
  pathParams?: {
    id?/memoryId?
  };
}

I backend noti sono preconfigurati in KNOWN_BACKENDS:

createKnownBackend("obsidian"); // → GenericMemoryBackend indirizzato a localhost:27123
createKnownBackend("notion"); // → GenericMemoryBackend indirizzato ad api.notion.com/v1

Backend integrati

SQLiteBackend (sqliteBackend.ts)

Il backend primario predefinito. Incapsula l'archivio di memoria esistente basato su SQLite utilizzando src/lib/memory/store.ts. Viene registrato automaticamente all'avvio.

import { sqliteBackend } from "./sqliteBackend";
memoryManager.register(sqliteBackend);
ObsidianBackend (obsidianBackend.ts)

Incapsula l'integrazione Obsidian esistente (src/lib/memory/obsidianBackend.ts). Si connette a un vault Obsidian tramite l'API REST locale di Obsidian.

Impostazioni

Le impostazioni dei backend di memoria sono archiviate nella tabella delle impostazioni dell'app e gestite tramite src/lib/memory/settings.ts:

Impostazione Chiave env/config Valore predefinito Descrizione
Backend primario memoryPrimaryBackend "sqlite" ID del backend primario
Backend di fallback memoryFallbackBackends [] ID ordinati dei backend di fallback
Configurazioni dei backend memoryBackendConfigs {} Override della configurazione per backend

Le impostazioni vengono normalizzate tramite normalizeMemorySettings() e memorizzate nella cache in getMemorySettings().

Flusso di inizializzazione

Avvio dell'app
  → importazioni di index.ts (effetto collaterale): registra SQLiteBackend
  → initMemoryBackends() chiamata dal ciclo di vita dell'app:
      1. Carica le impostazioni (getMemorySettings)
      2. Configura il backend primario e quelli di fallback
      3. Inizializza tutti i backend (controllo dello stato)
      4. Pronto per le richieste

Aggiunta di un nuovo backend

  1. Implementare l'interfaccia MemoryBackend in src/lib/memory/<name>Backend.ts
  2. Esportare da src/lib/memory/index.ts
  3. Registrare con memoryManager.register(yourBackend) all'avvio
  4. Configurare tramite le impostazioni: impostare memoryPrimaryBackend sull'ID del proprio backend
  5. Testare usando src/lib/memory/__tests__/generic-backend.test.ts come riferimento

Esempio: backend Brain

import { createGenericMemoryBackend } from "./genericBackend";

const brainBackend = createGenericMemoryBackend("brain", "BK-Brain", {
  baseUrl: process.env.BRAIN_API_URL || "http://localhost:9099",
  apiKey: process.env.BRAIN_API_KEY,
  endpoints: {
    search: "/api/memory/search",
    create: "/api/memory",
    health: "/api/health",
  },
});

memoryManager.register(brainBackend);

Verifica

Test unitari

npx vitest run src/lib/memory/__tests__/generic-backend.test.ts --reporter=verbose

Output previsto: 35 test, tutti superati, relativi a:

  • Costruttore (2)
  • Controllo dello stato (4) — esito positivo, errore 500, errore di rete, latenza
  • Inizializzazione (2) — esito positivo, errore
  • Creazione (2) — endpoint predefinito, endpoint personalizzato
  • Recupero (4) — esito positivo, 404 → null, eccezione per errori diversi da 404, parametri del percorso personalizzati
  • Aggiornamento (2) — esito positivo, 404 → false
  • Eliminazione (2) — esito positivo, 404 → false
  • Elenco (2) — parametri di query, nomi dei parametri personalizzati
  • Ricerca (3) — parametri di query, endpoint personalizzato, serializzazione delle opzioni
  • Header di autenticazione (2) — token Bearer, header personalizzati
  • Factory (1)

Controllo dei tipi

npm run typecheck:core

Risultato previsto: 0 errori.