InfoForge: Wikipédia offline como fonte RAG para os meus agentes de IA locais

Publicado em
28 agosto 2026
Atualizado em
16 setembro 2026
Por
Jacob Lloyd — escrito com ajuda de IA, depois do projeto
Tempo de leitura
17 min de leitura

Em termos simples: O InfoForge é uma cópia de toda a Wikipédia em inglês armazenada num computador na minha casa, além de um pequeno programa que permite que os assistentes de IA que eu uso consultem essa informação. Faça uma pergunta e, em vez de responder de memória, o assistente procura nesses artigos, lê os que correspondem e escreve uma resposta que indica de que artigos veio. Se não encontrar nada relevante, diz "Não consegui encontrar" em vez de inventar. Nada é enviado para nenhuma empresa, ninguém registra a pergunta, e continua a funcionar quando a internet não funciona.

Perguntei a um modelo local de 8B quando nasceu o romancista Natsume Sōseki. Respondeu “9 de fevereiro de 1867” e depois fez a parte que realmente me interessava: nomeou os artigos onde leu isso. O modelo não sabe esse facto. Procurou-o, numa cópia de 52,69 GB da Wikipédia que está em uma máquina na minha casa, com a internet irrelevante para a transação.

tl;dr

  • O que é: a Wikipédia inglesa completa como um único arquivo ZIM (19,19 milhões de entradas, dump de junho de 2026, sem imagens) servida localmente, mais uma pequena CLI em Python que a transforma numa fonte de recuperação para um modelo local.
  • Quanto custa: nada. Um download, ~53 GB de disco, sem conta, sem chave de API, sem subscrição.
  • Quão rápido: uma resposta fundamentada completa leva 4–10 segundos, e quase tudo isso é o modelo de linguagem a pensar. A busca e a obtenção de artigos são a parte barata (números abaixo).
  • A regra que o torna útil: quando a busca não encontra nada, a ferramenta imprime uma falha e sai com código 3. Quando as fontes não contêm a resposta, o modelo é instruído a dizê-lo. Os agentes que o usam relatam uma consulta falhada em vez de responder de memória.
  • O que nunca faz: sair de casa. Não há fornecedor, nem chamada de API, nem registo de consultas.

Atualizado em 16/09/2026. Dois dias após escrever isso pela primeira vez, o script simples tornou-se um CLI empacotado (versão 2.0.0): infoforge ask, search, serve start|stop|status|recreate, status e um comando doctor que verifica todos os links da cadeia. O download abaixo corresponde a essa versão empacotada, não ao script original. Os exemplos de comandos nesta página são da v2 e foram executados em 16/09/2026; a resposta mais conhecida da primeira versão, sobre o romance *Kokoro*, encontra-se na seção “Armadilhas”, pois a v2 já não a localiza. Adicionei os flags reais do container, a unidade de serviço, o prompt e as regras de recusa, além de corrigir o valor de tempo de carregamento.

Por que se dar ao trabalho, se o modelo já “sabe” as coisas?

Porque ele não sabe, e o modo de falha é bem problemático. Peça a um modelo pequeno uma data, uma propriedade química ou o nome de um lugar; ele lhe dará uma resposta fluente, confiante e plausível, sem nenhum indício de que seja verdadeira ou não. Ele não está mentindo; simplesmente não tem como distinguir entre lembrança e geração. Isso é aceitável para textos narrativos, mas desastroso para fatos concretos.

A recuperação de informações corrige esse problema, porém a abordagem usual pressupõe uma API de busca na nuvem: sua pergunta vai para uma empresa, que a registra; seu assistente passa então a depender da disponibilidade e dos termos de serviço de terceiros. Em minha casa, onde um stack de IA responde às perguntas dos meus filhos, ambos os aspectos me incomodam.

A Wikipedia resolve esses dois problemas de uma só vez: é o corpus de conhecimento geral mais denso existente, pode ser baixado legalmente na íntegra e cabe num disco que custa menos que um jantar. O projeto Kiwix já fez o trabalho pesado há anos: empacota wikis inteiras no formato comprimido e indexado ZIM, útil para escolas, navios e locais sem conexão à internet. Eu apenas direciono uma IA para esse conteúdo, em vez de um navegador.

O que se obtém no final

Uma pergunta entra, uma resposta sai, além de uma lista numerada de citações que indica quais artigos da Wikipedia foram usados para formar a resposta. Este é um exemplo real de 16/09/2026 (o endereço do servidor é loopback):

