Por que QVAC
As cinco chamadas do SDK que fazem a maior parte do trabalho, e por que vêm juntas.
QVAC é o SDK que roda o loop de IA local do LocalLens. O interessante dele para um projeto desse tamanho: um pacote cobre cinco trabalhos que de outra forma puxariam cinco bibliotecas diferentes.
As cinco chamadas do SDK que usamos
import {
loadModel,
ragChunk,
ragIngest,
ragSearch,
completion,
// and the lifecycle helpers:
ragCloseWorkspace,
ragDeleteWorkspace,
close,
} from "@qvac/sdk";| Chamada | Onde é usada | O que faz |
|---|---|---|
loadModel | src/qvac.ts → ensureReady | Carrega o modelo de embedding (GTE_LARGE_FP16) e o modelo de chat (QWEN3_1_7B_INST_Q4 com fallback de 600M). |
ragChunk | src/rag.ts → chunkDocument | Quebra o texto do documento em janelas de ~220 tokens com 40 tokens de overlap. |
ragIngest | src/qvac.ts → ingestChunks | Embeda chunks e armazena em um workspace nomeado. |
ragSearch | src/qvac.ts → search | Embeda uma query e retorna os top-K chunks que deram match. |
completion | src/qvac.ts → answer | Faz stream de um completion de chat a partir do modelo carregado. |
ragCloseWorkspace | src/qvac.ts → closeWorkspace | Fecha (e opcionalmente apaga) o workspace em disco. |
close | src/qvac.ts → close | Desmonta o runtime QVAC quando o app sai. |
Essa é a superfície de API inteira que o LocalLens usa. Sem loop manual de embedding, sem banco vetorial separado, sem token splitter customizado.
Ciclo de vida do modelo
Modelos QVAC carregam lazy no primeiro uso:
private async ensureReady(): Promise<void> {
if (this.chatModelId && this.embeddingModelId) return;
this.readyPromise ??= this.loadModels().finally(() => {
this.readyPromise = undefined;
});
await this.readyPromise;
}Duas consequências que vale conhecer:
- O custo de cold-start cai na primeira pergunta, não no boot.
Isso mantém o
bun run devresponsivo e te dá um momento claro de "carregando modelo…" para pendurar um hint na UI. - Chamadas concorrentes compartilham um load em voo. Todo chamador
espera a mesma
readyPromiseaté resolver. Dois requests chegando no mesmo tick não disparam doisloadModelpara o mesmo source.
Por que um modelo de fallback
O modelo de chat default é QWEN3_1_7B_INST_Q4. Em máquinas mais antigas ou
menores ele pode falhar ao carregar. O gateway captura isso e cai
para 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 });
}O fallback é invisível para os chamadores. QvacGateway.answer mantém a
mesma assinatura de streaming dos dois jeitos.
Por que um SDK para tudo?
O LocalLens poderia ter usado @xenova/transformers para embeddings,
chromadb ou qdrant para vetores, e llama.cpp para completion.
Cada um é uma escolha boa por conta própria. O custo de usar os três é a
cola de integração que você teria que escrever: ciclo de vida do modelo,
ciclo de vida do workspace, mapeamento de erros, iteração async. O QVAC entrega
isso para você. É a maior parte do motivo desse app caber em oito arquivos.
Páginas upstream úteis
- Referência QVAC RAG —
ragChunk,ragIngest,ragSearch. - Referência QVAC completion — streaming, KV cache, captura de thinking.
- Catálogo de modelos QVAC — IDs de modelos suportados.