Ir al contenido
OmniRoute source

Memory System (Español)

Elección de un proveedor de embeddings (v3.8.16+)

Sección titulada «Elección de un proveedor de embeddings (v3.8.16+)»

El motor de memoria de OmniRoute admite cuatro fuentes de embeddings (src/lib/memory/embedding/). Cada una presenta distintas ventajas y desventajas en cuanto a latencia, coste, calidad del modelo y complejidad de configuración.

Proveedor Fuente Latencia Coste Calidad Configuración
transformers Modelo ONNX local (Xenova/all-MiniLM-L6-v2) ~50-150ms (CPU) Gratis Buena Solo npm install
static Vectores precalculados (en caché) <1ms Gratis N/D (depende del acierto de caché) Ninguna
remote API de OpenAI / Cohere / Voyage ~100-300ms $0.02-0.10/1M tokens Excelente Clave de API
auto Selecciona la mejor fuente disponible durante la ejecución Igual que la fuente seleccionada Gratis Igual que la fuente seleccionada Ninguna
(caché) Capa LRU en memoria sobre cualquier fuente <1ms (acierto), latencia completa (fallo) Gratis Igual que la subyacente Siempre activa (no es una fuente seleccionable)
¿Cuál es el contexto de su despliegue?
│
┌───────────┼───────────┬──────────────┐
│ │ │ │
DESARROLLO/ PRODUCCIÓN PRODUCCIÓN PERÍMETRO /
PRUEBAS PEQUEÑA GRANDE SIN CONEXIÓN
│ │ │ │
▼ ▼ ▼ ▼
transformers transformers remote (Qdrant) transformers
(gratis, sin API) (mejor calidad) (sin internet)
│ │ │ │
└────────┬──┴───────────┴──────────────┘
│
▼
AÑADA SIEMPRE la capa `cache` encima
(LruCache envuelve cualquier proveedor)

Las opciones de embeddings de memoria se configuran mediante la API/interfaz de Configuración, no mediante variables de entorno. Las claves pertinentes de la base de datos de configuración en Configuración (normalizeMemorySettings en src/lib/memory/settings.ts) son:

  • memoryEmbeddingSource: "transformers" (local), "remote" (basada en API, p. ej., OpenAI), "static" (almacén externo) o "auto"
  • memoryEmbeddingProviderModel: identificador del modelo para fuentes remotas/estáticas (p. ej., "text-embedding-3-small")
  • memoryTransformersEnabled: true | false
  • memoryStaticEnabled: true | false
  • memoryVectorStore: "sqlite-vec", "qdrant" o "auto"

Utiliza transformers.js internamente para ejecutar modelos locales:

Ventana de terminal
# Variables de entorno leídas en el código (src/lib/memory/embedding/index.ts):
MEMORY_TRANSFORMERS_MODEL=Xenova/all-MiniLM-L6-v2 # Repositorio del modelo en HF
MEMORY_STATIC_MODEL=minishlab/potion-base-8M # Modelo estático Potion de HF
MEMORY_STATIC_CACHE_DIR=<DATA_DIR>/embeddings # Directorio de caché

La caché está siempre activa de forma predeterminada y se configura mediante variables de entorno:

Ventana de terminal
MEMORY_EMBEDDING_CACHE_MAX=1000 # Número máximo de elementos en caché
MEMORY_EMBEDDING_CACHE_TTL_MS=300000 # TTL (5 min)

Benchmark en un servidor x86 típico de 4 núcleos (textos de ~100 tokens cada uno):

Proveedor p50 p95 p99 Coste / 1 millón de embeddings
transformers (CPU) 80ms 180ms 350ms Gratis
remote (OpenAI) 120ms 220ms 400ms ~$0.02 (ada-002) / $0.13 (3-large)
static (Qdrant) 15ms 30ms 60ms Depende del alojamiento de Qdrant
cache (acierto) <1ms <1ms 2ms Gratis

Patrones de extracción de hechos (v3.8.16+)

Sección titulada «Patrones de extracción de hechos (v3.8.16+)»

El módulo extraction.ts (src/lib/memory/extraction.ts) utiliza coincidencia de patrones mediante expresiones regulares para extraer hechos estructurados de los mensajes de las conversaciones. Comprender estos patrones le ayudará a ajustar la calidad de la extracción para su caso de uso.

Categoría Patrón de ejemplo Captura
PREFERENCE_PATTERNS "Prefiero <X>", "Me gusta <X>", "Odio <X>" Preferencias del usuario
DECISION_PATTERNS "Usaré <X>", "Decidí <X>", "Elegí <X>" Decisiones del usuario (episódicas)
PATTERN_PATTERNS "Normalmente <X>", "Siempre <X>", "Nunca <X>" Patrones de comportamiento persistentes
// De 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];