$ infoforge ask "When was Natsume Soseki born?"
infoforge: warning: skipping disambiguation page: Natsume
infoforge: warning: skipping disambiguation page: Soseki
infoforge: grounding on 5 article(s)
According to the provided sources, Natsume Soseki was born on 9 February 1867. ...

Citations:
[1] Natsume Soseki — http://127.0.0.1:8080/content/wikipedia_en_all_nopic/Natsume_Soseki
[2] Natsume Sōseki — http://127.0.0.1:8080/content/wikipedia_en_all_nopic/Natsume_S%C5%8Dseki
[3] Beongcheon Yu — ...

Isso levou 4,4 segundos. E o que acontece quando não há nada para fundamentar a resposta?

$ infoforge search "qzxvbnm plorkwidget fnargle"
infoforge: error: no sources found for 'qzxvbnm plorkwidget fnargle'
$ echo $?
3

Nenhum parágrafo, nenhuma suposição; apenas um código de saída que qualquer script pode verificar. O comando ask também devolve o código 3, além de sugerir que se usem palavras mais simples ou que se execute infoforge doctor. A tabela completa de códigos é curta: 0 significa sucesso; 2 indica configuração ou uso incorreto; 3 mostra que não há fontes para fundamentar a resposta; 4 indica ausência de citações (só ocorre com --strict); 5 significa que todos os endpoints do modelo estão inacessíveis. Mesmo no caso do código 5, o programa ainda exibe os artigos encontrados, evitando assim um desperdício da busca.

Como funciona

Cinco etapas, nenhuma delas complexa; recomendo que permaneçam assim.

Extração de palavras-chave. A pergunta não é o termo exato da busca. O Kiwix combina todos os termos com operador AND; assim, “Who wrote Kokoro and when was it published?” quase não retorna nada, enquanto Kokoro é um bom critério de busca. O núcleo do extrator usa um filtro de palavras irrelevantes para manter a ordem original dos termos:

def keyword_query(question: str) -> str:
    tokens = _TOKEN_RE.findall(question)
    keywords = [
        t for t in tokens
        if t.lower() not in _STOPWORDS and t.lower() not in _WEAK_VERBS
    ]
    return " ".join(keywords) if keywords else question.strip()

A v2 acrescenta duas funcionalidades: frases com letras maiúsculas da pergunta são testadas primeiro como títulos de artigos; os resultados combinados são reordenados, fazendo com que páginas de desambiguação, anos isolados e páginas do tipo “Lista de…” fiquem em posições inferiores.

Busca. A busca full-text do Kiwix é um verdadeiro índice de busca, não um simples grep; isso explica o bom desempenho. Na época da implementação, ela devolvia títulos classificados entre 19,19 milhões de entradas em 3 a 30 milissegundos. Em 16/09/2026, num dia mais movimentado na mesma máquina, esse tipo de consulta demorou de 90 a 730 ms. Acredito que o índice tenha saído do cache de páginas; ainda assim, esse tempo é insignificante comparado ao tempo gasto pelo modelo. Não há embeddings, banco vetorial ou armazenamento de trechos nesse sistema; os editores da Wikipedia já fizeram o “dividir em partes” – eles chamam essas partes de artigos.

Carregamento e corte. O kiwix-serve devolve um artigo via loopback em cerca de 1 ms quando o cache está quente. Medi 0,7–1,1 ms com curl em três artigos que eu acabara de ler; na primeira vez, o tempo foi de 6–9 ms. No meu primeiro rascunho escrevi “0,5 ms” sem esclarecer que era um valor para cache quente. O corte do texto é a única etapa que exige julgamento: cortar desde o início fazia perder a resposta com frequência, pois a informação necessária costuma estar no meio de artigos longos. Assim, cada artigo mantém um trecho inicial curto (um quarto do limite definido) e uma janela ao redor da parte que melhor corresponde às palavras mais raras da pergunta.

O prompt fundamentado. Essa é a chave de todo o processo; tem apenas quatro frases. Primeiro vêm as regras, depois as fontes numeradas e por fim a pergunta:

You are a question-answering assistant grounded ONLY in the source texts provided below.
Answer the user's question using only information found in these sources. If the sources do not contain the answer, say so clearly.
When you use information from a source, cite the article title (for example: "according to <title>").
Do not invent facts, titles, or URLs that are not present in the sources.

Source 1: <title>
<trimmed article text>
...
Question: <the question>

A temperatura usada é 0,3 em vez de 0. O comentário no código diz que 0,0 fazia algumas compilações locais entrar em loop por causa de textos repetitivos nas fontes. Trata-se de um prompt, não de uma garantia absoluta; por isso existe a próxima seção.

