LocalLens
Arquitectura

Por qué QVAC

Las cinco llamadas al SDK que hacen la mayor parte del trabajo, y por qué vienen juntas.

QVAC es el SDK que corre el loop de IA local de LocalLens. Lo interesante de él para un proyecto de este tamaño: un solo paquete maneja cinco trabajos que de otro modo arrastrarían cinco librerías distintas.

Las cinco llamadas al SDK que usamos

import {
  loadModel,
  ragChunk,
  ragIngest,
  ragSearch,
  completion,
  // and the lifecycle helpers:
  ragCloseWorkspace,
  ragDeleteWorkspace,
  close,
} from "@qvac/sdk";
LlamadaDónde se usaQué hace
loadModelsrc/qvac.ts → ensureReadyCarga el modelo de embeddings (GTE_LARGE_FP16) y el modelo de chat (QWEN3_1_7B_INST_Q4 con un fallback de 600M).
ragChunksrc/rag.ts → chunkDocumentParte el texto de un documento en ventanas de ~220 tokens con 40 tokens de solapamiento.
ragIngestsrc/qvac.ts → ingestChunksEmbeddea chunks y los almacena en un workspace con nombre.
ragSearchsrc/qvac.ts → searchEmbeddea una query y devuelve los top-K chunks que hacen match.
completionsrc/qvac.ts → answerHace stream de un completion de chat desde el modelo cargado.
ragCloseWorkspacesrc/qvac.ts → closeWorkspaceCierra (y opcionalmente borra) el workspace en disco.
closesrc/qvac.ts → closeDesmonta el runtime de QVAC cuando la app sale.

Esa es toda la superficie de la API que LocalLens usa. Sin loop manual de embeddings, sin base de datos vectorial aparte, sin splitter de tokens custom.

Lifecycle de los modelos

Los modelos de QVAC cargan lazy en el primer uso:

private async ensureReady(): Promise<void> {
  if (this.chatModelId && this.embeddingModelId) return;
  this.readyPromise ??= this.loadModels().finally(() => {
    this.readyPromise = undefined;
  });
  await this.readyPromise;
}

Dos consecuencias que vale la pena conocer:

  1. El costo de cold-start aterriza en la primera pregunta, no en el boot. Eso mantiene bun run dev ágil y te da un momento claro de "cargando modelo…" donde colgar una pista en la UI.
  2. Las llamadas concurrentes comparten una sola carga en vuelo. Cada caller espera el mismo readyPromise hasta que resuelve. Dos requests que llegan en el mismo tick no van a disparar ambos loadModel para la misma fuente.

Por qué un modelo de fallback

El modelo de chat por default es QWEN3_1_7B_INST_Q4. En máquinas más viejas o chicas puede fallar al cargar. El gateway captura eso y hace fallback a QWEN3_600M_INST_Q4:

try {
  this.chatModelId = await loadModel({ modelSrc: QWEN3_1_7B_INST_Q4, modelConfig });
} catch {
  this.chatModelId = await loadModel({ modelSrc: QWEN3_600M_INST_Q4, modelConfig });
}

El fallback es invisible para los callers. QvacGateway.answer mantiene la misma firma de streaming en cualquier caso.

¿Por qué un SDK para todo?

LocalLens podría haber usado @xenova/transformers para embeddings, chromadb o qdrant para vectores, y llama.cpp para completion. Cada uno es una buena opción por su cuenta. El costo de usar los tres es el pegamento de integración que tendrías que escribir: lifecycle de modelos, lifecycle de workspaces, mapeo de errores, iteración async. QVAC trae todo eso. Esa es la mayor parte de por qué esta app cabe en ocho archivos.

Páginas upstream útiles

On this page