Zum Inhalt springen
OmniRoute source

Memory System (Deutsch)

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.

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

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 | false
  • memoryStaticEnabled: true | false
  • memoryVectorStore: "sqlite-vec", "qdrant" oder "auto"

Verwendet intern transformers.js, um lokale Modelle auszuführen:

Terminal-Fenster
# 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

Der Cache ist standardmäßig immer aktiviert und wird über Umgebungsvariablen konfiguriert:

Terminal-Fenster
MEMORY_EMBEDDING_CACHE_MAX=1000 # Maximale Anzahl zwischengespeicherter Elemente
MEMORY_EMBEDDING_CACHE_TTL_MS=300000 # TTL (5 Min.)

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

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.

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
// 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];

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:typescript Präferenz faktisch “TypeScript”
decision:postgres_for_this_project Entscheidung episodisch “Postgres für dieses Projekt”
pattern:commit_before_pushing Muster faktisch “vor dem Pushen committen”
preference:python Präferenz faktisch “Python”

Um eine unkontrollierte Extraktion zu verhindern, gelten die folgenden Grenzen:

| Mindestlänge des Inhalts | 3 Zeichen | | Maximallänge des Inhalts | 500 Zeichen |

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

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.

Für jede infrage kommende Erinnerung lautet der RRF-Score:

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

Dabei gilt:

  • k ist die Konstante (Standardwert 60)
  • rank_i(d) ist der Rang des Dokuments d im i-ten Abrufsystem (FTS, Vektor)
  • Die Summe erstreckt sich über alle Abrufsysteme
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
Terminal-Fenster
# 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.

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

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).


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.

Auslöser Schwellenwert (Standard)
Manuelle Auslösung über die API k. A.

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 als days ist, erstellt daraus eine einzige verdichtete Zusammenfassungserinnerung und löscht (wenn dryRun den Wert false hat) die Originale. Übergeben Sie dryRun: 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.

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:

Terminal-Fenster
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).

  • Zeigen Sie zuerst mit dryRun eine 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
Terminal-Fenster
# Cron-Stil: täglich um 3 Uhr zusammenfassen
0 3 * * * curl -X POST http://localhost:20128/api/memory/summarize \
-H "Authorization: Bearer $OMNIROUTE_KEY"

Maßgebliche Quelle: src/lib/memory/backend.ts, src/lib/memory/genericBackend.ts, src/lib/memory/manager.ts Tests: 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.

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

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&lt;boolean&gt;;
delete(id: string): Promise&lt;boolean&gt;;
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&lt;void&gt;;
shutdown?(): Promise&lt;void&gt;;
}

Singleton-Orchestrator, der:

  • Backends über register(backend) registriert — wird beim Start aus index.ts aufgerufen
  • 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

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

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

Kapselt die vorhandene Obsidian-Integration (src/lib/memory/obsidianBackend.ts). Stellt über die Obsidian Local REST API eine Verbindung zu einem Obsidian-Vault her.

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.

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
  1. MemoryBackend-Schnittstelle implementieren in src/lib/memory/&lt;name&gt;Backend.ts
  2. Exportieren aus src/lib/memory/index.ts
  3. Registrieren mit memoryManager.register(yourBackend) beim Start
  4. Konfigurieren über die Einstellungen: memoryPrimaryBackend auf die ID Ihres Backends setzen
  5. Testen mit src/lib/memory/__tests__/generic-backend.test.ts als Referenz
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);
Terminal-Fenster
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)
Terminal-Fenster
npm run typecheck:core

Erwartet: 0 Fehler.


OmniRoute-Quellcode (a58000c7685f)

HagiCode

HagiCode ist ein agentischer Coding-Arbeitsplatz mit strukturierten Workflows, Multi-Agent-Ausführung und Hero-Dungeon-Ansichten.

Mit einem intelligenteren, schnelleren und unterhaltsameren agentischen Workflow wird aus Ideen nutzbare Software.

HagiCode-Hauptoberfläche im hellen Design
  • SmartStrukturierte Workflows machen aus Absichten einen umsetzbaren Weg von der Idee bis zur Auslieferung.
  • EfficientMulti-Agent-Workflows führen Recherche, Umsetzung und Prüfung parallel aus.
  • FunHero Dungeon macht lange Coding-Sitzungen anschaulich und gemeinschaftlich.
HagiCode besuchen