O modelo. Um modelo de raciocínio local (DeepSeek-R1 8B), rodando no servidor GPU da casa, é quem gera a resposta. O arquivo Q4_K_M tem 5,0 GB; seu cache KV consome cerca de 0,15 GB para cada 1.000 tokens de contexto, no formato f16. Assim, os pesos do modelo mais uma janela de contexto de 32 mil tokens resultam em aproximadamente 10 GB de uso de memória antes mesmo dos cálculos propriamente ditos. A resposta gerada é posteriormente processada para remover os blocos <think> antes de ser exibida. Minha medição inicial indicou cerca de 8 segundos para todo o ciclo de resposta. Três execuções realizadas em 16/09/2026 demoraram 4,4, 5,2 e 9,7 segundos, respectivamente. A recuperação de informações é gratuita; o “pensamento” do modelo, porém, não é. Não existe mais nenhum mecanismo de fallback local: removi o servidor de modelos do mini PC em 16/09/2026. Caso o servidor GPU não responda, a ferramenta retorna o código de erro 5 e ainda exibe os artigos que encontrou.

O “ancoramento” como contrato de recusa

Essa é a decisão de design que eu defenderia com mais afinco – e não se trata de uma questão técnica.

Um prompt do tipo “use apenas essas fontes” é, na verdade, um pedido. Na maioria das vezes os modelos obedecem; às vezes, porém, ignoram-no silenciosamente, especialmente quando as fontes são escassas e o modelo se lembra de algo que se encaixa no contexto. Se essa instrução for a única defesa do sistema, o “ancoramento” da resposta não passa de uma sensação subjetiva.

Por isso, parte desse “ancoramento” é imposto externamente ao modelo, em três camadas:

  1. Sem fontes, sem chamada ao modelo. Caso a busca não retorne nenhum resultado, a ferramenta jamais envia uma pergunta diretamente ao LLM como forma de fallback. Nesse caso, ela encerra a execução com o código 3 – um sinal que todos os programas que a utilizam já verificam por padrão.
  2. Fontes que não respondem. O prompt instrui o modelo a declarar isso; nesse cenário, a execução ainda é considerada bem-sucedida. Uma resposta honesta do tipo “não consta nessas fontes” representa o funcionamento correto da ferramenta.
  3. Resposta que não cita nenhuma fonte. A ferramenta exibe um aviso indicando que a resposta deve ser considerada não ancorada. Com a opção --strict, ela encerra a execução com o código 4.

Além disso, os agentes que utilizam essa ferramenta são orientados a relatar uma falha na busca em vez de fornecer uma resposta baseada em suas próprias memórias. Assim, qualquer recusa na obtenção de informações é repassada para o nível superior da hierarquia; ninguém acima pode “maquiar” esse fato.

O resultado é um assistente que diz, de forma transparente, que não conseguiu encontrar a resposta desejada. Num cenário onde isso poderia parecer uma fraqueza, na verdade é o oposto: um modelo que admite “não encontrei” vale muito mais do que um modelo maior que inventa respostas. Isso porque com o primeiro você pode confiar e construir sobre ele; com o segundo, é preciso verificar cada informação fornecida. Se você precisa checar todas as respostas, na verdade não economizou nenhum esforço.

Privacidade que você pode verificar

Todos os grandes assistentes virtuais possuem uma página de privacidade. Este aqui, porém, dispensa isso: a pergunta parte do terminal, vai para um servidor de busca na mesma máquina, depois para o modelo na minha rede doméstica, e retorna ao usuário. Nenhum ponto dessa cadeia consegue registrar essa interação; nenhuma chave identifica quem está fazendo a pergunta; também não há termos de serviço sujeitos a mudanças futuras. Uma pergunta feita aqui é tão privada quanto procurar algo num livro na estante – pois, em essência, é exatamente isso que acontece.

Ninguém precisa acreditar cegamente nisso: você pode comprovar pessoalmente desconectando a internet e vendo que tudo continua funcionando normalmente.

Os 90% “não glamourosos”

A implementação do pipeline foi um trabalho agradável durante um final de semana. O desafio real, porém, residiu em configurar três parâmetros relacionados a containers.

O servidor da Wikipedia roda como um container Podman sem privilégios de root num desktop Linux imutável com o SELinux ativado. Cada uma dessas condições me custou uma noite inteira de configuração. O comando que finalmente funcionou foi este:

podman run -d --name infoforge-kiwix \
  --user root \
  -p 127.0.0.1:8080:8080 \
  -v "$HOME/zims:/data:z" \
  ghcr.io/kiwix/kiwix-serve \
  -k /data/wikipedia_en_all_nopic.zim
  • --user root. No Podman sem privilégios de root, o usuário root dentro do container corresponde ao seu próprio usuário comum fora dele. O usuário padrão da imagem fica num intervalo de sub-UIDs, não consegue acessar sua pasta pessoal e recebe “Permission denied” ao tentar ler /data.
  • :z no volume. Em sistemas SELinux, o mount precisa ser reetiquetado; caso contrário, o kernel bloqueia o acesso, mesmo para o root do container. O erro exibido parece indicar que o arquivo simplesmente não existe – o que leva a procurar no lugar errado.
  • Caminhos ZIM explícitos (e -k). Sem esse argumento, o script de inicialização da imagem tentava rodar kiwix-serve sem nenhum arquivo ZIM, resultando na mensagem “Arguments did not match expected patterns”. O parâmetro -k (--skipInvalid) permite que o servidor inicie mesmo quando algum arquivo ainda está em download.

Num host Docker sem SELinux ou mapeamento de usuários sem privilégios, provavelmente nada disso seria necessário. A vinculação a 127.0.0.1 foi uma mudança feita na versão 2; a primeira versão aceitava conexões em toda a rede local – algo desnecessário para um servidor de conhecimento sem autenticação.

Esses três parâmetros consumiram cerca de 90% do tempo total de configuração. Essa é a realidade da maioria dos projetos de auto-hospedagem; infelizmente, essa parte raramente é abordada nos tutoriais. Por isso muitas pessoas acabam achando que não têm jeito para isso – quando na verdade estão apenas fazendo pela primeira vez, sozinhas, diante dos mesmos desafios.

Depois parou de funcionar… e ninguém percebeu

Funcionou perfeitamente. Era rápido. Testei, anotei os resultados e segui em frente.

Uma semana depois, tentei usá-lo novamente – e ele simplesmente não estava mais ativo. A máquina havia reiniciado em algum momento; o container não voltou a rodar, e nada me avisou disso, pois não havia nenhuma unidade de serviço, monitoramento ou verificação de integridade configurada. Pior: o arquivo de habilidades que informaria aos agentes da IA sobre a existência dessa ferramenta estava só como rascunho e nunca instalado. Durante toda aquela semana, cada agente da casa respondeu baseando-se em suas próprias memórias, quando poderia ter feito buscas; nenhum deles sabia que havia outra opção disponível.

A lição é simples:

Uma capacidade que nada inicia automaticamente e que ninguém conhece é indistinguível de uma capacidade que você nunca implementou.

A correção foi criar uma unidade de serviço com 12 linhas (comentários omitidos aqui) e um arquivo de habilidades de apenas uma página:

[Unit]
Description=InfoForge kiwix-serve (base de conhecimento offline para respostas ancoradas)
After=default.target

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=%h/.local/bin/infoforge serve start
ExecStop=%h/.local/bin/infoforge serve stop
ExecReload=%h/.local/bin/infoforge serve recreate
TimeoutStartSec=180

[Install]
WantedBy=default.target

O uso de oneshot combinado com RemainAfterExit foi intencional. O comando serve start retorna imediatamente após o container ser iniciado, mas o serviço deve permanecer “ativo” enquanto o container estiver rodando; assim, o comando systemctl --user stop tem algo para interromper. Para ativá-lo, basta executar systemctl --user enable --now infoforge.service. Como o serve start é idempotente, um “reload” simplesmente recria o container caso haja alterações nos arquivos ZIM.

O arquivo de habilidades funciona como uma nota compartilhada que os agentes leem para saber o que podem fazer. Ele contém três informações: a ferramenta existe, este é o comando para usá-la e toda resposta ancorada deve incluir suas respectivas citações.

O mecanismo de verificação de integridade que eu desejava agora é o comando infoforge doctor. Ele percorre sequencialmente todos os componentes (configuração, pasta dos arquivos ZIM, container, servidor HTTP, busca de teste e cada endpoint do modelo) e exibe “PASS” ou “FAIL” para cada um deles (saída resumida abaixo):

$ infoforge doctor
  [PASS] config          parsed <caminho da configuração>
  [PASS] zims            1 habilitado: wikipedia_en_all_nopic.zim
  [PASS] container       infoforge-kiwix: rodando, servindo wikipedia_en_all_nopic.zim
  [PASS] kiwix http      http://127.0.0.1:8080 responde normalmente
  [PASS] search          3 resultados encontrados para 'água' em wikipedia_en_all_nopic
  [PASS] llm primary     <servidor GPU>/v1 acessível; modelo ...DeepSeek-R1-0528-Qwen3-8B... (modelo listado)