Cuando un usuario dice:

“Prefiero TypeScript. Usaré Postgres para este proyecto. Siempre hago commit antes de hacer push. No me gusta Python.” La extracción produce 4 recuerdos:

Clave Categoría Tipo Contenido
preference:typescript preference factual “TypeScript”
decision:postgres_for_this_project decision episodic “Postgres para este proyecto”
pattern:commit_before_pushing pattern factual “hacer commit antes de hacer push”
preference:python preference factual “Python”

Para evitar una extracción descontrolada, se aplican los siguientes límites:

| Longitud mínima del contenido | 3 caracteres | | Longitud máxima del contenido | 500 caracteres |

La extracción se ejecuta automáticamente siempre que la memoria esté activada; no existe un interruptor independiente exclusivo para la extracción. Para desactivarla, desactive la memoria por completo (enabled: false mediante PUT /api/settings/memory). Considere hacerlo cuando:

  • Tenga un gran volumen de mensajes y el coste de extracción no sea trivial
  • Sus conversaciones sean principalmente transitorias (chat, depuración) y no tengan valor a largo plazo
  • Ya esté capturando el contexto mediante plugins personalizados

El algoritmo Reciprocal Rank Fusion (RRF) combina resultados de FTS5 (palabras clave) y vectoriales (semánticos). El parámetro k controla cuánto peso se asigna a los resultados con una posición inferior.

Para cada recuerdo candidato, la puntuación RRF es:

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

Donde:

  • k es la constante (60 de forma predeterminada)
  • rank_i(d) es la posición del documento d en el i-ésimo sistema de recuperación (FTS, vectorial)
  • La suma se realiza sobre todos los sistemas de recuperación
Valor de k Efecto Ideal para
k=0 Fusión pura de posiciones (sin suavizado) Referencia teórica
k=10-30 Da mucho peso a los primeros resultados; las posiciones bajas apenas contribuyen Cuando los 3 primeros resultados suelen ser correctos
k=60 (predeterminado) Equilibrado: los 10 primeros resultados contribuyen de forma significativa Recuperación de propósito general
k=100+ Más uniforme: incluso los resultados con posiciones bajas pueden dominar si aparecen en varios sistemas Cuando la exhaustividad > precisión es fundamental
Ventana de terminal
# Valor predeterminado
MEMORY_RRF_K=60
# Precisión agresiva (memoria pequeña, pocos documentos)
MEMORY_RRF_K=20
# Máxima exhaustividad (memoria grande, consultas variadas)
MEMORY_RRF_K=120

Ejemplo con k=20:

  • Posición FTS 1 → contribución 1/21 = 0.048
  • Posición FTS 10 → contribución 1/30 = 0.033
  • Posición vectorial 1 → contribución 0.048
  • Máximo combinado: 0.096

Ejemplo con k=60:

  • Posición FTS 1 → contribución 1/61 = 0.016
  • Posición FTS 10 → contribución 1/70 = 0.014
  • Posición vectorial 1 → contribución 0.016
  • Máximo combinado: 0.033

Con un valor de k más alto, la diferencia relativa entre la primera y la décima posición es menor, por lo que el algoritmo depende más del consenso entre los sistemas de recuperación que de la confianza en la primera posición.

Síntoma Pruebe
El primer resultado siempre gana, pero es incorrecto Un valor de k más bajo (p. ej., 20): la confianza en la primera posición importa más
La respuesta correcta está entre las 5 primeras, pero no es la primera Un valor de k más alto (p. ej., 100): una puntuación más uniforme recompensa el consenso
La exhaustividad es alta, pero la precisión es baja Un valor de k más bajo: haga más nítida la clasificación
La exhaustividad es baja (faltan documentos relevantes) Un valor de k más alto: dé una oportunidad a los documentos con posiciones inferiores

La fusión recíproca de posiciones utiliza pesos iguales para la posición vectorial semántica y la posición de búsqueda de texto completo:

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

No existen variables de entorno para ajustar los pesos individuales (MEMORY_RRF_VECTOR_WEIGHT/MEMORY_RRF_FTS_WEIGHT no existen).


El módulo summarization.ts (src/lib/memory/summarization.ts) comprime los recuerdos más antiguos para mantener pequeño el conjunto activo y, al mismo tiempo, preservar la capacidad de recuperación.

Activador Umbral (predeterminado)
Activación manual mediante API no aplica

