Pular para o conteúdo
OmniRoute source

Memory System (Português (Brasil))

O mecanismo de memória do OmniRoute oferece suporte a quatro fontes de embeddings (src/lib/memory/embedding/). Cada uma apresenta diferentes relações de compromisso entre latência, custo, qualidade do modelo e complexidade de configuração.

Provedor Fonte Latência Custo Qualidade Configuração
transformers Modelo ONNX local (Xenova/all-MiniLM-L6-v2) ~50-150ms (CPU) Grátis Boa Apenas npm install
static Vetores pré-calculados (em cache) <1ms Grátis N/D (depende de acerto no cache) Nenhuma
remote API da OpenAI / Cohere / Voyage ~100-300ms $0.02-0.10/1M tokens Excelente Chave de API
auto Seleciona a melhor fonte disponível em tempo de execução Igual à fonte selecionada Grátis Igual à fonte selecionada Nenhuma
(cache) Camada LRU em memória sobre qualquer fonte <1ms (acerto), latência total (falha) Grátis Igual à fonte subjacente Sempre ativa (não é uma fonte selecionável)
Qual é o contexto da sua implantação?
│
┌───────────┼───────────┬──────────────┐
│ │ │ │
DEV/TESTE PROD PEQUENA PROD GRANDE EDGE / OFFLINE
│ │ │ │
▼ ▼ ▼ ▼
transformers transformers remote (Qdrant) transformers
(grátis, sem API) (melhor qualidade) (sem internet)
│ │ │ │
└────────┬──┴───────────┴──────────────┘
│
▼
SEMPRE adicione a camada `cache` por cima
(LruCache envolve qualquer provedor)

As opções de embeddings da memória são configuradas por meio da API/interface de Configurações, não por variáveis de ambiente. As chaves relevantes do banco de dados de configurações, em Configurações (normalizeMemorySettings em src/lib/memory/settings.ts), são:

  • memoryEmbeddingSource: "transformers" (local), "remote" (baseada em API, por exemplo, OpenAI), "static" (armazenamento externo) ou "auto"
  • memoryEmbeddingProviderModel: identificador do modelo para fontes remotas/estáticas (por exemplo, "text-embedding-3-small")
  • memoryTransformersEnabled: true | false
  • memoryStaticEnabled: true | false
  • memoryVectorStore: "sqlite-vec", "qdrant" ou "auto"

Usa transformers.js internamente para executar modelos locais:

Janela do terminal
# Variáveis de ambiente lidas no código (src/lib/memory/embedding/index.ts):
MEMORY_TRANSFORMERS_MODEL=Xenova/all-MiniLM-L6-v2 # Repositório do modelo no HF
MEMORY_STATIC_MODEL=minishlab/potion-base-8M # Modelo potion estático do HF
MEMORY_STATIC_CACHE_DIR=<DATA_DIR>/embeddings # Diretório do cache

O cache está sempre ativo por padrão e é configurado por meio de variáveis de ambiente:

Janela do terminal
MEMORY_EMBEDDING_CACHE_MAX=1000 # Máximo de itens em cache
MEMORY_EMBEDDING_CACHE_TTL_MS=300000 # TTL (5 min)

Benchmark em um servidor x86 típico de 4 núcleos (textos com ~100 tokens cada):

Provedor p50 p95 p99 Custo / 1 milhão de embeddings
transformers (CPU) 80ms 180ms 350ms Gratuito
remote (OpenAI) 120ms 220ms 400ms ~$0,02 (ada-002) / $0,13 (3-large)
static (Qdrant) 15ms 30ms 60ms Depende da hospedagem do Qdrant
cache (acerto) <1ms <1ms 2ms Gratuito

O módulo extraction.ts (src/lib/memory/extraction.ts) usa correspondência de padrões com expressões regulares para extrair fatos estruturados de mensagens de conversas. Entender esses padrões ajuda você a ajustar a qualidade da extração para seu caso de uso.

Categoria Exemplo de padrão Captura
PREFERENCE_PATTERNS "I prefer <X>", "I like <X>", "I hate <X>" Preferências do usuário
DECISION_PATTERNS "I'll use <X>", "I decided to <X>", "I went with <X>" Decisões do usuário (episódicas)
PATTERN_PATTERNS "I usually <X>", "I always <X>", "I never <X>" Padrões comportamentais 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];

Quando um usuário diz:

