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";| Llamada | Dónde se usa | Qué hace |
|---|---|---|
loadModel | src/qvac.ts → ensureReady | Carga el modelo de embeddings (GTE_LARGE_FP16) y el modelo de chat (QWEN3_1_7B_INST_Q4 con un fallback de 600M). |
ragChunk | src/rag.ts → chunkDocument | Parte el texto de un documento en ventanas de ~220 tokens con 40 tokens de solapamiento. |
ragIngest | src/qvac.ts → ingestChunks | Embeddea chunks y los almacena en un workspace con nombre. |
ragSearch | src/qvac.ts → search | Embeddea una query y devuelve los top-K chunks que hacen match. |
completion | src/qvac.ts → answer | Hace stream de un completion de chat desde el modelo cargado. |
ragCloseWorkspace | src/qvac.ts → closeWorkspace | Cierra (y opcionalmente borra) el workspace en disco. |
close | src/qvac.ts → close | Desmonta 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:
- 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. - Las llamadas concurrentes comparten una sola carga en vuelo.
Cada caller espera el mismo
readyPromisehasta que resuelve. Dos requests que llegan en el mismo tick no van a disparar ambosloadModelpara 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
- Referencia RAG de QVAC —
ragChunk,ragIngest,ragSearch. - Referencia de completion de QVAC — streaming, KV cache, captura de thinking.
- Catálogo de modelos de QVAC — IDs de modelos soportados.