LocalLens
Estender

Adicionar diagnósticos melhores

Exibe estado de modelo, progresso de indexação, contagens de arquivo e chunk e números reais de latência do profiler QVAC.

Onde eles pertencem: src/qvac.ts para estado do modelo e a ponte do profiler, src/server.ts para exposição, src/ui/app.js para exibição.

O LocalLens esconde a maior parte do ciclo de vida dele do usuário. Tudo bem quando tudo é rápido. É chato quando um modelo demora um minuto para carregar e a UI não mostra nada. Uma superfície pequena de diagnóstico resolve isso sem complicar o core.

Dois tipos de sinal valem ser mostrados:

  • Estado operacional — o que é: loading vs loaded, contagens de arquivo e chunk, o modelo de chat ativo. O gateway já sabe isso; você só precisa expor.
  • Métricas de performance — quão rápido: tempos de load de modelo, latência de embedding, timings de busca e ingest RAG, throughput de completion. O QVAC SDK entrega um utilitário profiler que captura tudo isso sem nenhum código de timing do seu lado.

Os dois são complementares. Estado operacional te diz o que está acontecendo agora. O profiler te diz quanto tempo as coisas levaram.

O que expor

  1. Estado de carregamento do modelo. Loading / loaded / failed, mais quais modelos.
  2. Progresso de indexação. Bytes lidos, chunks criados, arquivo atual.
  3. Estatísticas do brain. Contagem de arquivos, contagem de chunks, nome do modelo de embedding.
  4. Modelo ativo. Qual modelo de chat está em uso agora (o 1.7B ou o fallback 600M).
  5. Agregados de latência. Último timing e média para loadModel, embed, completion, ragIngest, ragSearch.

Os quatro primeiros vêm do gateway. O quinto vem do profiler.

A receita

1. Adiciona um getter de status ao gateway

src/qvac.ts
type ModelState = "unloaded" | "loading" | "loaded" | "error";

export class QvacGateway {
  // existing fields
  private state: { chat: ModelState; embedding: ModelState } = {
    chat: "unloaded",
    embedding: "unloaded",
  };

  status() {
    return {
      chat: { state: this.state.chat, modelId: this.chatModelId },
      embedding: { state: this.state.embedding, modelId: this.embeddingModelId },
    };
  }

  // wrap loadModel calls to update state
  private async loadModels(): Promise<void> {
    this.state.embedding = "loading";
    try {
      this.embeddingModelId ??= await loadModel({ modelSrc: GTE_LARGE_FP16 });
      this.state.embedding = "loaded";
    } catch (e) {
      this.state.embedding = "error";
      throw e;
    }
    // … same pattern for chat …
  }
}

O getter de status é read-only e sem side-effects, então é seguro chamar do endpoint de diagnóstico sem tocar o ciclo de vida do modelo.

2. Liga o profiler QVAC

@qvac/sdk entrega um utilitário profiler que grava timing entre loads de modelo, completions, embeddings e operações de RAG. Liga uma vez no boot e deixa capturar toda chamada que o gateway faz:

src/qvac.ts
import { profiler } from "@qvac/sdk";

// near the top of the file, alongside chatModelConfig
profiler.enable();

profiler.enable() é um switch global. Toda chamada SDK seguinte grava eventos. Se você preferir opt-in por chamada, toda função QVAC também aceita { profiling: { enabled: true } } nas opções, e você pode dar opt-out seletivo com { profiling: { enabled: false } }.

Três formatos de export estão disponíveis:

MétodoRetornaMelhor para
profiler.exportSummary()String de resumo de alto nívelLogging no shutdown.
profiler.exportTable()Tabela agregada detalhadaLeitura num terminal.
profiler.exportJSON(){ aggregates, recentEvents, config }Servir por HTTP para a UI.

JSON é o que o endpoint de diagnóstico deve retornar. A UI pode formatar do jeito que quiser.

Adiciona um helper minúsculo no gateway para os chamadores não terem que importar profiler direto:

src/qvac.ts
metrics() {
  return profiler.exportJSON();
}

Profiler não mede tudo

O profiler captura latência para chamadas SDK. Não mede progresso de indexação direto, contagens de arquivo ou chunk, ou qual modelo de chat está ativo no momento. Esses vêm do estado próprio do gateway, e é por isso que essa página mantém as duas superfícies. O profiler complementa o getter de status — não substitui.

3. Adiciona uma rota /api/diagnostics

src/server.ts
if (url.pathname === "/api/diagnostics" && request.method === "GET") {
  return json({
    qvac: app.diagnostics(),
    metrics: app.metrics(),
    brains: (await app.listBrains()).map((b) => ({
      id: b.id,
      name: b.name,
      status: b.status,
      fileCount: b.fileCount,
      chunkCount: b.chunkCount,
      lastIndexedAt: b.lastIndexedAt,
      lastError: b.lastError,
    })),
  });
}

app.qvac é privado. Ou expõe diagnostics() e metrics() em LocalLensApp que fazem proxy, ou torna o gateway protected. Proxiar é mais limpo — mantém LocalLensApp como a única coisa que o servidor conversa:

src/locallens.ts
diagnostics() {
  return this.qvac.status();
}

metrics() {
  return this.qvac.metrics();
}

4. Progresso de indexação

A indexação hoje roda como uma única chamada async. Para fazer stream do progresso, troca o workflow para um generator:

src/locallens.ts
async *createBrainFromFolderProgress(input: CreateBrainFromFolderInput): AsyncGenerator<ProgressEvent, Brain> {
  yield { type: "discovery", message: "Walking folder…" };
  const documents = await discoverTextDocuments(folderPath);
  yield { type: "discovered", fileCount: documents.length };

  yield { type: "chunking" };
  const chunks = await chunkDocuments(documents, { brainId: brain.id });
  yield { type: "chunked", chunkCount: chunks.length };

  yield { type: "ingesting" };
  await this.qvac.ingestChunks(brain.workspace, chunks);
  yield { type: "ingested" };

  return indexed;
}

O servidor então expõe como Server-Sent Events:

if (url.pathname === "/api/brains/from-files/stream") {
  const stream = new ReadableStream({ /* yield events */ });
  return new Response(stream, { headers: { "content-type": "text/event-stream" } });
}

A UI consome o stream SSE e atualiza uma barra de progresso.

5. Renderiza

Três funções de render novas em app.js:

function renderDiagnostics(diagnostics) {
  // model state badge in the topbar
  // brain table in a side panel
}

function renderMetrics(metrics) {
  // "Last search: 230 ms · avg 280 ms"
  // "Embedding model loaded in 4.2s"
  // — pulled from metrics.aggregates and metrics.recentEvents
}

function renderIndexingProgress(event) {
  // progress bar updates from SSE events
}

Mantém o painel colapsável. A maior parte dos usuários não quer ele aberto por default.

Diagnósticos são read-only

Nenhum desses endpoints deve mudar estado. Se uma feature futura precisa reiniciar um modelo ou despejar um brain, são rotas separadas. Diagnóstico mostra o que está acontecendo. Não intervém.

O que você não precisa mudar

  • domain.ts — tipos continuam iguais.
  • rag.ts — chunking e prompts inalterados.
  • store.ts — o formato JSON está inalterado. O endpoint de diagnóstico lê o que já está lá.

Referências externas

  • Referência do profiler QVAC — API completa incluindo enable, disable, exportSummary, exportTable, exportJSON e a opção profiling por chamada.

On this page