“I prefer TypeScript. I’ll use Postgres for this project. I always commit before pushing. I don’t like Python.” A extração produz 4 memórias:

Chave Categoria Tipo Conteúdo
preference:typescript preferência factual “TypeScript”
decision:postgres_for_this_project decisão episódica “Postgres for this project”
pattern:commit_before_pushing padrão factual “commit before pushing”
preference:python preferência factual “Python”

Para evitar extrações descontroladas, aplicam-se os seguintes limites:

| Tamanho mínimo do conteúdo | 3 caracteres | | Tamanho máximo do conteúdo | 500 caracteres |

A extração é executada automaticamente sempre que a memória está habilitada; não há uma opção separada exclusiva para extração. Para desativá-la, desabilite completamente a memória (enabled: false por meio de PUT /api/settings/memory). Considere fazer isso quando:

  • Você tem um alto volume de mensagens e o custo da extração não é insignificante
  • Suas conversas são, em sua maioria, transitórias (bate-papo, depuração), sem valor de longo prazo
  • Você já está capturando o contexto por meio de plugins personalizados

O algoritmo Reciprocal Rank Fusion (RRF) combina resultados do FTS5 (palavras-chave) e de vetores (semânticos). O parâmetro k controla quanto peso é atribuído aos resultados com classificação mais baixa.

Para cada memória candidata, a pontuação RRF é:

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

Onde:

  • k é a constante (padrão 60)
  • rank_i(d) é a classificação do documento d no i-ésimo sistema de recuperação (FTS, vetor)
  • A soma abrange todos os sistemas de recuperação
Valor de k Efeito Mais adequado para
k=0 Fusão pura de classificações (sem suavização) Referência teórica
k=10-30 Atribui muito peso aos principais resultados; classificações baixas pouco contribuem Quando os 3 primeiros resultados geralmente estão corretos
k=60 (padrão) Equilibrado — todos os 10 primeiros resultados contribuem de maneira significativa Recuperação de uso geral
k=100+ Mais uniforme — até resultados com classificação baixa podem dominar caso apareçam em vários sistemas Quando a revocação > precisão é essencial
Janela do terminal
# Padrão
MEMORY_RRF_K=60
# Precisão agressiva (memória pequena, poucos documentos)
MEMORY_RRF_K=20
# Revocação máxima (memória grande, consultas variadas)
MEMORY_RRF_K=120

Exemplo com k=20:

  • Classificação 1 no FTS → contribuição 1/21 = 0.048
  • Classificação 10 no FTS → contribuição 1/30 = 0.033
  • Classificação 1 no vetor → contribuição 0.048
  • Máximo combinado: 0.096

Exemplo com k=60:

  • Classificação 1 no FTS → contribuição 1/61 = 0.016
  • Classificação 10 no FTS → contribuição 1/70 = 0.014
  • Classificação 1 no vetor → contribuição 0.016
  • Máximo combinado: 0.033

Com um k mais alto, a diferença relativa entre a primeira e a décima posição é menor, portanto o algoritmo depende mais do consenso entre os sistemas de recuperação do que da confiança na primeira posição.

Sintoma Experimente
O primeiro resultado sempre vence, mas está errado Diminuir k (por exemplo, 20) — a confiança na primeira posição importa mais
A resposta correta está entre as 5 primeiras, mas não em primeiro lugar Aumentar k (por exemplo, 100) — uma pontuação mais uniforme recompensa o consenso
A revocação é alta, mas a precisão é baixa Diminuir k — torne a classificação mais precisa
A revocação é baixa (documentos relevantes ausentes) Aumentar k — dê uma chance aos documentos com classificação mais baixa

A fusão recíproca de classificações usa pesos iguais para a classificação vetorial semântica e a classificação da pesquisa de texto completo:

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

Não há variáveis de ambiente para ajustar pesos individuais (MEMORY_RRF_VECTOR_WEIGHT/MEMORY_RRF_FTS_WEIGHT não existem).


O módulo summarization.ts (src/lib/memory/summarization.ts) compacta memórias antigas para manter pequeno o conjunto ativo, preservando a capacidade de recuperação.

Acionador Limite (padrão)
Acionamento manual via API não se aplica