doctor: todas as verificações foram bem-sucedidas

A primeira versão era um único script Python com cerca de 700 linhas não vazias – e essa sim foi uma história positiva. O que realmente tornou tudo funcional, porém, foram a unidade de serviço e o arquivo de habilidades; essa é uma lição que eu continuo aprendendo.

Integrado à pilha de agentes

Agora, o InfoForge é uma ferramenta compartilhada pelos agentes locais que eu gerencio, e não apenas um script que preciso lembrar de executar. Um agente que necessita de alguma informação simplesmente roda infoforge ask (ou --json para obter uma resposta legível por máquina), recebe uma resposta acompanhada das respectivas citações e repassa tudo adiante. Essas citações são mais importantes do que parecem: quando um agente menciona o título de um artigo, posso verificá-lo com apenas um clique; essa possibilidade de conferência é o que torna um assistente confiável realmente útil.

Pontos de atenção

  • Espaço em disco antes do download. A versão sem imagens ocupa cerca de 53 GB; a versão completa, com imagens, quase o dobro disso. Verifique o espaço livre antes de iniciar o download – um arquivo ZIM parcialmente baixado é uma maneira lenta de descobrir essa limitação.
  • A data do dump importa. O meu é de junho de 2026; a Wikipedia, porém, continua sendo atualizada. Para este sistema, qualquer conteúdo mais recente que esse dump simplesmente não existe. Isso é bom se você lembrar disso; caso contrário, torna-se uma armadilha.
  • A qualidade da busca depende da extração de palavras-chave e da correspondência de títulos. Na primeira versão, ao perguntar “Quem escreveu Kokoro e quando foi publicado?”, a resposta foi “Natsume Sōseki, 1914”, com o artigo devidamente citado. Ao refazer essa mesma pergunta na versão 2 (16/09/2026), o campo de título exibiu um álbum chamado KoKoro; o artigo sobre o romance sequer apareceu entre os cinco primeiros resultados. O modelo, então, agiu corretamente: listou os outros “Kokoros” encontrados e perguntou qual eu queria. Ao questionar “Quem escreveu o romance Kokoro?”, ele respondeu que as fontes não continham essa informação. Não houve alucinação, mas também nenhuma resposta concreta. Quase todas as respostas ruins que observei resultam de consultas mal formuladas, e não de falhas no modelo; portanto, aperfeiçoe primeiro a recuperação de informações antes de optar por um modelo maior.
  • O “trimming” do contexto é uma escolha real. Pouco contexto gera respostas incompletas; muito contexto faz o modelo se desviar das instruções. Ajuste esse parâmetro com base em perguntas reais que lhe interessam, e não em exemplos sintéticos.
  • Teste intencionalmente os caminhos de falha. Peça algo para o qual a Wikipedia não tenha artigo algum e confirme se você obtém o código de erro 3, e não um parágrafo fluente. Se você só testa perguntas que dão certo, na verdade testou apenas metade do cenário.
  • Crie a unidade de serviço já no primeiro dia. Veja os pontos acima.

Onde isso me deixa agora

Minha casa conta agora com uma enciclopédia offline equipada com um índice de busca, além de uma IA autorizada a responder apenas a partir desse conteúdo. Isso exigiu apenas um download e um final de semana de trabalho. A ferramenta continua operando durante quedas de rede, não pode ser desativada e não possui nenhum modelo de negócios que me envolva diretamente.

Próximos passos: adicionar mais pacotes ZIM ao mesmo ambiente (as coleções médicas e técnicas disponibilizadas pelo Kiwix, além de uma ou duas wikis próprias), pois a camada de recuperação não se importa com o tipo exato do corpus. A versão 2 já inclui um comando zimify para transformar pastas de documentos em arquivos ZIM. Outro ponto a ser corrigido é o erro com “Kokoro” mencionado anteriormente; trata-se de um problema de correspondência de títulos, e não de falha no modelo.

Relacionado: minha pilha de agentes de IA local, StudioForge (o servidor GPU onde o modelo roda), o mini PC que hospeda o InfoForge e quanto realmente custam os agentes de IA (o InfoForge, por si só, é gratuito).

Downloads

Gratuito para uso pessoal. Se isso te economizar uma tarde, o botão do café está logo ali.


← Mais de IA e LLM Local