Desde summarization.ts se exportan dos puntos de entrada:

  • summarizeMemories(apiKeyId, sessionId?, maxTokens = 4000) — condensa los recuerdos de una sesión en un único texto de resumen limitado por un presupuesto de tokens.
  • summarizeMemoriesOlderThan(apiKeyId, days, dryRun) — la compactación basada en la antigüedad utilizada por la API: selecciona todos los recuerdos anteriores a days, crea a partir de ellos un único recuerdo de resumen condensado y (cuando dryRun es false) elimina los originales. Pasa dryRun: true para previsualizar el conjunto candidato y el total de tokens sin modificar nada.

No hay ninguna pasada de agrupación por etiqueta/clave ni puntuación por recuerdo de “principal frente a resumible”: la selección se basa exclusivamente en el límite de antigüedad, y el texto del resumen es una línea condensada con el tipo como prefijo para cada candidato.

El resumen es manual / opcional: la configuración autoSummarize es false de forma predeterminada, por lo que nada se compacta automáticamente. Actívalo mediante la API:

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

Para mantenerlo desactivado, simplemente conserva autoSummarize en su valor predeterminado (false).

Consejos para mejorar la calidad del resumen

Sección titulada «Consejos para mejorar la calidad del resumen»
  • Previsualiza primero con dryRun: summarizeMemoriesOlderThan(..., true) devuelve la lista de candidatos y el recuento total de tokens para que puedas confirmar qué se combinaría antes de eliminar los originales.
  • Ejecuta el resumen durante las horas de poco tráfico si tienes un corpus de recuerdos grande; la llamada al LLM es la parte lenta.
Ventana de terminal
# Estilo cron: resumir diariamente a las 3 a. m.
0 3 * * * curl -X POST http://localhost:20128/api/memory/summarize \
-H "Authorization: Bearer $OMNIROUTE_KEY"

Fuente de referencia: src/lib/memory/backend.ts, src/lib/memory/genericBackend.ts, src/lib/memory/manager.ts Pruebas: src/lib/memory/__tests__/generic-backend.test.ts

El patrón de proveedor MemoryBackend introduce una capa de abstracción de backend conectable sobre el motor de memoria existente. En lugar de estar vinculado a una única implementación de almacenamiento, el sistema de memoria ahora admite varios backends (SQLite, Obsidian, Notion y backends HTTP personalizados) con enrutamiento configurable de backend principal y alternativas.

┌──────────────────────────────────────────────────────────┐
│ Rutas de la API │
│ (src/app/api/memory/route.ts) │
└──────────────────────┬───────────────────────────────────┘
│
┌──────────────────────▼───────────────────────────────────┐
│ MemoryManager │
│ Orquestador singleton (manager.ts) │
│ │
│ Principal ──► Backend A (p. ej., SQLite) │
│ Alternativa ─► Backend B (p. ej., Obsidian) │
│ Backend C (p. ej., Notion mediante │
│ GenericBackend) │
└──────────────────────┬───────────────────────────────────┘
│
┌──────────────┼──────────────┐
▼ ▼ ▼
┌────────────┐ ┌────────────┐ ┌──────────────────┐
│ Backend de │ │ Backend de │ │ Backend │
│ SQLite │ │ Obsidian │ │ GenericMemory │
│ │ │ │ │ (HTTP) │
└────────────┘ └────────────┘ └──────────────────┘

Cada backend debe implementar la interfaz 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&lt;boolean&gt;;
delete(id: string): Promise&lt;boolean&gt;;
list(filter: MemoryFilter): Promise<{ data: Memory[]; total: number; byType: Record<string, number> }>;
// Búsqueda
search(config: SearchConfig): Promise<Memory[]>;
// Estado
health(): Promise<HealthCheckResult>;
// Ciclo de vida (opcional)
initialize?(): Promise&lt;void&gt;;
shutdown?(): Promise&lt;void&gt;;
}

Orquestador singleton que:

  • Registra backends mediante register(backend) — se llama durante el arranque desde index.ts
  • Configura el backend principal y las alternativas mediante configure(primary, fallbacks)
  • Enruta las operaciones CRUD y las búsquedas al backend principal, con una cadena de alternativas en caso de fallo
  • Comprueba el estado de todos los backends periódicamente

Comportamiento de las alternativas:

Operación Principal Alternativas
create ✅ Solo el principal ❌
get ✅ Probar primero el principal ✅ Alternativa si devuelve null
update ✅ Solo el principal ✅ Sincronización sin esperar respuesta
delete ✅ Solo el principal ✅ Sincronización sin esperar respuesta
list ✅ Solo el principal ❌
search ✅ Primero el principal ✅ Alternativa en caso de error

Un conector HTTP genérico que adapta cualquier API REST a un MemoryBackend. Resulta útil para:

  • Notion — conexión mediante la API de Notion
  • Obsidian — conexión mediante la API REST local de Obsidian
  • Backends personalizados — cualquier servicio que exponga una API RESTful de memoria