Dois pontos de entrada são exportados de summarization.ts:

  • summarizeMemories(apiKeyId, sessionId?, maxTokens = 4000) — condensa as memórias de uma sessão em um único texto de resumo, limitado por um orçamento de tokens.
  • summarizeMemoriesOlderThan(apiKeyId, days, dryRun) — a compactação baseada em idade usada pela API: seleciona todas as memórias mais antigas que days, cria uma única memória de resumo condensada a partir delas e, quando dryRun é false, exclui os originais. Passe dryRun: true para visualizar o conjunto candidato e o total de tokens sem modificar nada.

Não há uma etapa de agrupamento por tag/chave nem uma pontuação por memória de “essencial vs. sumarizável” — a seleção é feita exclusivamente pelo limite de idade, e o texto do resumo é uma linha condensada, prefixada pelo tipo, para cada candidata.

A sumarização é manual / opcional — a configuração autoSummarize é false por padrão, portanto nada é compactado automaticamente. Acione-a por meio da API:

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

Para mantê-la desativada, basta deixar autoSummarize com seu valor padrão (false).

  • Visualize primeiro com dryRun — summarizeMemoriesOlderThan(..., true) retorna a lista de candidatas e a contagem total de tokens, permitindo confirmar o que seria mesclado antes de excluir os originais.
  • Execute a sumarização durante horários de baixo tráfego se você tiver um grande corpus de memórias — a chamada ao LLM é a parte lenta
Janela do terminal
# Estilo cron: sumarizar diariamente às 3h
0 3 * * * curl -X POST http://localhost:20128/api/memory/summarize \
-H "Authorization: Bearer $OMNIROUTE_KEY"

Fonte da verdade: src/lib/memory/backend.ts, src/lib/memory/genericBackend.ts, src/lib/memory/manager.ts Testes: src/lib/memory/__tests__/generic-backend.test.ts

O padrão de provedor MemoryBackend introduz uma camada de abstração de backend conectável sobre o mecanismo de memória existente. Em vez de ficar vinculado a uma única implementação de armazenamento, o sistema de memória agora oferece suporte a vários backends (SQLite, Obsidian, Notion e backends HTTP personalizados), com roteamento configurável de primário/fallback.

┌──────────────────────────────────────────────────────────┐
│ Rotas da API │
│ (src/app/api/memory/route.ts) │
└──────────────────────┬───────────────────────────────────┘
│
┌──────────────────────▼───────────────────────────────────┐
│ MemoryManager │
│ Orquestrador singleton (manager.ts) │
│ │
│ Primário ──► Backend A (por exemplo, SQLite) │
│ Fallback ──► Backend B (por exemplo, Obsidian) │
│ Backend C (por exemplo, Notion via GenericBackend) │
└──────────────────────┬───────────────────────────────────┘
│
┌──────────────┼──────────────┐
▼ ▼ ▼
┌────────────┐ ┌────────────┐ ┌──────────────────┐
│ Backend │ │ Backend │ │ Backend de │
│ SQLite │ │ Obsidian │ │ memória genérico │
│ │ │ │ │ (HTTP) │
└────────────┘ └────────────┘ └──────────────────┘

Todo backend deve implementar a interface 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> }>;
// Pesquisa
search(config: SearchConfig): Promise<Memory[]>;
// Integridade
health(): Promise<HealthCheckResult>;
// Ciclo de vida (opcional)
initialize?(): Promise&lt;void&gt;;
shutdown?(): Promise&lt;void&gt;;
}

Orquestrador singleton que:

  • Registra backends por meio de register(backend) — chamado na inicialização a partir de index.ts
  • Configura o primário e os fallbacks por meio de configure(primary, fallbacks)
  • Roteia operações CRUD/pesquisas para o primário, com uma cadeia de fallback em caso de falha
  • Verifica a integridade de todos os backends periodicamente

Comportamento de fallback:

Operação Primário Fallbacks
create ✅ Somente o primário ❌
get ✅ Tenta primeiro o primário ✅ Fallback se for null
update ✅ Somente o primário ✅ Sincronização sem aguardar
delete ✅ Somente o primário ✅ Sincronização sem aguardar
list ✅ Somente o primário ❌
search ✅ Primeiro o primário ✅ Fallback em caso de erro

Um conector HTTP genérico que adapta qualquer API REST a um MemoryBackend. Útil para:

  • Notion — conecte por meio da API do Notion
  • Obsidian — conecte por meio da API REST local do Obsidian
  • Backends personalizados — qualquer serviço que exponha uma API RESTful de memória

Configuração:

