Fluxo de request
Uma pergunta, traçada de ponta a ponta pelo retrieval e pelo LLM.
O fluxo de perguntas é o caminho crítico do LocalLens. Cada round-trip de chat atravessa quatro atores numa ordem fixa: o caller, a classe de workflow, o QVAC gateway e o prompt builder.
O fluxo tem duas metades:
- Retrieval (RAG): embute a pergunta com
GTE_LARGE_FP16, rodaragSearchno workspace e devolve os top-K chunks. Esse lado não escreve uma palavra sequer da resposta. - Geração (LLM): entrega os chunks recuperados e a pergunta
para
QWEN3_1_7B_INST_Q4(ou o fallbackQWEN3_600M_INST_Q4) viacompletion(). Esse é o chat model que de fato compõe a resposta, em streaming, token por token.
Passo a passo
1. Embed e search (RAG)
LocalLensApp.askBrain(id, question) (src/locallens.ts)
chama QvacGateway.search(workspace, question, 5). O gateway
embute a pergunta com GTE_LARGE_FP16 e roda ragSearch no
workspace QVAC do brain. O resultado é uma lista de SearchHit:
type SearchHit = {
id: string;
relativePath: string;
chunkIndex: number;
content: string;
score?: number;
};Top-K é fixo em 5. Esse número troca tamanho de prompt por qualidade de resposta e funciona bem em brains pequenos. Se você mexer nele, fique de olho na context window do chat model — 4096 tokens por default.
2. Monta o grounded prompt (a costura)
buildGroundedHistory(question, hits) em src/rag.ts devolve um
ChatMessage[] de duas mensagens. Essa é a costura entre o lado
RAG (que produziu os hits) e o LLM (que está prestes a lê-los):
- uma mensagem system com as regras: usar só fatos dos excerpts, citar com colchetes, responder no idioma da pergunta, sem chain-of-thought escondido;
- uma mensagem user com os excerpts num bloco numerado, seguidos da pergunta.
Os excerpts numerados são o que o modelo devolve como [1] e
[2] quando cita. O LLM nunca vê os embeddings nem os scores —
só esse prompt empacotado.
3. Streama a completion do LLM
QvacGateway.answer(history) é onde o chat LLM faz o trabalho
de fato. Chama completion() do QVAC com stream: true em
QWEN3_1_7B_INST_Q4 (o modelo Qwen3 instruct quantizado em Q4
com 1.7B de parâmetros, default), e entrega cada evento
contentDelta como um AsyncGenerator<string>. askBrain
acumula esses tokens até formar a string final.
Em máquinas que não conseguem carregar o 1.7B, o gateway faz
fallback transparente para QWEN3_600M_INST_Q4 na primeira
vez que os modelos carregam (ver QvacGateway.loadModels).
O generator streamado tem a mesma forma nos dois casos, então nada
acima percebe a troca.
captureThinking: true mantém qualquer token de raciocínio interno
fora do output visível, e kvCache: true permite que o runtime
reaproveite o estado de atenção do prefixo em follow-ups.
Esse é o passo onde a resposta de fato é escrita. Sem ele, você teria chunks rankeados mas nenhuma prosa.
4. Retorna as citações
Os hits usados para montar o prompt voltam direto para o caller
como ChatAnswer.citations:
type ChatAnswer = {
answer: string;
citations: { id; relativePath; chunkIndex; score? }[];
};É isso que a CLI imprime e o que a UI renderiza embaixo da resposta. A resposta é o output do LLM; as citações são a lista de evidências do lado RAG. Os dois pedaços vão juntos para o leitor poder verificar a afirmação contra a fonte.
O que não está nesse fluxo
- Sem reingestão. Fazer uma pergunta nunca toca o filesystem.
- Sem mutação do store. O JSON store é read-only no caminho da pergunta.
- Sem segunda chamada ao LLM. Uma completion por pergunta. O embedding model dispara uma vez por pergunta (pro search), o chat LLM dispara uma vez por pergunta (pra resposta).
Manter o read path enxuto assim é o que faz os follow-ups serem rápidos, mesmo em hardware modesto. Duas chamadas de modelo, zero escrita em disco, e o LLM nunca vê mais que os top-K chunks e a pergunta.