* 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.
73 KiB
Memory System (Deutsch)
🌐 Languages: 🇺🇸 English · 🇪🇹 am · 🇸🇦 ar · 🇦🇿 az · 🇧🇬 bg · 🇧🇩 bn · 🇨🇿 cs · 🇩🇰 da · 🇬🇷 el · 🇪🇸 es · 🇪🇪 et · 🇮🇷 fa · 🇫🇮 fi · 🇫🇷 fr · 🇮🇪 ga · 🇮🇳 gu · 🇳🇬 ha · 🇮🇱 he · 🇮🇳 hi · 🇭🇷 hr · 🇭🇺 hu · 🇦🇲 hy · 🇮🇩 id · 🇳🇬 ig · 🇮🇹 it · 🇯🇵 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
Verbindliche Quelle:
src/lib/memory/undsrc/app/api/memory/Zuletzt aktualisiert: 2026-06-28 — v3.8.40 (standardmäßig deaktiviert + Nachholung der int8-Quantisierung)
OmniRoute bietet einen dauerhaften Konversationsspeicher, der nach API-Schlüssel (und optional nach Sitzungs-ID) getrennt ist. Erinnerungen werden mittels leichtgewichtiger Regex-Mustererkennung automatisch aus LLM-Antworten extrahiert und als vorangestellte Systemnachricht wieder in nachfolgende Anfragen eingefügt (oder als erste Benutzernachricht bei Anbietern, die die Systemrolle ablehnen).
Der Speicher ist standardmäßig DEAKTIVIERT (v3.8.30+).
DEFAULT_MEMORY_SETTINGS.enabledist jetztfalse(src/lib/memory/settings.ts). Durch Aktivieren des Speichers werden bis zumaxTokens(~2k) an abgerufenem Kontext in jede Chat-Anfrage eingefügt, was abgerechnet wird — unerwartete Kosten für neue Installationen und für Clients, die ihren eigenen Kontext verwalten. Aktivieren Sie die Funktion explizit unter Einstellungen → Speicher (derMemorySkillsTabzeigt bei aktiviertem Speicher einen Warnhinweis zu Token-Kosten an). Ein Client kann den Speicher für eine einzelne Anfrage über den Anfrage-Headerx-omniroute-no-memory(true/1/yes) deaktivieren — siehe die Anfrage-Header-Tabelle in API_REFERENCE.md. Eine Anfrage ohne Speicher setztmemoryOwnerId = null, wodurch für diese Anfrage sowohl die Speicher- als auch die Skill-Injektion deaktiviert wird (open-sse/handlers/chatCore/headers.ts::isNoMemoryRequested).
Der Speicher ist pro API-Schlüssel abgegrenzt, nicht pro Benutzer — jede
Anfrage, die mit demselben API-Schlüssel authentifiziert wird, verwendet
denselben Speicherpool, mit optionaler weiterer Abgrenzung über sessionId.
Architektur
Client → /v1/chat/completions (apiKeyInfo vorgelagert aufgelöst)
→ handleChatCore() [open-sse/handlers/chatCore.ts]
→ resolveMemoryOwnerId(apiKeyInfo) # extrahiert die ID
→ getMemorySettings() # zwischengespeicherte Einstellungen
→ shouldInjectMemory(body, {enabled}) # Zugriffsprüfung
→ retrieveMemories(apiKeyId, config) # SQL + FTS5 + optionaler Vektor
→ injectMemory(body, memories, provider) # System- oder Benutzernachricht
→ Aufruf des vorgelagerten Anbieters
→ bei Antwort: extractFacts(text, apiKeyId, sessionId) # nicht blockierend
→ setImmediate → createMemory(fact) pro Treffer
→ embed(content) + upsertVector(id, vec)
Die Aufrufstellen für Injektion und Extraktion sind in
open-sse/handlers/chatCore.ts eingebunden (suchen Sie nach retrieveMemories,
injectMemory und extractFacts).
Engine-Architektur (dreistufige Auflösung)
Die Memory Engine bestimmt den Abrufpfad zur Laufzeit anhand der verfügbaren Infrastruktur und Einstellungen. Es gibt drei Stufen, die in der folgenden Prioritätsreihenfolge angewendet werden:
┌─────────────────────────────────────────────────────────────┐
│ STUFE 0 — Schlüsselwort (FTS5) │
│ Testbasierte Verfügbarkeit: FTS5, wenn der SQLite-Build │
│ es unterstützt (better-sqlite3 / node:sqlite / bun:sqlite);│
│ nicht verfügbar bei Builds ohne FTS5 (z. B. sql.js/WASM — │
│ "no such module: fts5"). Wird bei strategy = "exact" oder │
│ als Rückfalloption verwendet; der Schlüsselwortstatus der │
│ Engine gibt das Testergebnis wieder. │
└──────────────────────────────────┬──────────────────────────┘
│ strategy = semantic|hybrid?
▼
┌─────────────────────────────────────────────────────────────┐
│ STUFE 1 — Eingebetteter Vektor (sqlite-vec) │
│ sqlite-vec v0.1.9 wird über db.loadExtension() geladen. │
│ KNN-Brute-Force-Suche über Float32-Vektoren. Aktiv, wenn: │
│ • sqlite-vec loadExtension erfolgreich ist │
│ • Eine Embedding-Quelle verfügbar ist (remote | static | │
│ transformers), die ein Float32Array erzeugen kann │
│ • die Tabelle vec_memories existiert (beim ersten ready() │
│ erstellt) │
└──────────────────────────────────┬──────────────────────────┘
│ qdrant.enabled?
▼
┌─────────────────────────────────────────────────────────────┐
│ STUFE 2 — Qdrant (optionale externe Vektordatenbank) │
│ Wenn aktiviert, ersetzt Qdrant sqlite-vec für semantische/ │
│ hybride Suchen. Erfordert eine laufende Qdrant-Instanz und │
│ einen konfigurierten Host/Port. │
└─────────────────────────────────────────────────────────────┘
Die Herabstufung erfolgt automatisch und transparent:
- Wenn sqlite-vec nicht geladen werden kann, ist Stufe 1 nicht verfügbar → es wird auf Stufe 0 zurückgefallen.
- Wenn die Embedding-Quelle einen Fehler zurückgibt, fällt Stufe 1 auf Stufe 0 zurück.
- Wenn Qdrant nicht funktionsfähig ist, fällt Stufe 2 auf Stufe 1 zurück (oder auf Stufe 0, wenn auch Stufe 1 nicht verfügbar ist).
Embedding-Quellen
Die Embedding-Schicht (src/lib/memory/embedding/) bestimmt anhand von
MemorySettingsExtended.embeddingSource, welche Quelle verwendet wird:
| Quelle | Beschreibung | Schlüssel erforderlich | Kaltstart |
|---|---|---|---|
remote |
Verwendet die Embedding-API eines konfigurierten Anbieters (OpenAI, Cohere usw.) | Ja | Keiner |
static |
Lokales Lookup-Tabellen-Embedding über potion-base-8M (WordPiece + Mean-Pooling) |
Nein | ~200ms |
transformers |
Lokale ONNX-Inferenz über @huggingface/transformers v4, all-MiniLM-L6-v2 |
Nein | ~3s + ~400MB RAM |
auto |
Laufzeitauflösung: remote (wenn Schlüssel vorhanden) → static → transformers → null | Abhängig | Abhängig |
Auflösungsreihenfolge für auto:
- Ersten Anbieter in
listEmbeddingProviders()mithasKey === truesuchen →remote. - Wenn
settings.staticEnabled === true→static. - Wenn
settings.transformersEnabled === true→transformers. - Andernfalls →
null(fällt auf die FTS5-Schlüsselwortsuche zurück).
Der Embedding-Cache (src/lib/memory/embedding/cache.ts) verwendet eine
In-Memory-LRU-Map mit Schlüsseln im Format ${source}:${model}:${dim}:${sha256(text)},
die auf MEMORY_EMBEDDING_CACHE_MAX Einträge (Standardwert 1000) begrenzt ist und
eine TTL von MEMORY_EMBEDDING_CACHE_TTL_MS (Standardwert 5 Min.) besitzt. Sie wird
während des Prozesslebenszyklus von allen Aufrufern gemeinsam genutzt.
Hybrides RRF (k=60)
Wenn strategy = "hybrid" gilt und der Vektorspeicher verfügbar ist, verwendet
der Abruf Reciprocal Rank Fusion, um FTS5- und Vektorergebnisse zusammenzuführen:
RRF(d) = Σ 1 / (k + rank_i(d)) wobei k = 60 (konfigurierbar über MEMORY_RRF_K)
i
Konkret:
- FTS5-Suche ausführen → sortierte Liste
R_fts(Position 1..N). - KNN-Vektorsuche ausführen → sortierte Liste
R_vec(Position 1..M). - Für jede eindeutige
memoryId:
rrf_score = 1/(60 + fts_rank)+1/(60 + vec_rank)(0, wenn nicht in der Liste). - Nach
rrf_scoreDESC sortieren und das Token-Budget schrittweise anwenden.
RRF ist dafür bekannt, auch ohne Normalisierung der Scores verschiedener
Abrufsysteme effektiv zu sein. Der Standardwert k=60 stammt aus der
ursprünglichen Veröffentlichung von Cormack et al. und funktioniert gut für
kleine Korpora (<10k Erinnerungen).
Nachträgliche Befüllung (verzögert + Neuindizierung)
Wenn sich das Embedding-Modell ändert (erkannt über embedding_signature), wird
der Vektorspeicher neu aufgebaut und alle vorhandenen Erinnerungen werden in
der Tabelle memories mit needs_reindex = 1 markiert.
Verzögerte nachträgliche Befüllung: Beim nächsten Abruf werden alle
Erinnerungen ohne Vektoreintrag eingebettet und vor Ausführung der Suche in
vec_memories eingefügt. Dadurch werden die Kosten der nachträglichen Befüllung
auf tatsächliche Anfragen verteilt, ohne den Start zu blockieren.
Explizite Neuindizierung: Die Registerkarte „Engine“ unter
/dashboard/memory stellt eine Schaltfläche „Jetzt neu indizieren“ bereit, die
POST /api/memory/reindex aufruft. Der Handler ruft runReindexBatch() aus
src/lib/memory/reindex.ts auf, wodurch pro Anfrage bis zu limit ausstehende
Einträge verarbeitet werden. Der Fortschritt kann über
GET /api/memory/engine-status (vectorStore.needsReindex) abgefragt werden.
Die Tabelle memory_vec_meta (Migration 083_memory_vec.sql) speichert:
active_dim— aktuelle Vektordimension (null = noch nicht kalibriert).embedding_signature—${source}:${model}:${dim}zur Erkennung von Änderungen.last_reset_at— Zeitstempel der letzten vollständigen Zurücksetzung.vec_loaded— 0/1-Kennzeichen dafür, ob sqlite-vec erfolgreich geladen wurde.
Einstellungserweiterung
Neun Einbettungs- und Vektorfelder sind in MemorySettingsExtended in
src/shared/schemas/memory.ts verfügbar und werden über src/lib/db/settings.ts persistiert:
| Feld | Typ | Standardwert | Beschreibung |
|---|---|---|---|
embeddingSource |
"remote" | "static" | "transformers" | "auto" |
"auto" |
Zu verwendende Einbettungsquelle |
embeddingProviderModel |
string | null |
null |
Anbieter/Modell im Format provider/model |
customBaseUrl |
string | null |
null |
OpenAI-kompatible Basis-URL des Endpunkts nur für Memory |
customModelId |
string | null |
null |
An den benutzerdefinierten Endpunkt gesendete Modell-ID |
transformersEnabled |
boolean |
false |
Opt-in für Transformers.js (MiniLM, ~400MB) |
staticEnabled |
boolean |
false |
Opt-in für das lokale statische Modell potion-base-8M |
rerankEnabled |
boolean |
false |
Neusortierungsschritt aktivieren (+200–500 ms/Anfrage) |
rerankProviderModel |
string | null |
null |
Anbieter/Modell für Neusortierung im Format provider/model |
vectorStore |
"sqlite-vec" | "qdrant" | "auto" |
"auto" |
Zu verwendendes Vektor-Backend |
Diese werden über GET /PUT /api/settings/memory bereitgestellt (Schema MemorySettingsExtendedSchema).
Für die Quelle remote akzeptiert Memory außerdem die optionalen Einstellungen customBaseUrl und
customModelId. Gemeinsam wählen sie einen OpenAI-kompatiblen /embeddings-Endpunkt
und ein Modell aus, ohne die globale Einbettungsregistrierung zu ändern. Der Endpunkt wird
vor der Verwendung normalisiert und anhand der Richtlinie für ausgehende Anbieter-URLs geprüft: HTTP(S) ist
erforderlich, eingebettete Zugangsdaten und Abfragezeichenfolgen werden abgelehnt und Adressen
für Cloud-Metadaten bleiben gesperrt. Leere Werte behalten den ausgewählten registrierten Anbieter bei. An das
Dashboard zurückgegebene Fehler werden bereinigt, und Endpunkt-Zugangsdaten werden niemals protokolliert.
TODO (D20): Der Gültigkeitsbereich
global(gemeinsame Nutzung von Erinnerungen über alle API-Schlüssel hinweg) ist in dieser Version nicht implementiert. Dafür sind Schemaänderungen und ein globaler Abrufpfad erforderlich. Separat nachverfolgen.
Speicherschichten
Primär: SQLite (Tabelle memories)
Erstellt durch die Migration 015_create_memories.sql:
| Spalte | Typ | Hinweise |
|---|---|---|
id |
TEXT PRIMARY KEY |
Über crypto.randomUUID() generierte UUID |
api_key_id |
TEXT NOT NULL |
Zugehöriger API-Schlüssel |
session_id |
TEXT |
Optionaler Gültigkeitsbereich pro Unterhaltung |
type |
TEXT NOT NULL |
Einer von factual, episodic, procedural, semantic |
key |
TEXT |
Stabiler Upsert-Schlüssel, z. B. preference:i_prefer_python |
content |
TEXT NOT NULL |
Der eigentliche Faktentext |
metadata |
TEXT |
JSON-Blob (Kategorie, extractedAt, Quelle, ...) |
created_at / updated_at |
TEXT |
ISO-8601-Zeichenfolgen |
expires_at |
TEXT |
Optionales Ablaufdatum; NULL bedeutet dauerhaft |
memory_id |
INTEGER UNIQUE |
Durch 023_fix_memory_fts_uuid.sql hinzugefügt, um UUIDs ↔ FTS5-rowids zu verbinden |
Indizes: api_key_id, session_id, type, expires_at sowie der eindeutige
memory_id-Index.
Upsert-Semantik: createMemory() sucht nach einer vorhandenen Zeile mit demselben
(api_key_id, key) und aktualisiert sie direkt, wenn sie gefunden wird (wobei metadata mittels
flacher Verteilung zusammengeführt wird). Dadurch wächst die Tabelle bei wiederholten
Präferenzaussagen nicht unbegrenzt.
Volltextsuche (virtuelle Tabelle memory_fts)
022_add_memory_fts5.sql erstellt eine virtuelle FTS5-Tabelle über content und
key. 023_fix_memory_fts_uuid.sql behebt einen in der Praxis aufgetretenen Fehler, bei dem der UUID-
Primärschlüssel nicht mit der ganzzahligen rowid von FTS5 verknüpft werden konnte — die Migration fügt die
Spalte memory_id hinzu, erstellt die FTS-Tabelle neu und richtet Trigger
(memory_fts_ai, memory_fts_ad, memory_fts_au) ein, die FTS bei
INSERT, DELETE und UPDATE synchron halten.
Wird von retrieval.ts für die Strategien semantic und hybrid verwendet (siehe unten).
Der Abrufcode prüft mit hasTable("memory_fts") und fällt auf die
chronologische Reihenfolge zurück, wenn die FTS-Tabelle fehlt oder die FTS-Abfrage einen Fehler auslöst.
Optional: Qdrant (Vektorspeicher der Stufe 2)
src/lib/memory/qdrant.ts implementiert eine optionale Qdrant-Integration als Vektorspeicher
der Stufe 2. Der Abruf wird nur dann an Qdrant weitergeleitet, wenn der Engine-Selektor
memoryVectorStore === "qdrant" ist — der Standardwert "auto" (und "sqlite-vec")
wählt Qdrant niemals aus. Der Umschalter auf der Registerkarte „Engine“ setzt sowohl qdrantEnabled als auch
memoryVectorStore gemeinsam: Durch Aktivieren wird Qdrant zum primären Speicher, durch Deaktivieren
wird auf "auto" zurückgesetzt (#5597 — vor dieser Korrektur hatte das Aktivieren keine Wirkung, da nichts
den Engine-Selektor setzte). Wenn Qdrant nicht erreichbar ist oder nichts zurückgibt, fällt der Abruf
auf sqlite-vec → FTS5 zurück.
upsertSemanticMemoryPoint()— bettetkey + contentmit dem konfigurierten Embedding-Modell ein, stellt sicher, dass die Collection vorhanden ist (erstellt bei der ersten Verwendung Vektoren mit Kosinusdistanz), und fügt einen Punkt mit der Payload{memoryId, apiKeyId, sessionId, key, content, metadata, createdAtUnix, expiresAtUnix}ein oder aktualisiert ihn.searchSemanticMemory(query, topK, scope)— bettet die Abfrage ein, durchsucht die Collection, gefiltert nachkind = "omniroute_memory"und optional nachapiKeyId/sessionId. BegrenzttopKauf[1, 20].deleteSemanticMemoryPoint(id)— löscht einen einzelnen Punkt. Wird vondeleteMemory()aufgerufen, nachdem die SQLite-Zeile entfernt wurde (D15).cleanupSemanticMemoryPoints({retentionDays})— löscht gesammelt Punkte, derenexpiresAtUnixin der Vergangenheit liegt oder derencreatedAtUnixälter als der Aufbewahrungsgrenzwert ist. Zählt sie zuerst, damit das Dashboard die tatsächlichen Zahlen anzeigen kann.checkQdrantHealth()— Zustandsprüfung überGET /readyzeinschließlich Latenz.
Die Einstellungsoberfläche stellt die Qdrant-Konfiguration, Zustandsprüfung,
einen Test der semantischen Suche und die Bereinigung im Engine-Tab von
/dashboard/memory bereit. Die entsprechenden Routen unter
src/app/api/settings/qdrant/ sind seit v3.8.6 vollständig angebunden:
| Route | Methode | Beschreibung |
|---|---|---|
/api/settings/qdrant |
GET / PUT |
Qdrant-Einstellungen lesen/aktualisieren |
/api/settings/qdrant/health |
GET |
Verfügbarkeitsprüfung + Latenz |
/api/settings/qdrant/search |
POST |
Test der semantischen Suche |
/api/settings/qdrant/cleanup |
POST |
Abgelaufene/alte Punkte entfernen |
/api/settings/qdrant/embedding-models |
GET |
Verfügbare Embedding-Modelle auflisten |
Hinweise zum Verhalten (was zu erwarten ist):
- Engine-Auswahl — wenn Qdrant im Engine-Tab aktiviert wird, wird es zum
primären Speicher (setzt
memoryVectorStore="qdrant"); beim Deaktivieren wird die Einstellung auf"auto"zurückgesetzt (#5597). - Keine nachträgliche Übernahme — nur Erinnerungen, die nach der Aktivierung von Qdrant erstellt/aktualisiert werden, werden dorthin geschrieben (asynchrones Dual-Write ohne Warten auf das Ergebnis). Bereits vorhandene SQLite-Erinnerungen werden nicht migriert; „Jetzt neu indizieren“ erstellt lediglich den sqlite-vec-Index neu, nicht Qdrant.
- Die Vektordimension wird bei der ersten Verwendung automatisch aus dem tatsächlichen Embedding erkannt — es muss kein Dimensionsfeld ausgefüllt werden. Das Ändern des Embedding-Modells nach dem Erstellen einer Collection wird nicht automatisch verarbeitet: Die vorhandene Collection bleibt unverändert, Schreib-/Suchvorgänge mit abweichender Dimension schlagen fehl und greifen ersatzweise auf sqlite-vec zurück. Erstellen Sie die Collection neu (neuer Name oder Löschen in Qdrant), um den Embedder zu wechseln.
- Distanzmetrik — immer Kosinus (bei der Erstellung der Collection fest codiert; nicht konfigurierbar).
- Authentifizierung — nur per API-Schlüssel (wird als
api-key-Header gesendet; für nicht authentifiziertes lokales Docker optional). JWT/RBAC werden nicht verwendet. - Konfigurationsfelder — die Benutzeroberfläche stellt
host,port,collection,embeddingModel,apiKeybereit.vectorSize/hnswEfConstructsind nur über Umgebungsvariablen bzw. die Datenbank verfügbar, undvectorSizewird nicht für die Erstellung der Collection verwendet (die Dimension stammt aus dem Embedding).
Vektorquantisierung (int8 — optional, beide Backends)
Beide Vektor-Backends unterstützen eine optionale int8-Quantisierung, um den Speicherbedarf der gespeicherten Vektoren zu reduzieren (etwa 4-mal kleiner als Float32), wobei die Trefferquote geringfügig sinkt. Standardmäßig ist sie bei beiden deaktiviert — die Vektoren behalten ihre volle Genauigkeit, sofern sie nicht ausdrücklich aktiviert wird.
| Backend | Einstellung | Typ | Standard | Ausgelesen in |
|---|---|---|---|---|
| Qdrant | qdrantQuantization (DB-Schlüssel) |
"none" | "int8" | "binary" |
"none" |
src/lib/memory/qdrant.ts::normalizeQdrantConfig() |
| sqlite-vec | MEMORY_VEC_QUANTIZATION (Umgebungsvariable) |
"none" | "int8" |
"none" |
src/lib/memory/vectorStore.ts::requestedVecQuantization() |
- Qdrant wird pro Instanz über den Einstellungsschlüssel
qdrantQuantizationkonfiguriert (als FeldquantizationbeiPUT /api/settings/qdrantverfügbar). Bei"int8"fordertbuildQuantizationConfig()eine skalare Quantisierung (always_ram, Quantil0.99) an, und Suchvorgänge aktivierenrescore: true, damit die Vektoren mit voller Genauigkeit die int8-Kandidatenmenge verfeinern. - Die Quantisierung von sqlite-vec ist nur über die Umgebung
konfigurierbar (keine DB-Einstellung): Setzen Sie
MEMORY_VEC_QUANTIZATION=int8, um die lokalen Vektoren übervec_quantize_int8(?, 'unit')alsint8[dim]-Spalte zu speichern. Der ausgewählte Modus wird in dieembedding_signatureaufgenommen (mit dem Suffix:int8), sodass ein Wechsel des Modus eine vollständige Neuindizierung der Tabellevec_memoriesauslöst — über denselben verzögerten Backfill-Pfad, der bei einer Änderung des Embedding-Modells verwendet wird.
Speichertypen
MemoryType (src/lib/memory/types.ts):
| Typ | Verwendungszweck |
|---|---|
factual |
Präferenzen, stabile Benutzerfakten, Verhaltensmuster |
episodic |
Entscheidungen, die an einen bestimmten Zeitpunkt gebunden sind („Ich habe Postgres gewählt“) |
procedural |
Workflow-/Anleitungswissen (reserviert; derzeit keine automatische Extraktion) |
semantic |
Reserviert für Einträge im Vektorspeicher |
Die Abrufstrategie von MemoryConfig ist entweder exact, semantic oder hybrid,
und der Geltungsbereich ist entweder session, apiKey oder global. Der
Standardgeltungsbereich von getMemorySettings() ist apiKey.
Faktenextraktion (extraction.ts)
Die Extraktion ist regex-basiert, nicht LLM-basiert — sie wird prozessintern mit
setImmediate() ausgeführt, sodass sie den Antwortstream niemals blockiert:
- Präferenzmuster →
MemoryType.FACTUAL(z. B.I prefer …,I really like …,my favorite is …,I hate …) - Entscheidungsmuster →
MemoryType.EPISODIC(z. B.I'll use …,I chose …,I went with …,I'm going to adopt …) - Verhaltensmuster →
MemoryType.FACTUAL(z. B.I usually …,I always …,I tend to …)
Jeder Treffer wird bereinigt (trim, Zusammenfassung von Leerraum, Begrenzung
auf 500 Zeichen), innerhalb des Batches über einen stabilen
factKey(category, content) dedupliziert und mittels createMemory() mit den
Metadaten {category, extractedAt, source: "llm_response"} gespeichert. Der
Eingabetext ist auf 64 KiB (MAX_EXTRACTION_TEXT_LENGTH) begrenzt — bei
längeren Texten wird das Ende des Textes verwendet, damit die neuesten
Assistenteninhalte immer berücksichtigt werden.
extractFactsFromText(text) wird für Tests exportiert und gibt die
strukturierten Fakten zurück, ohne sie zu speichern.
Abruf (retrieval.ts)
retrieveMemories(apiKeyId, config) ist der Haupteinstiegspunkt. Die Funktion:
- Normalisiert und validiert die Konfiguration mithilfe von
MemoryConfigSchema. - Gibt sofort
[]zurück, wennenabledfalse odermaxTokens <= 0ist. - Begrenzt
maxTokensauf[1, 8000]. - Erkennt, ob die moderne Tabelle
memoriesvorhanden ist (im Gegensatz zur älteren Tabellememory), damit ältere Datenbanken weiterhin funktionieren. - Erstellt die Basisabfrage mit einer Ablaufprüfung
(
expires_at IS NULL OR datetime(expires_at) > datetime('now')), einem optionalen Sitzungsbereich und einem optionalen Grenzwert fürretentionDays. - Verzweigt abhängig von der Strategie:
exact(Standard): chronologisch mitORDER BY created_at DESC LIMIT 100.semantic: Wennconfig.querygesetzt ist undmemory_ftsexistiert, wird per JOINmemory_fts MATCH ?verknüpft und nach FTS-Rang sortiert; wenn FTS 0 Zeilen zurückgibt, wird auf die chronologische Sortierung zurückgegriffen.hybrid: Vereinigung der FTS-Ergebnisse (höhere Relevanz) mit der chronologischen Menge, dedupliziert nach ID.
- Berechnet bei Angabe einer Abfrage einen Schlüsselwort-Relevanzwert
(
getRelevanceScore) übercontent,keyund das JSON-Feldmetadata. Zeilen mit einem Wert von null werden herausgefiltert. - Sortiert absteigend nach Relevanzwert und anschließend absteigend nach
createdAt. - Durchläuft die Rangliste und übernimmt Einträge, solange die fortlaufende
Summe von
estimateTokens(content)(≈length / 4) das Budget nicht überschreitet. Gibt immer mindestens einen Eintrag zurück, sofern es mindestens einen Treffer gibt.
estimateTokens wird exportiert und vom Abruf, von der Zusammenfassung und vom
MCP-Tool omniroute_memory_search verwendet.
Injektion (injection.ts)
injectMemory(request, memories, provider):
- Fügt alle Speicherinhalte zu einer einzigen Zeichenfolge
Memory context: …zusammen. - Wählt anhand des Anbieternamens eine Strategie:
- Systemnachricht (Standard für OpenAI, Anthropic, Gemini, …) — stellt
eine
{role: "system", content: memoryText}-Nachricht allen vorhandenen Systemnachrichten voran, sodass System-Prompts des Benutzers weiterhin Vorrang haben. - Benutzernachricht (Fallback) — für Anbieter in
PROVIDERS_WITHOUT_SYSTEM_MESSAGE:o1,o1-mini,o1-preview,glm,glmt,glm-cn,zai,qianfan. Diese lehnen die Systemrolle ab und würden andernfalls einen 400-Fehler zurückgeben (siehe Issue #1701 für GLM/Zhipu).
- Systemnachricht (Standard für OpenAI, Anthropic, Gemini, …) — stellt
eine
- Protokolliert Anzahl, Strategie und Modell unter
memory.injection.injected.
providerSupportsSystemMessage(provider) wird für Aufrufer exportiert, die
eigene Routing-Entscheidungen treffen müssen. Bei unbekannten Anbietern wird
aus Sicherheitsgründen standardmäßig true verwendet (Systemrolle zulässig).
Einstellungen (settings.ts)
Die Speicherkonfiguration wird in der DB-Einstellungstabelle gespeichert, nicht in Umgebungsvariablen.
getMemorySettings() liest aus getSettings() und speichert das Ergebnis
prozessintern zwischen; invalidateMemorySettingsCache() wird nach Schreibvorgängen
von der PUT-Route für Einstellungen aufgerufen.
Legacy-Felder (alle Versionen)
| DB-Schlüssel | Typ | Standard | UI-Steuerelement |
|---|---|---|---|
memoryEnabled |
boolean | false (seit v3.8.30 standardmäßig deaktiviert) |
Speicher ein/aus |
memoryMaxTokens |
integer | 2000 (Bereich 0–16000) |
Token-Budget für die Injektion |
memoryRetentionDays |
integer | 30 (Bereich 1–365) |
Aufbewahrungszeitraum |
memoryStrategy |
enum | "hybrid" (einer von recent, semantic, hybrid) |
Abrufstrategie |
skillsEnabled |
boolean | false |
Schaltet die schlüsselspezifische Skill-Injektion um (siehe SKILLS.md) |
Hinweis: Die UI-Strategie "recent" wird über toMemoryRetrievalConfig() der
internen Abrufstrategie "exact" zugeordnet (chronologische Reihenfolge).
Neue Felder (v3.8.6, Plan 21 D9)
Siehe auch den obigen Abschnitt „Erweiterung der Einstellungen“ für Feldbeschreibungen.
| DB-Schlüssel | API-Feld | Standard |
|---|---|---|
memoryEmbeddingSource |
embeddingSource |
"auto" |
memoryEmbeddingModel |
embeddingProviderModel |
null |
memoryTransformersEnabled |
transformersEnabled |
false |
memoryStaticEnabled |
staticEnabled |
false |
memoryRerankEnabled |
rerankEnabled |
false |
memoryRerankModel |
rerankProviderModel |
null |
memoryVectorStore |
vectorStore |
"auto" |
Qdrant-bezogene DB-Schlüssel (qdrantEnabled, qdrantHost, qdrantPort,
qdrantApiKey, qdrantCollection mit dem Standardwert "omniroute_memory",
qdrantEmbeddingModel mit dem Standardwert "openai/text-embedding-3-small") werden von
normalizeQdrantConfig() in qdrant.ts gelesen.
Umgebungsvariablen (v3.8.6)
Sechs optionale Umgebungsvariablen passen das Laufzeitverhalten der Engine an (dokumentiert in .env.example):
| Variable | Standard | Beschreibung |
|---|---|---|
MEMORY_EMBEDDING_CACHE_TTL_MS |
300000 |
TTL des Embedding-Caches (5 Min.) |
MEMORY_EMBEDDING_CACHE_MAX |
1000 |
Maximale Anzahl von Einträgen im Embedding-LRU-Cache |
MEMORY_TRANSFORMERS_MODEL |
Xenova/all-MiniLM-L6-v2 |
HF-Repository für das Transformers.js-Modell |
MEMORY_STATIC_MODEL |
minishlab/potion-base-8M |
HF-Repository für das statische Potion-Modell |
MEMORY_STATIC_CACHE_DIR |
<DATA_DIR>/embeddings |
Speicherort für heruntergeladene Modelle |
MEMORY_VEC_TOP_K |
20 |
Standardmäßiges Top-K für die Vektorsuche |
MEMORY_RRF_K |
60 |
RRF-k-Konstante für die Hybridsuche |
MEMORY_VEC_QUANTIZATION |
none |
Auf int8 setzen, um lokale sqlite-vec-Vektoren quantisiert zu speichern (~4× kleiner; Opt-in). Eine Modusänderung erzwingt eine Neuindizierung. |
Zusammenfassung (summarization.ts)
summarizeMemories(apiKeyId, sessionId?, maxTokens = 4000) komprimiert ältere
Inhalte, wenn die laufende Gesamtzahl der Token in den Erinnerungen eines Schlüssels
das Budget überschreitet. Die Funktion durchläuft die Zeilen absteigend nach created_at,
behält passende Zeilen bei und ersetzt bei den übrigen content direkt durch die ersten
drei Sätze des Originals. tokensSaved ist die Differenz von estimateTokens zwischen
altem und neuem Inhalt.
Diese Routine ist verfügbar, wird aber in der aktuellen Chat-Pipeline nicht automatisch
aufgerufen — rufen Sie sie über einen Cronjob, eine Administratoraktion oder eine
MemoryConfig.autoSummarize-Integration auf, wenn Sie eine fortlaufende Komprimierung
benötigen. Der Datenverlust ist unumkehrbar: Der ursprüngliche Text wird überschrieben.
REST-API
Alle Endpunkte erfordern eine Management-Authentifizierung (requireManagementAuth).
Zentrale Speicherendpunkte (vorhanden + aktualisiert)
| Methode | Pfad | Beschreibung |
|---|---|---|
GET |
/api/memory |
Paginierte Liste mit Filtern: apiKeyId, type, sessionId, q, limit, page, offset. Die Antwort enthält stats.total, stats.tokensUsed, stats.hitRate, cacheStats |
POST |
/api/memory |
Erstellt einen Eintrag (Zod-validiert: content, key, optional type, sessionId, apiKeyId, metadata, expiresAt). Ruft createMemory() auf, das für (apiKeyId, key) ein Upsert durchführt |
GET |
/api/memory/[id] |
Ruft einen einzelnen Eintrag anhand der UUID ab |
PUT |
/api/memory/[id] |
Aktualisiert Eintragsfelder (type, key, content, metadata). Body: MemoryUpdatePutSchema. Synchronisiert außerdem den Vektor, wenn eine Embedding-Quelle verfügbar ist. |
DELETE |
/api/memory/[id] |
Löscht einen Eintrag; löscht ihn nach Möglichkeit auch aus vec_memories (D15) und Qdrant. Gibt 404 zurück, wenn er nicht vorhanden ist. |
GET |
/api/memory/health |
Führt verifyExtractionPipeline("health-check") aus — Rundlauf aus Erstellen→Auflisten→Löschen. Gibt {working, latencyMs, error?} zurück |
Neue Endpunkte der Speicher-Engine (Plan 21)
| Methode | Pfad | Beschreibung |
|---|---|---|
POST |
/api/memory/retrieve-preview |
Probelauf von retrieveMemories — gibt sortierte Ergebnisse mit Bewertung, Stufe und Token zurück. Body: RetrievePreviewSchema. Fügt KEINE Erinnerungen ein und verändert sie nicht. |
GET |
/api/memory/embedding-providers |
Listet Anbieter mit Embedding-Modellen auf und gibt an, für welche ein API-Schlüssel konfiguriert ist. |
GET |
/api/memory/engine-status |
Gibt den vollständigen Engine-Status zurück: Schlüsselwortstufe, Embedding-Auflösung, Vektorspeicherstatistiken, Qdrant-Status, Rerank-Konfiguration. Struktur: MemoryEngineStatusSchema. |
POST |
/api/memory/summarize |
Löst die Speicherkomprimierung manuell aus. Body: MemorySummarizeSchema (olderThanDays, apiKeyId?, dryRun). Gibt {candidates, tokensSaved} zurück. |
POST |
/api/memory/reindex |
Löst die Vektorneuindizierung für Erinnerungen mit needs_reindex=1 aus. Body: MemoryReindexSchema (force). Gibt {started, pending} zurück. |
Einstellungsendpunkte
| Methode | Pfad | Beschreibung |
|---|---|---|
GET |
/api/settings/memory |
Aktuelle normalisierte MemorySettingsExtended (7 neue Felder + Legacy-Felder) |
PUT |
/api/settings/memory |
Aktualisiert ein beliebiges Feld aus MemorySettingsExtendedSchema (insgesamt 12 Felder) |
GET |
/api/settings/qdrant |
Aktuelle Qdrant-Einstellungen (QdrantSettingsSchema) |
PUT |
/api/settings/qdrant |
Aktualisiert die Qdrant-Einstellungen. Body: QdrantSettingsUpdateSchema. apiKey = eine leere Zeichenfolge entfernt den Schlüssel. |
GET |
/api/settings/qdrant/health |
Verfügbarkeitsprüfung der konfigurierten Qdrant-Instanz. Gibt QdrantHealthResultSchema zurück. |
POST |
/api/settings/qdrant/search |
Test der semantischen Suche in Qdrant. Body: QdrantSearchSchema (query, topK). |
POST |
/api/settings/qdrant/cleanup |
Entfernt Qdrant-Punkte für abgelaufene / alte Erinnerungen. |
GET |
/api/settings/qdrant/embedding-models |
Listet die für Qdrant verfügbaren Embedding-Modelle auf. |
Die Listenabfrage von /api/memory unterstützt entweder eine page-basierte
Paginierung (parsePaginationParams) oder ein direktes offset — wenn offset
vorhanden ist, hat es Vorrang, und für die Antwortstruktur wird ein abgeleitetes
page berechnet.
MCP-Tools (open-sse/mcp-server/tools/memoryTools.ts)
Wenn der MCP-Server aktiviert ist, werden drei Speicher-Tools registriert:
omniroute_memory_search—{apiKeyId, query?, type?, maxTokens?, limit?}→ umschließtretrieveMemories(). Seit v3.8.6 (D16) wird diestrategyausgetMemorySettings()gelesen, anstatt fest auf"exact"codiert zu sein. Wennqueryangegeben ist undstrategyden Wertsemanticoderhybridhat, wird der Vektorspeicher verwendet, sofern er verfügbar ist.omniroute_memory_add—{apiKeyId, sessionId?, type, key, content, metadata?}→ umschließtcreateMemory(). Akzeptiert nur die 4 kanonischen Typen:factual,episodic,procedural,semantic(D17).omniroute_memory_clear—{apiKeyId, type?, olderThan?}→ listet passende Einträge auf, filtert optional nach einem „erstellt vor“-Zeitstempel und löscht anschließend jeden überdeleteMemory()(wodurch auch Vektoren aus sqlite-vec + Qdrant entfernt werden).
Details zu Transport und Geltungsbereich finden Sie unter MCP-SERVER.md.
Dashboard (Memory Studio)
src/app/(dashboard)/dashboard/memory/page.tsx ist jetzt ein Studio mit 3 Tabs:
Tab: Erinnerungen
- Konzeptkarte (einklappbare Erklärung „So funktioniert es“).
- Echtzeitliste, Suche und Paginierung (Entprellung von 300 ms).
- Typfilter (
factual/episodic/procedural/semantic/ alle). - Modal zum Hinzufügen von Erinnerungen (Schlüssel, Inhalt, Typ).
- Inline-Bearbeitung (Stift-Schaltfläche →
PUT /api/memory/[id]). - Löschen pro Zeile (mit Bestätigungsdialog).
- JSON-Export der aktuellen Seite; JSON-Import über die Dateiauswahl.
- Statistikkarten:
totalEntries,tokensUsed,hitRate. - Schaltfläche „Alte komprimieren“ →
POST /api/memory/summarize(ein Probelauf zeigt zunächst die Anzahl der Kandidaten an, danach erfolgt die Bestätigung). - Ein grüner/roter Statuspunkt, der durch
GET /api/memory/healthgesteuert wird.
Tab: Playground
- Abfrageeingabe + Strategieauswahl (Exakt / Semantisch / Hybrid) + Token-Budget.
- „Simulieren“ →
POST /api/memory/retrieve-preview— zeigt bewertete Ergebnisse mitscore,tier,tokens,vecScore,ftsScore. - Auflösungsbereich, der anzeigt, welche Embedding-Quelle/welcher Vektorspeicher verwendet wurde und ob ein Fallback erfolgt ist.
Tab: Engine
- Engine-Statusbereich (Keyword-FTS5-Chip, Embedding-Chip, Vektorspeicher-Chip, Qdrant-Zustands-Chip, Rerank-Chip).
- Schaltfläche „Jetzt neu indizieren“ →
POST /api/memory/reindex. - Auswahl der Embedding-Quelle (automatisch / remote / statisch / Transformers + Umschalter).
- Qdrant-Konfigurationskarte (Aktivierungsumschalter, Host/Port/Collection/Schlüssel, Verbindung testen, semantische Suche testen, Bereinigung).
- Rerank-Konfigurationskarte (Aktivierungsumschalter, Anbieter-/Modellauswahl).
Die Speicher- und Qdrant-Einstellungen befinden sich außerdem unter
/dashboard/settings → Memory & Skills (MemorySkillsTab.tsx) als
Legacy-/globale Einstellungsoberfläche.
Caching
src/lib/memory/store.ts verwaltet einen prozessinternen LRU-ähnlichen Cache
(MEMORY_CACHE_TTL = 1 min, MEMORY_MAX_CACHE_SIZE = 500, mit Entfernung der ältesten 20 %)
für getMemory(id)-Lesezugriffe sowie eine generische Schlüssel/Wert-
memoryCache-Schicht (src/lib/memory/cache.ts) mit den Methoden get/set/invalidate,
die von Aufrufern verwendet wird, die einen eigenen Cache mit spezifischem Geltungsbereich benötigen (LRU mit 1.000 Einträgen,
Standard-TTL 5 min).
Datenschutz & Lebenszyklus
- Eigentümer des Speichers ist die API-Schlüssel-ID (
resolveMemoryOwnerIdinchatCore.ts). Ohne eineapiKeyInfo.idwerden weder Abruf noch Injektion noch Extraktion ausgeführt. - Einträge mit einem in der Zukunft liegenden
expires_atwerden beim Abruf herausgefiltert; alte Einträge, die überretentionDayshinausgehen, werden durch die Klauselcreated_at >= cutoffinretrieveMemoriesausgeschlossen. - Verwenden Sie für die endgültige Löschung
DELETE /api/memory/[id]oderomniroute_memory_clear. - Die Extraktion erfolgt nach dem Fire-and-Forget-Prinzip über
setImmediate; Fehler werden untermemory.extraction.background.failedprotokolliert und niemals an den Aufrufer weitergegeben. - Verifizierungsdurchläufe (
verifyExtractionPipeline) bereinigen ihre eigenen Testeinträge in einemfinally-Block.
Siehe auch
- SKILLS.md — die Einstellung
skillsEnabledinjiziert Werkzeugdefinitionen zusammen mit dem Speicher. - MCP-SERVER.md — MCP-Transport/Berechtigungsbereiche.
- API_REFERENCE.md — umfassendere API-Oberfläche.
- Quellmodule:
src/lib/memory/types.ts,schemas.tssrc/lib/memory/store.ts,retrieval.ts,injection.ts,reindex.tssrc/lib/memory/extraction.ts,summarization.ts,verify.tssrc/lib/memory/settings.ts,qdrant.ts,cache.tssrc/lib/memory/vectorStore.ts— sqlite-vec + hybrides RRFsrc/lib/memory/embedding/index.ts— Einbettungsschicht mit mehreren Quellensrc/lib/memory/embedding/types.ts,remote.ts,staticPotion.ts,transformersLocal.ts,cache.tssrc/shared/schemas/memory.ts— Zod-Schemas für alle Speicher-API-Anfragekörpersrc/shared/schemas/qdrant.ts— Zod-Schemas für Qdrant-Einstellungen/-Operationensrc/lib/db/memoryVec.ts— CRUD fürmemory_vec_metasrc/lib/db/migrations/015_create_memories.sql,022_add_memory_fts5.sql,023_fix_memory_fts_uuid.sql,083_memory_vec.sqlsrc/app/api/memory/route.ts,[id]/route.ts,health/route.tssrc/app/api/memory/retrieve-preview/route.tssrc/app/api/memory/engine-status/route.tssrc/app/api/memory/embedding-providers/route.tssrc/app/api/memory/summarize/route.tssrc/app/api/memory/reindex/route.tssrc/app/api/settings/memory/route.tssrc/app/api/settings/qdrant/route.ts+ Unterroutensrc/app/(dashboard)/dashboard/memory/— Studio-Benutzeroberfläche (Seite + Komponenten + Registerkarten + Hooks)open-sse/handlers/chatCore.ts(Verknüpfung von Injektion/Extraktion)open-sse/mcp-server/tools/memoryTools.ts
Auswahl eines Einbettungsanbieters (v3.8.16+)
Die Speicher-Engine von OmniRoute unterstützt vier Einbettungsquellen (src/lib/memory/embedding/). Jede bietet unterschiedliche Vor- und Nachteile hinsichtlich Latenz, Kosten, Modellqualität und Einrichtungsaufwand.
Die Einbettungsquellen
| Anbieter | Quelle | Latenz | Kosten | Qualität | Einrichtung |
|---|---|---|---|---|---|
transformers |
Lokales ONNX-Modell (Xenova/all-MiniLM-L6-v2) | ~50-150ms (CPU) | Kostenlos | Gut | Nur npm install |
static |
Vorberechnete Vektoren (zwischengespeichert) | <1ms | Kostenlos | N. z. (abhängig vom Cache-Treffer) | Keine |
remote |
OpenAI-/Cohere-/Voyage-API | ~100-300ms | $0.02-0.10/1M Token | Ausgezeichnet | API-Schlüssel |
auto |
Wählt zur Laufzeit die beste verfügbare Quelle aus | Wie die gewählte Quelle | Kostenlos | Wie die gewählte Quelle | Keine |
| (cache) | LRU-In-Memory-Schicht über einer beliebigen Quelle | <1ms (Treffer), volle Latenz (Fehlschlag) | Kostenlos | Wie die zugrunde liegende Quelle | Immer aktiv (keine auswählbare Quelle) |
Entscheidungsbaum
Wie sieht Ihr Bereitstellungskontext aus?
│
┌───────────┼───────────┬──────────────┐
│ │ │ │
ENTW./TEST KLEINE PROD. GROSSE PROD. EDGE/OFFLINE
│ │ │ │
▼ ▼ ▼ ▼
transformers transformers remote (Qdrant) transformers
(kostenlos, keine API) (beste Qualität) (kein Internet)
│ │ │ │
└────────┬──┴───────────┴──────────────┘
│
▼
IMMER die `cache`-Schicht darüber hinzufügen
(`LruCache` umschließt jeden Anbieter)
Datenbank- und API-Konfiguration
Optionen für Speichereinbettungen werden über die Einstellungs-API/-Benutzeroberfläche und nicht über Umgebungsvariablen konfiguriert. Die relevanten Datenbankschlüssel unter „Einstellungen“ (normalizeMemorySettings in src/lib/memory/settings.ts) sind:
memoryEmbeddingSource:"transformers"(lokal),"remote"(API-basiert, z. B. OpenAI),"static"(externer Speicher) oder"auto"memoryEmbeddingProviderModel: Modellkennung für Remote-/statische Quellen (z. B."text-embedding-3-small")memoryTransformersEnabled:true|falsememoryStaticEnabled:true|falsememoryVectorStore:"sqlite-vec","qdrant"oder"auto"
Lokales Modell (transformers)
Verwendet intern transformers.js, um lokale Modelle auszuführen:
# Im Code gelesene Umgebungsvariablen (src/lib/memory/embedding/index.ts):
MEMORY_TRANSFORMERS_MODEL=Xenova/all-MiniLM-L6-v2 # HF-Modell-Repository
MEMORY_STATIC_MODEL=minishlab/potion-base-8M # Statisches HF-Potion-Modell
MEMORY_STATIC_CACHE_DIR=<DATA_DIR>/embeddings # Cache-Verzeichnis
LRU-Einbettungscache
Der Cache ist standardmäßig immer aktiviert und wird über Umgebungsvariablen konfiguriert:
MEMORY_EMBEDDING_CACHE_MAX=1000 # Maximale Anzahl zwischengespeicherter Elemente
MEMORY_EMBEDDING_CACHE_TTL_MS=300000 # TTL (5 Min.)
Leistungswerte
Benchmark auf einem typischen x86-Server mit 4 Kernen (Texte mit jeweils ~100 Tokens):
| Anbieter | p50 | p95 | p99 | Kosten / 1 Mio. Embeddings |
|---|---|---|---|---|
transformers (CPU) |
80ms | 180ms | 350ms | Kostenlos |
remote (OpenAI) |
120ms | 220ms | 400ms | ~$0.02 (ada-002) / $0.13 (3-large) |
static (Qdrant) |
15ms | 30ms | 60ms | Abhängig vom Qdrant-Hosting |
cache (Treffer) |
<1ms | <1ms | 2ms | Kostenlos |
Muster zur Faktenextraktion (v3.8.16+)
Das Modul extraction.ts (src/lib/memory/extraction.ts) verwendet Musterabgleich mit regulären Ausdrücken, um strukturierte Fakten aus Konversationsnachrichten zu extrahieren. Das Verständnis dieser Muster hilft Ihnen, die Extraktionsqualität für Ihren Anwendungsfall zu optimieren.
Standardmäßige Musterkategorien
| Kategorie | Beispielmuster | Erfasst |
|---|---|---|
| PREFERENCE_PATTERNS | "Ich bevorzuge <X>", "Ich mag <X>", "Ich hasse <X>" |
Benutzerpräferenzen |
| DECISION_PATTERNS | "Ich werde <X> verwenden", "Ich habe mich für <X> entschieden" |
Benutzerentscheidungen (episodisch) |
| PATTERN_PATTERNS | "Ich mache normalerweise <X>", "Ich mache immer <X>", "Ich mache nie <X>" |
Dauerhafte Verhaltensmuster |
Beispielmuster (vereinfacht)
// Aus 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];
Was extrahiert wird
Wenn ein Benutzer Folgendes sagt:
„Ich bevorzuge TypeScript. Ich werde Postgres für dieses Projekt verwenden. Ich committe immer vor dem Pushen. Ich mag Python nicht.“ Die Extraktion erzeugt 4 Erinnerungen:
Schlüssel Kategorie Typ Inhalt preference:typescriptPräferenz faktisch "TypeScript" decision:postgres_for_this_projectEntscheidung episodisch "Postgres für dieses Projekt" pattern:commit_before_pushingMuster faktisch "vor dem Pushen committen" preference:pythonPräferenz faktisch "Python"
Extraktionsgrenzen
Um eine unkontrollierte Extraktion zu verhindern, gelten die folgenden Grenzen:
| Mindestlänge des Inhalts | 3 Zeichen | | Maximallänge des Inhalts | 500 Zeichen |
Wann die Extraktion deaktiviert werden sollte
Die Extraktion wird automatisch ausgeführt, sobald der Speicher aktiviert ist; es gibt keinen separaten
Schalter nur für die Extraktion. Um sie zu deaktivieren, deaktivieren Sie den Speicher vollständig (enabled: false
über PUT /api/settings/memory). Dies kann in folgenden Fällen sinnvoll sein:
- Sie haben ein hohes Nachrichtenvolumen und die Extraktionskosten sind nicht unerheblich
- Ihre Konversationen sind überwiegend temporär (Chat, Debugging) und haben keinen langfristigen Wert
- Sie erfassen den Kontext bereits über benutzerdefinierte Plugins
Optimierung von Hybrid-RRF (v3.8.16+)
Der Algorithmus Reciprocal Rank Fusion (RRF) kombiniert Ergebnisse aus FTS5 (Schlüsselwörter) und Vektorsuche (Semantik). Der Parameter k steuert, wie stark niedriger eingestufte Ergebnisse gewichtet werden.
Die Formel
Für jede infrage kommende Erinnerung lautet der RRF-Score:
RRF(d) = Σ 1 / (k + rank_i(d))
Dabei gilt:
kist die Konstante (Standardwert 60)rank_i(d)ist der Rang des Dokumentsdim i-ten Abrufsystem (FTS, Vektor)- Die Summe erstreckt sich über alle Abrufsysteme
Auswirkungen von k auf die Ergebnisse
k-Wert |
Auswirkung | Am besten geeignet für |
|---|---|---|
k=0 |
Reine Rangfusion (keine Glättung) | Theoretische Basislinie |
k=10-30 |
Gewichtet die besten Ergebnisse stark; niedrige Ränge tragen kaum bei | Wenn die Top-3-Ergebnisse meist korrekt sind |
k=60 (Standard) |
Ausgewogen — alle Top-10-Ergebnisse tragen wesentlich bei | Allgemeine Suche |
k=100+ |
Flacher — selbst Ergebnisse mit niedrigem Rang können dominieren, wenn sie in mehreren Systemen erscheinen | Wenn Trefferquote > Präzision entscheidend ist |
k in der Praxis optimieren
# Standardwert
MEMORY_RRF_K=60
# Aggressive Präzision (kleiner Speicher, wenige Dokumente)
MEMORY_RRF_K=20
# Maximale Trefferquote (großer Speicher, vielfältige Abfragen)
MEMORY_RRF_K=120
Beispiel mit k=20:
- FTS-Rang 1 → Beitrag
1/21 = 0.048 - FTS-Rang 10 → Beitrag
1/30 = 0.033 - Vektorrang 1 → Beitrag
0.048 - Kombiniertes Maximum:
0.096
Beispiel mit k=60:
- FTS-Rang 1 → Beitrag
1/61 = 0.016 - FTS-Rang 10 → Beitrag
1/70 = 0.014 - Vektorrang 1 → Beitrag
0.016 - Kombiniertes Maximum:
0.033
Bei einem höheren k ist der relative Unterschied zwischen Rang 1 und Rang 10 kleiner, sodass sich der Algorithmus stärker auf den Konsens zwischen den Abrufsystemen als auf die Konfidenz des höchsten Rangs stützt.
Wann k geändert werden sollte
| Symptom | Versuch |
|---|---|
| Das Top-Ergebnis gewinnt immer, ist aber falsch | k senken (z. B. 20) — die Konfidenz des höchsten Rangs zählt stärker |
| Die richtige Antwort ist in den Top 5, aber nicht auf Rang 1 | k erhöhen (z. B. 100) — eine flachere Bewertung belohnt Konsens |
| Die Trefferquote ist hoch, aber die Präzision niedrig | k senken — die Rangfolge schärfen |
| Die Trefferquote ist niedrig (relevante Dokumente fehlen) | k erhöhen — niedriger eingestuften Dokumenten eine Chance geben |
RRF-Gewichtung
Die Reciprocal Rank Fusion verwendet gleiche Gewichtungen für den Rang der semantischen Vektorsuche und den Rang der Volltextsuche:
RRF(d) = 1/(k + rank_vector) + 1/(k + rank_fts)
Es gibt keine Umgebungsvariablen, mit denen sich die einzelnen Gewichtungen anpassen lassen (MEMORY_RRF_VECTOR_WEIGHT/MEMORY_RRF_FTS_WEIGHT existieren nicht).
Zusammenfassungsstrategie (v3.8.16+)
Das Modul summarization.ts (src/lib/memory/summarization.ts) komprimiert ältere Erinnerungen, um die aktive Menge klein zu halten und gleichzeitig die Abrufbarkeit zu bewahren.
Wann die Zusammenfassung ausgelöst wird
| Auslöser | Schwellenwert (Standard) |
|---|---|
| Manuelle Auslösung über die API | k. A. |
Was zusammengefasst wird
Aus summarization.ts werden zwei Einstiegspunkte exportiert:
summarizeMemories(apiKeyId, sessionId?, maxTokens = 4000)— verdichtet die Erinnerungen einer Sitzung zu einem einzigen Zusammenfassungstext, der durch ein Token-Budget begrenzt ist.summarizeMemoriesOlderThan(apiKeyId, days, dryRun)— die von der API verwendete altersbasierte Komprimierung: Sie wählt jede Erinnerung aus, die älter alsdaysist, erstellt daraus eine einzige verdichtete Zusammenfassungserinnerung und löscht (wenndryRunden Wertfalsehat) die Originale. Übergeben SiedryRun: true, um eine Vorschau der Kandidatenmenge und der gesamten Token-Anzahl anzuzeigen, ohne Änderungen vorzunehmen.
Es gibt weder einen Clustering-Durchlauf nach Tags/Schlüsseln noch eine Bewertung einzelner Erinnerungen als „zentral“ oder „zusammenfassbar“ — die Auswahl basiert ausschließlich auf dem Altersgrenzwert, und der Zusammenfassungstext besteht aus einer verdichteten, mit dem Typ präfixierten Zeile pro Kandidat.
Zusammenfassung auslösen
Die Zusammenfassung ist manuell / optional — die Einstellung autoSummarize ist
standardmäßig false, sodass nichts automatisch komprimiert wird. Lösen Sie sie über die API aus:
curl -X POST http://localhost:20128/api/memory/summarize \
-H "Authorization: Bearer $OMNIROUTE_KEY"
Um sie deaktiviert zu lassen, belassen Sie autoSummarize einfach auf dem Standardwert (false).
Tipps zur Qualität der Zusammenfassung
- Zeigen Sie zuerst mit
dryRuneine Vorschau an —summarizeMemoriesOlderThan(..., true)gibt die Kandidatenliste und die gesamte Token-Anzahl zurück, sodass Sie vor dem Löschen der Originale überprüfen können, was zusammengeführt würde. - Führen Sie die Zusammenfassung in Zeiten mit geringem Datenverkehr aus, wenn Sie über einen großen Erinnerungsbestand verfügen — der LLM-Aufruf ist der langsame Teil
# Cron-Stil: täglich um 3 Uhr zusammenfassen
0 3 * * * curl -X POST http://localhost:20128/api/memory/summarize \
-H "Authorization: Bearer $OMNIROUTE_KEY"
MemoryBackend-Provider-Muster
Maßgebliche Quelle:
src/lib/memory/backend.ts,src/lib/memory/genericBackend.ts,src/lib/memory/manager.tsTests:src/lib/memory/__tests__/generic-backend.test.ts
Das MemoryBackend-Provider-Muster führt eine austauschbare Backend-Abstraktionsschicht über der bestehenden Memory-Engine ein. Statt an eine einzige Speicherimplementierung gebunden zu sein, unterstützt das Memory-System nun mehrere Backends (SQLite, Obsidian, Notion, benutzerdefinierte HTTP-Backends) mit konfigurierbarem Primär-/Fallback-Routing.
Architektur
┌──────────────────────────────────────────────────────────┐
│ API-Routen │
│ (src/app/api/memory/route.ts) │
└──────────────────────┬───────────────────────────────────┘
│
┌──────────────────────▼───────────────────────────────────┐
│ MemoryManager │
│ Singleton-Orchestrator (manager.ts) │
│ │
│ Primär ──► Backend A (z. B. SQLite) │
│ Fallback ──► Backend B (z. B. Obsidian) │
│ Backend C (z. B. Notion via GenericBackend)│
└──────────────────────┬───────────────────────────────────┘
│
┌──────────────┼──────────────┐
▼ ▼ ▼
┌────────────┐ ┌────────────┐ ┌──────────────────┐
│ SQLite- │ │ Obsidian- │ │ GenericMemory- │
│ Backend │ │ Backend │ │ Backend (HTTP) │
└────────────┘ └────────────┘ └──────────────────┘
Kernschnittstelle (backend.ts)
Jedes Backend muss die Schnittstelle MemoryBackend implementieren:
interface MemoryBackend {
readonly id: string;
readonly displayName: string;
// CRUD-Operationen
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> }>;
// Suche
search(config: SearchConfig): Promise<Memory[]>;
// Zustandsprüfung
health(): Promise<HealthCheckResult>;
// Lebenszyklus (optional)
initialize?(): Promise<void>;
shutdown?(): Promise<void>;
}
MemoryManager (manager.ts)
Singleton-Orchestrator, der:
- Backends über
register(backend)registriert — wird beim Start ausindex.tsaufgerufen - Primär- und Fallback-Backends über
configure(primary, fallbacks)konfiguriert - CRUD-Operationen und Suchanfragen an das primäre Backend weiterleitet, bei Fehlern über die Fallback-Kette
- regelmäßig Zustandsprüfungen für alle Backends durchführt
Fallback-Verhalten:
| Operation | Primär | Fallbacks |
|---|---|---|
create |
✅ Nur primäres Backend | ❌ |
get |
✅ Zuerst primäres versuchen | ✅ Fallback, falls null |
update |
✅ Nur primäres Backend | ✅ Asynchrone Synchronisierung |
delete |
✅ Nur primäres Backend | ✅ Asynchrone Synchronisierung |
list |
✅ Nur primäres Backend | ❌ |
search |
✅ Zuerst primäres Backend | ✅ Fallback bei Fehler |
GenericMemoryBackend (genericBackend.ts)
Ein generischer HTTP-Konnektor, der jede REST-API an ein MemoryBackend anpasst. Nützlich für:
- Notion — Verbindung über die Notion API
- Obsidian — Verbindung über die Obsidian Local REST API
- Benutzerdefinierte Backends — jeder Dienst, der eine RESTful Memory-API bereitstellt
Konfiguration:
interface GenericBackendConfig {
baseUrl: string; // Basis-URL der Backend-API
apiKey?: string; // Bearer-Token für die Authentifizierung
headers?: Record<string, string>; // Benutzerdefinierte HTTP-Header
timeout?: number; // Anfrage-Timeout (Standard: 30000ms)
backendType?: string; // Für die Protokollierung
// Überschreibungen für Endpunkte (Standardwerte folgen REST-Konventionen)
endpoints?: {
search?: string; // Standard: "/memories/search"
create?: string; // Standard: "/memories"
list?: string; // Standard: "/memories"
get?: string; // Standard: "/memories/{id}"
update?: string; // Standard: "/memories/{id}"
delete?: string; // Standard: "/memories/{id}"
health?: string; // Standard: "/health"
};
// Zuordnungen von Abfrageparameternamen
queryParams?: {
query?/apiKeyId?/limit?/offset?/strategy?/maxTokens?/type?/sessionId?/orderBy?/orderDir?/options?
};
// Zuordnungen von Pfadparameternamen
pathParams?: {
id?/memoryId?
};
}
Bekannte Backends sind in KNOWN_BACKENDS vorkonfiguriert:
createKnownBackend("obsidian"); // → GenericMemoryBackend verweist auf localhost:27123
createKnownBackend("notion"); // → GenericMemoryBackend verweist auf api.notion.com/v1
Integrierte Backends
SQLiteBackend (sqliteBackend.ts)
Das standardmäßige primäre Backend. Kapselt den vorhandenen SQLite-basierten Speicher unter Verwendung von src/lib/memory/store.ts. Wird beim Start automatisch registriert.
import { sqliteBackend } from "./sqliteBackend";
memoryManager.register(sqliteBackend);
ObsidianBackend (obsidianBackend.ts)
Kapselt die vorhandene Obsidian-Integration (src/lib/memory/obsidianBackend.ts). Stellt über die Obsidian Local REST API eine Verbindung zu einem Obsidian-Vault her.
Einstellungen
Die Einstellungen für Speicher-Backends werden in der App-Einstellungstabelle gespeichert und über src/lib/memory/settings.ts verwaltet:
| Einstellung | Umgebungs-/Konfigurationsschlüssel | Standard | Beschreibung |
|---|---|---|---|
| Primäres Backend | memoryPrimaryBackend |
"sqlite" |
ID des primären Backends |
| Fallback-Backends | memoryFallbackBackends |
[] |
Geordnete IDs der Fallback-Backends |
| Backend-Konfigurationen | memoryBackendConfigs |
{} |
Konfigurationsüberschreibungen je Backend |
Die Einstellungen werden über normalizeMemorySettings() normalisiert und bei getMemorySettings() zwischengespeichert.
Initialisierungsablauf
App-Bootstrap
→ index.ts-Importe (Nebeneffekt): registrieren SQLiteBackend
→ initMemoryBackends() wird aus dem App-Lebenszyklus aufgerufen:
1. Einstellungen laden (getMemorySettings)
2. Primäres Backend und Fallbacks konfigurieren
3. Alle Backends initialisieren (Integritätsprüfung)
4. Bereit für Anfragen
Hinzufügen eines neuen Backends
MemoryBackend-Schnittstelle implementieren insrc/lib/memory/<name>Backend.ts- Exportieren aus
src/lib/memory/index.ts - Registrieren mit
memoryManager.register(yourBackend)beim Start - Konfigurieren über die Einstellungen:
memoryPrimaryBackendauf die ID Ihres Backends setzen - Testen mit
src/lib/memory/__tests__/generic-backend.test.tsals Referenz
Beispiel: Brain-Backend
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);
Verifizierung
Unit-Tests
npx vitest run src/lib/memory/__tests__/generic-backend.test.ts --reporter=verbose
Erwartete Ausgabe: 35 Tests, alle erfolgreich, mit Abdeckung für:
- Konstruktor (2)
- Integritätsprüfung (4) — Erfolg, Fehler 500, Netzwerkfehler, Latenz
- Initialisierung (2) — Erfolg, Fehler
- Erstellen (2) — Standardendpunkt, benutzerdefinierter Endpunkt
- Abrufen (4) — Erfolg, 404 → null, Ausnahme bei anderem Status als 404, benutzerdefinierte Pfadparameter
- Aktualisieren (2) — Erfolg, 404 → false
- Löschen (2) — Erfolg, 404 → false
- Auflisten (2) — Abfrageparameter, benutzerdefinierte Parameternamen
- Suchen (3) — Abfrageparameter, benutzerdefinierter Endpunkt, Serialisierung von Optionen
- Authentifizierungs-Header (2) — Bearer-Token, benutzerdefinierte Header
- Factory (1)
Typprüfung
npm run typecheck:core
Erwartet: 0 Fehler.