interface GenericBackendConfig {
baseUrl: string; // URL base da API do backend
apiKey?: string; // Token Bearer para autenticação
headers?: Record<string, string>; // Cabeçalhos HTTP personalizados
timeout?: number; // Tempo limite da solicitação (padrão: 30000ms)
backendType?: string; // Para registro de logs
// Substituições de endpoints (os padrões usam convenções REST)
endpoints?: {
search?: string; // padrão: "/memories/search"
create?: string; // padrão: "/memories"
list?: string; // padrão: "/memories"
get?: string; // padrão: "/memories/{id}"
update?: string; // padrão: "/memories/{id}"
delete?: string; // padrão: "/memories/{id}"
health?: string; // padrão: "/health"
};
// Mapeamentos de nomes de parâmetros de consulta
queryParams?: {
query?/apiKeyId?/limit?/offset?/strategy?/maxTokens?/type?/sessionId?/orderBy?/orderDir?/options?
};
// Mapeamentos de nomes de parâmetros de caminho
pathParams?: {
id?/memoryId?
};
}

Os backends conhecidos são pré-configurados em KNOWN_BACKENDS:

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

O backend primário padrão. Encapsula o armazenamento de memória existente baseado em SQLite usando src/lib/memory/store.ts. Registrado automaticamente na inicialização.

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

Encapsula a integração existente com o Obsidian (src/lib/memory/obsidianBackend.ts). Conecta-se a um cofre do Obsidian por meio da API REST local do Obsidian.

As configurações do backend de memória são armazenadas na tabela de configurações do aplicativo e gerenciadas por meio de src/lib/memory/settings.ts:

Configuração Chave de ambiente/configuração Padrão Descrição
Backend primário memoryPrimaryBackend "sqlite" ID do backend primário
Backends de fallback memoryFallbackBackends [] IDs ordenados dos backends de fallback
Configurações dos backends memoryBackendConfigs {} Substituições de configuração por backend

As configurações são normalizadas por meio de normalizeMemorySettings() e armazenadas em cache em getMemorySettings().

Inicialização do aplicativo
→ importações de index.ts (efeito colateral): registram SQLiteBackend
→ initMemoryBackends() chamado a partir do ciclo de vida do aplicativo:
1. Carregar configurações (getMemorySettings)
2. Configurar backend primário + fallback
3. Inicializar todos os backends (verificação de integridade)
4. Pronto para solicitações
  1. Implemente a interface MemoryBackend em src/lib/memory/&lt;name&gt;Backend.ts
  2. Exporte a partir de src/lib/memory/index.ts
  3. Registre com memoryManager.register(yourBackend) na inicialização
  4. Configure por meio das configurações: defina memoryPrimaryBackend como o ID do seu backend
  5. Teste usando src/lib/memory/__tests__/generic-backend.test.ts como referência
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);
Janela do terminal
npx vitest run src/lib/memory/__tests__/generic-backend.test.ts --reporter=verbose

Saída esperada: 35 testes, todos aprovados, abrangendo:

  • Construtor (2)
  • Verificação de integridade (4) — sucesso, falha 500, erro de rede, latência
  • Inicialização (2) — sucesso, falha
  • Criação (2) — endpoint padrão, endpoint personalizado
  • Obtenção (4) — sucesso, 404 → null, lançamento de erro diferente de 404, parâmetros de caminho personalizados
  • Atualização (2) — sucesso, 404 → false
  • Exclusão (2) — sucesso, 404 → false
  • Listagem (2) — parâmetros de consulta, nomes de parâmetros personalizados
  • Pesquisa (3) — parâmetros de consulta, endpoint personalizado, serialização de opções
  • Cabeçalhos de autenticação (2) — token Bearer, cabeçalhos personalizados
  • Fábrica (1)
Janela do terminal
npm run typecheck:core

Esperado: 0 erros.


Código-fonte do OmniRoute (a58000c7685f)

HagiCode

HagiCode é um ambiente de programação com agentes, fluxos estruturados, execução multiagente e visualizações Hero Dungeon.

Transforme ideias em software útil com um fluxo de trabalho com agentes mais inteligente, rápido e agradável.

Interface principal do HagiCode no tema claro
  • SmartFluxos estruturados transformam intenções em um caminho executável da ideia à entrega.
  • EfficientFluxos multiagente mantêm pesquisa, implementação e revisão em andamento simultaneamente.
  • FunO Hero Dungeon torna longas sessões de programação mais visuais e colaborativas.
Acessar HagiCode