Configuración:

interface GenericBackendConfig {
baseUrl: string; // URL base de la API del backend
apiKey?: string; // Token Bearer para autenticación
headers?: Record<string, string>; // Encabezados HTTP personalizados
timeout?: number; // Tiempo de espera de la solicitud (predeterminado: 30000ms)
backendType?: string; // Para registros
// Sustituciones de endpoints (los valores predeterminados usan convenciones REST)
endpoints?: {
search?: string; // predeterminado: "/memories/search"
create?: string; // predeterminado: "/memories"
list?: string; // predeterminado: "/memories"
get?: string; // predeterminado: "/memories/{id}"
update?: string; // predeterminado: "/memories/{id}"
delete?: string; // predeterminado: "/memories/{id}"
health?: string; // predeterminado: "/health"
};
// Asignaciones de nombres de parámetros de consulta
queryParams?: {
query?/apiKeyId?/limit?/offset?/strategy?/maxTokens?/type?/sessionId?/orderBy?/orderDir?/options?
};
// Asignaciones de nombres de parámetros de ruta
pathParams?: {
id?/memoryId?
};
}

Los backends conocidos están preconfigurados en KNOWN_BACKENDS:

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

El backend principal predeterminado. Encapsula el almacén de memoria existente basado en SQLite mediante src/lib/memory/store.ts. Se registra automáticamente durante el arranque.

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

Encapsula la integración existente con Obsidian (src/lib/memory/obsidianBackend.ts). Se conecta a un repositorio de Obsidian mediante la API REST local de Obsidian.

La configuración de los backends de memoria se almacena en la tabla de configuración de la aplicación y se administra mediante src/lib/memory/settings.ts:

Configuración Clave de entorno/configuración Valor predeterminado Descripción
Backend principal memoryPrimaryBackend "sqlite" ID del backend principal
Backends de respaldo memoryFallbackBackends [] IDs ordenados de los backends de respaldo
Configuraciones de backend memoryBackendConfigs {} Sustituciones de configuración por backend

La configuración se normaliza mediante normalizeMemorySettings() y se almacena en caché en getMemorySettings().

Arranque de la aplicación
→ importaciones de index.ts (efecto secundario): registran SQLiteBackend
→ initMemoryBackends() llamado desde el ciclo de vida de la aplicación:
1. Cargar la configuración (getMemorySettings)
2. Configurar el backend principal y los de respaldo
3. Inicializar todos los backends (comprobación de estado)
4. Listo para recibir solicitudes
  1. Implementa la interfaz MemoryBackend en src/lib/memory/&lt;name&gt;Backend.ts
  2. Exporta desde src/lib/memory/index.ts
  3. Registra con memoryManager.register(yourBackend) durante el arranque
  4. Configura mediante la configuración: establece memoryPrimaryBackend con el ID de tu backend
  5. Prueba tomando como referencia src/lib/memory/__tests__/generic-backend.test.ts
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);
Ventana de terminal
npx vitest run src/lib/memory/__tests__/generic-backend.test.ts --reporter=verbose

Resultado esperado: 35 pruebas, todas superadas, que cubren:

  • Constructor (2)
  • Comprobación de estado (4) — éxito, error 500, error de red, latencia
  • Inicialización (2) — éxito, error
  • Creación (2) — endpoint predeterminado, endpoint personalizado
  • Obtención (4) — éxito, 404 → null, excepción distinta de 404, parámetros de ruta personalizados
  • Actualización (2) — éxito, 404 → false
  • Eliminación (2) — éxito, 404 → false
  • Listado (2) — parámetros de consulta, nombres de parámetros personalizados
  • Búsqueda (3) — parámetros de consulta, endpoint personalizado, serialización de opciones
  • Encabezados de autenticación (2) — token Bearer, encabezados personalizados
  • Fábrica (1)
Ventana de terminal
npm run typecheck:core

Resultado esperado: 0 errores.


Código fuente de OmniRoute (a58000c7685f)

HagiCode

HagiCode es un espacio de trabajo de programación con agentes, flujos estructurados, ejecución multiagente y vistas de Hero Dungeon.

Convierte ideas en software útil con un flujo de trabajo con agentes más inteligente, rápido y ameno.

Interfaz principal de HagiCode con tema claro
  • SmartLos flujos estructurados convierten la intención en un itinerario ejecutable desde la idea hasta la entrega.
  • EfficientLos flujos multiagente permiten avanzar en paralelo con la investigación, implementación y revisión.
  • FunHero Dungeon hace que las largas sesiones de programación sean visuales y colaborativas.
Visitar HagiCode