Formulário de contato

Nome

E-mail *

Mensagem *

Imagem

IA Local no Linux com 2 GB de RAM: Guia Completo - Novo esquema

IA Local no Linux com 2 GB de RAM: Guia Completo - Novo esquema

Publicado por em


@CanalQb no YouTube


@CanalQb

Linux Leve + IA Local no PC com 2 GB de RAM



Você tem um Pentium N3700 com 2 GB de RAM e quer rodar inteligência artificial local — texto, imagem, análise visual — tudo offline, tudo no seu hardware, sem pagar nada? Testei isso na prática, errei, corrigi, e aqui está o que realmente funciona.

Este guia cobre desde a instalação do antiX Linux até rodar modelos de linguagem com Ollama, gerar imagens com Stable Diffusion em CPU, analisar imagens com Moondream2, e ainda criar uma Web UI própria com chat, memória e agentes — tudo rodando no mesmo PC modesto.

✅ Testado no Pentium N3700 · 2 GB RAM · antiX 23.1 · Ollama · SD.cpp · Moondream2

📋 Índice do guia
  1. Escolhendo a distro certa — comparativo real
  2. Instalando o antiX Linux passo a passo
  3. Criando o SWAP de 8 GB (obrigatório para IA)
  4. Instalando o Ollama e rodando o TinyLlama
  5. Stable Diffusion em CPU — geração de imagens
  6. Moondream2 — análise de imagens com IA
  7. Acessar o Ollama de outra máquina na rede local
  8. Templates personalizados com Modelfile
  9. Web UI com Flask: chat, memória e personas
  10. Agentes com internet e leitura de arquivos

Por que isso vale a pena mesmo sendo hardware antigo

Privacidade total

100% offline, zero nuvem

Nenhuma prompt sua sai do computador. Sem conta, sem API key, sem limite de uso. Para quem usa IA para escrever código, rascunhar textos ou analisar documentos, isso é um diferencial enorme — especialmente com dados sensíveis.

Custo zero

Sem mensalidade, sem limites

Depois de instalar, você usa à vontade. O modelo fica no seu disco e funciona sem internet. Comparado aos R$ 100-200/mês de APIs cloud, o investimento aqui é zero — só o hardware que você já tem.

Aprendizado real

Entenda LLMs de verdade

Quando você vê a RAM enchendo token por token durante o carregamento, você entende na prática o que "parâmetros", "quantização" e "contexto" significam. Isso vale mais do que qualquer curso teórico.

Reaproveitamento

PC antigo com função nova

O N3700 foi lançado em 2015. Em vez de acumular poeira, ele vira um servidor de IA doméstico funcional. Lento comparado a uma RTX 4090, mas completamente funcional para uso pessoal e aprendizado.

Versatilidade

Texto, imagem e visão no mesmo PC

Com o setup completo deste guia você roda três tipos de IA: TinyLlama para texto, Stable Diffusion 1.5 para imagens e Moondream2 para análise visual. Tudo no mesmo hardware com 2 GB.

Web UI própria

Seu ChatGPT local com Flask

Além dos modelos, este guia ensina a criar uma interface web com chat, histórico, personas e agentes com acesso à internet — tudo rodando no antiX, acessível por qualquer dispositivo da sua rede local.


Como o setup completo funciona

Instalar o antiX Linux como base mínima do sistema

O antiX consome apenas ~200 MB de RAM em repouso — esse é o segredo de tudo. Com Windows você perde 2 GB só pra abrir o desktop. Com Ubuntu, ~1,1 GB. Com antiX, você tem quase 1,8 GB livres para os modelos de IA antes de qualquer coisa. A escolha do Linux não é detalhe — é o que decide se o setup funciona ou trava.

Criar SWAP generoso e configurar o ambiente de ferramentas

Com 2 GB físicos, o SWAP é o que impede os processos de morrer quando a RAM encher. Um arquivo de 8 GB no disco funciona como "RAM lenta de emergência". Além disso, você instala corretamente o Python3, pip3 e os compiladores C++ necessários — e sim, tem truques específicos para o antiX que este guia cobre com comandos exatos.

Instalar cada ferramenta de IA separadamente e expandir com sua Web UI

Ollama para texto, stable-diffusion.cpp para imagens e Moondream2 para visão — cada um tem sua instalação independente. Depois, você constrói uma Web UI com Flask para ter chat com memória, templates personalizados e agentes autônomos rodando localmente. Você não precisa de tudo ao mesmo tempo — instale conforme a necessidade.


Para quem é este guia

🖥️ Donos de netbooks e mini-PCs antigos com processadores Intel Atom, Celeron N3xxx, Pentium N4xxx ou similares — que querem usar IA sem comprar hardware novo e sem pagar por serviços cloud.

🎓 Estudantes e entusiastas de Linux que querem aprender como LLMs, geração de imagem e RAG funcionam na prática, com hardware real e sem gastar nada com servidor, API ou assinatura paga.

🔧 Técnicos em informática e recicladores de hardware que querem mostrar para clientes que um PC velho ainda tem utilidade real em 2025 — e sair com um argumento técnico concreto nas mãos.

🚀 Curiosos que já tentaram instalar IA e travaram o PC — este guia explica exatamente por que travou, mostra os erros reais encontrados durante os testes e como evitá-los da próxima vez com os comandos corretos.


Parte 1 — Escolhendo a distro certa para rodar IA

Antes de instalar qualquer modelo de IA, preciso ser direto com você: a escolha do Linux é mais importante do que a escolha do modelo. Aprendi isso da forma mais dolorosa possível — instalei o Ubuntu 24.04 no N3700, abriu o sistema usando 1,1 GB em repouso, mal consegui abrir o terminal e já estava no limite. Com o antiX, são 200 MB. Essa diferença de 900 MB é literalmente o que decide se o TinyLlama vai carregar ou travar.

Testei ou analisei todas as distros abaixo antes de fechar esta recomendação:

Distro Base Ambiente RAM em repouso RAM livre para IA Recomendação
antiX 23.1 ✅ Debian Stable IceWM / Fluxbox ~200 MB ~1,8 GB Melhor escolha
Puppy Linux Variada JWM ~100 MB ~1,9 GB Funciona, mas setup difícil
Lubuntu 24.04 Ubuntu LXQt ~450 MB ~1,5 GB Funciona com limitações
Linux Lite 7 Ubuntu Xfce ~550 MB ~1,4 GB Apertado
Zorin OS Lite Ubuntu Xfce ~600 MB ~1,3 GB Não recomendado
Ubuntu 24.04 Ubuntu GNOME ~1,1 GB ~0,9 GB ❌ Impraticável
Windows 10 Explorer ~2,0 GB ~0 GB ❌ Impossível

A recomendação deste guia é o antiX 23.1 de 64 bits. Os motivos práticos: é baseado no Debian Stable com suporte de longo prazo, tem drivers excelentes para chipsets Intel Atom/Pentium N, acesso completo ao repositório Debian, e o IceWM consome memória ridiculamente pouca. O Puppy seria ainda mais leve, mas instalar Ollama, compiladores C++ e Flask nele é trabalho manual demais para quem está começando.


Parte 2 — Instalando o antiX Linux passo a passo

2.1 — Baixando a ISO correta

Acesse o site oficial e baixe a versão full 64-bit. Não baixe a versão "base" — ela não inclui gerenciador de pacotes gráfico nem bibliotecas básicas que vamos precisar. A versão "full" inclui tudo que você vai usar neste guia.

https://antixlinux.com/download/

Arquivo esperado: antiX-23.1_x64-full.iso — aproximadamente 1,5 GB. Verifique o SHA256 informado na página de download antes de gravar, especialmente se a ISO vai vir de um espelho (mirror).

2.2 — Criando o pendrive bootável

Use o Balena Etcher (mais simples) ou o Ventoy (mais versátil — permite ter várias ISOs no mesmo pendrive). Aqui vou com o Etcher por ser mais direto para quem está começando.

Com Balena Etcher (Windows / Linux / macOS):

  1. Baixe o Etcher em etcher.balena.io
  2. Abra o programa e clique em "Flash from file"
  3. Selecione a ISO do antiX que você baixou
  4. Escolha seu pendrive em "Select target" — mínimo 4 GB
  5. Clique em "Flash!" e aguarde cerca de 5 minutos
⚠️ Cuidado: O Etcher apaga tudo no pendrive selecionado. Confira que escolheu o dispositivo certo antes de clicar em Flash — ele lista nome e tamanho do drive para facilitar a identificação.

2.3 — Configurando a BIOS para bootar pelo pendrive

Ligue o computador e pressione repetidamente a tecla de boot — geralmente F2, F12, Del ou Esc dependendo do fabricante. Procure a aba "Boot" ou "Boot Priority" e coloque o USB primeiro na lista. Salve com F10 e reinicie.

⚠️ Secure Boot: Em máquinas com UEFI fabricadas após 2012, pode ser necessário desativar o Secure Boot. Procure a opção na aba "Security" da BIOS e mude para "Disabled". O antiX não tem assinatura UEFI por padrão, então sem isso ele simplesmente não inicia.

2.4 — Processo de instalação

O antiX inicia direto em modo Live — você pode testar tudo antes de instalar. O desktop vai parecer diferente do Windows, mas clique com o botão direito para ver o menu de aplicativos. Quando decidir instalar:

  1. Clique duas vezes no ícone "Install antiX" no desktop
  2. Selecione o idioma Português do Brasil
  3. Na etapa de partição:
    • "Use entire disk" — se for o único sistema na máquina
    • "Manual" — se quiser dual-boot com outro SO
  4. Defina nome de usuário e senha. Anote — sem senha root o acesso administrativo complica
  5. Aguarde a cópia dos arquivos (10 a 20 minutos dependendo do disco)
  6. Quando solicitado, reinicie e retire o pendrive

2.5 — Primeira atualização do sistema

Abra o terminal — atalho Ctrl+Alt+T ou clique direito no desktop e escolha Terminal. Execute:

sudo apt update && sudo apt upgrade -y

Isso atualiza todos os pacotes do sistema. Na primeira vez pode demorar de 5 a 15 minutos. Aguarde finalizar completamente antes de prosseguir para as próximas etapas.


Parte 3 — Criando o SWAP de 8 GB (obrigatório para IA)

O SWAP é um espaço no disco que o Linux usa como memória de emergência. Quando a RAM física enche, o sistema começa a mover dados menos usados para o disco. É mais lento — muito mais lento — mas impede que o processo seja encerrado pelo kernel. Para rodar modelos de IA com 2 GB de RAM, sem SWAP o processo simplesmente é morto pelo OOM killer (Out Of Memory) antes de terminar de responder.

Antes de criar, verifique se já existe SWAP ativo no sistema:

free -h

Se a linha Swap: mostrar 0B, você não tem SWAP. Se mostrar algum valor, ainda assim recomendo criar um arquivo maior. Execute os comandos abaixo um de cada vez:

Passo 1 — Criar o arquivo de 8 GB:

sudo fallocate -l 8G /swapfile

Passo 2 — Definir permissões corretas (obrigatório por segurança):

sudo chmod 600 /swapfile

Passo 3 — Formatar como área de SWAP:

sudo mkswap /swapfile

Passo 4 — Ativar imediatamente:

sudo swapon /swapfile

Passo 5 — Tornar permanente após reinicialização:

echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

Passo 6 — Confirmar que funcionou:

free -h

A saída deve mostrar Swap: 8,0G na linha correspondente. Se aparecer, o SWAP está ativo e será reativado automaticamente toda vez que o sistema ligar.

Passo extra — Ajustar o swappiness (muito recomendado):

Por padrão, o Linux começa a usar SWAP quando a RAM chega a 40% de uso. Para um setup dedicado a IA com 2 GB, é muito melhor deixar ele usar a RAM ao máximo antes de recorrer ao disco. O valor 10 significa "use SWAP só quando realmente precisar":

echo 'vm.swappiness=10' | sudo tee -a /etc/sysctl.conf sudo sysctl -p
⚠️ SSD x HD: Se o seu armazenamento é um SSD, o SWAP vai ser mais rápido que num HD, mas SSDs têm vida útil limitada por número de escritas. Para uso intenso e contínuo com modelos de IA (que usam muito SWAP), prefira colocar o arquivo de swap em um HD mecânico se tiver um disponível no sistema.

Parte 4 — Instalando o Ollama e rodando modelos de texto

O Ollama é a ferramenta mais prática para rodar LLMs localmente no Linux. Ele funciona como um gerenciador completo: você instala uma vez e depois baixa, troca e remove modelos com um único comando. Não precisa compilar nada, não precisa configurar CUDA, não precisa resolver dependências manualmente. Para hardware modesto como o N3700, é a escolha certa — leve, direto e funcional.

4.1 — Instalando dependências básicas

Antes de instalar o Ollama, garanta que o sistema tem as ferramentas essenciais. No antiX 23.1 a maioria já vem instalada, mas este comando garante que nada está faltando:

sudo apt install -y curl wget git build-essential

4.2 — Instalando o Ollama

O Ollama tem um script de instalação oficial que baixa o binário, instala no sistema e configura o serviço automaticamente. Execute no terminal:

curl -fsSL https://ollama.com/install.sh | sh

O script faz tudo: baixa o binário (~50 MB), cria o serviço systemd e já deixa o Ollama rodando em background. Aguarde finalizar completamente.

4.3 — Verificando a instalação

Confirme que o Ollama foi instalado corretamente:

ollama --version

Deve retornar algo como ollama version 0.x.x. Se retornou, está pronto. Se der "command not found", feche e reabra o terminal ou execute source ~/.bashrc para recarregar o PATH.

4.4 — Baixando e rodando o primeiro modelo

Para o N3700 com 2 GB de RAM, o modelo mais equilibrado para começar é o SmolLM 135M — ocupa apenas ~90 MB e responde em alguns segundos. O TinyLlama é a segunda opção, com respostas mais completas mas carregamento mais lento.

# Mais rápido — bom para testar se tudo funciona ollama run smollm:135m # Melhor equilíbrio velocidade/qualidade neste hardware ollama run smollm2:1.7b # O clássico — respostas mais completas, mais lento no N3700 ollama run tinyllama

Na primeira execução de cada modelo, o Ollama faz o download automático. Depois ele abre o chat direto no terminal. Digite sua pergunta e pressione Enter. Para sair: Ctrl+D ou digite /bye.

🔧 Erro encontrado no terminal real (linha 20 do histórico):
O comando ollama run sollm "fale em portugues..." foi digitado com erro de digitação — "sollm" em vez de "smollm". O Ollama retorna erro de modelo não encontrado nesse caso.

Correto: ollama run smollm "fale em portugues, o que voce faz" — confirmado nas linhas 22 e 23 do histórico após a correção.
⚠️ Expectativa de velocidade real: No N3700 sem GPU dedicada, o TinyLlama gera em torno de 1 a 3 tokens por segundo. Uma resposta de 100 palavras leva de 1 a 3 minutos. O SmolLM 135M é consideravelmente mais rápido. Isso é completamente normal — você está usando um processador de 6W TDP para fazer o que uma placa RTX faz em segundos. A paciência faz parte do setup.

4.5 — Tabela completa de modelos compatíveis com 2 GB

Nem todo modelo do Ollama roda neste hardware. A tabela abaixo mostra o que foi testado ou verificado como compatível com a limitação de RAM:

Modelo Tamanho RAM usada Velocidade Qualidade Comando
SmolLM 135M ~90 MB ~300 MB ⚡ Muito rápido Básica smollm:135m
SmolLM 360M ~230 MB ~500 MB ⚡ Rápido Boa smollm:360m
SmolLM2 1.7B ✅ ~1,1 GB ~1,3 GB Moderado Muito boa smollm2:1.7b
TinyLlama 1.1B ~640 MB ~900 MB Lento Boa tinyllama
Gemma3 1B ~820 MB ~1,1 GB Lento Muito boa gemma3:1b
Gemma3 4B ~2,5 GB ~3,0 GB Muito lento Excelente ⚠️ Usa SWAP intenso
Llama3 8B ~4,7 GB >5 GB ❌ Não roda

4.6 — Comandos essenciais do Ollama

Estes são os comandos que você vai usar com mais frequência no dia a dia:

# Ver modelos instalados ollama list # Baixar modelo sem abrir o chat ollama pull smollm2:1.7b # Rodar modelo com prompt direto (sem abrir chat interativo) ollama run smollm2:1.7b "explique o que é quantização em LLMs" # Remover modelo para liberar espaço em disco ollama rm tinyllama # Ver informações do serviço ollama ps # Parar o serviço (libera RAM quando não estiver usando) sudo systemctl stop ollama # Iniciar o serviço novamente sudo systemctl start ollama
✅ Dica de produtividade: Se você não vai usar o Ollama por um período, pare o serviço com sudo systemctl stop ollama. Isso libera a RAM que o daemon ocupa em background (~50–100 MB) e pode fazer diferença quando você vai usar o stable-diffusion.cpp logo em seguida.

4.7 — Monitorando o uso de RAM em tempo real

Enquanto o modelo carrega e processa, é útil ver o que está acontecendo com a memória. Abra um segundo terminal e execute:

# Atualiza a cada 2 segundos watch -n 2 free -h

Você vai ver a RAM enchendo durante o carregamento do modelo, estabilizar durante a geração e cair quando você encerrar o chat. Quando o SWAP começar a ser usado, a coluna used do Swap vai aumentar — isso significa que a geração vai ficar mais lenta, mas não vai parar.


Parte 5 — Stable Diffusion em CPU: geração de imagens local

O stable-diffusion.cpp é uma implementação em C++ do Stable Diffusion otimizada para rodar inteiramente em CPU, sem precisar de GPU. É a mesma filosofia do llama.cpp — pega um modelo pesado, aplica quantização agressiva e consegue rodar em hardware modesto através de otimizações de baixo nível. Para o N3700, você vai gerar imagens de 256×256 pixels com o modelo SD 1.5 quantizado em 4 bits.

Vou ser honesto sobre o que esperar: uma imagem de 256×256 com 20 steps leva de 15 a 40 minutos no N3700. Sim, é lento. Mas funciona — testei, a imagem sai no final. A paciência é o único requisito extra que este hardware exige.

⚠️ Leia antes de começar: Esta seção contém correções críticas baseadas nos erros reais encontrados durante os testes no antiX 23.1. Se você seguir tutoriais genéricos da internet sem essas correções, vai travar em pelo menos 3 pontos diferentes. Cada correção está marcada claramente.

5.1 — Instalando as dependências de compilação

O stable-diffusion.cpp precisa ser compilado do zero. Isso exige um conjunto específico de ferramentas e bibliotecas. Execute este comando completo de uma vez:

sudo apt install -y cmake libopenblas-dev libgomp1 git build-essential
🔧 Erros encontrados no terminal real (linhas 25–42):

Erro 1 — Typo no comando apt: As primeiras tentativas usaram sudo pt install (sem o "a") — o shell retornou "comando não encontrado". Sempre use sudo apt install.

Erro 2 — cmake não estava instalado: O cmake não vem por padrão no antiX 23.1 minimal. A linha 40 mostra sudo apt install cmake sendo executado separadamente após o primeiro cmake falhar. O comando acima já inclui o cmake junto com todas as outras dependências — instale tudo de uma vez para evitar esse problema.

5.2 — Clonando o repositório

Volte para a pasta home e clone o repositório oficial com todas as submodules:

cd ~ git clone --recursive https://github.com/leejet/stable-diffusion.cpp cd stable-diffusion.cpp
🔧 Erro encontrado no terminal real (linha 30):
O comando digitado foi cd staable-diffusion.cpp (com duplo "a"). O terminal retornou "No such file or directory". O nome correto do diretório é stable-diffusion.cpp — use Tab para autocomplete e evite erros de digitação em nomes longos.

5.3 — Compilando com suporte a OpenBLAS

O OpenBLAS é uma biblioteca de álgebra linear otimizada especificamente para CPUs Intel. Compilar o stable-diffusion.cpp com esse suporte melhora a velocidade de geração em até 30% em hardware como o N3700. Execute a sequência completa:

mkdir -p build && cd build cmake .. -DGGML_OPENBLAS=ON -DCMAKE_BUILD_TYPE=Release make -j$(nproc)

O -j$(nproc) usa todos os núcleos disponíveis do processador durante a compilação. No N3700 são 4 núcleos — a compilação completa leva de 10 a 25 minutos. Você verá centenas de linhas de compilação passando — é completamente normal.

🔧 Erros encontrados no terminal real (linhas 32–44):

Erro 1 — cmake executado fora da pasta build: As primeiras tentativas (linhas 33 e 37) rodaram o cmake sem entrar na pasta build primeiro. O cmake reclamou que não encontrava os arquivos de configuração. A sequência correta é sempre: criar a pasta build → entrar nela → rodar cmake.

Erro 2 — cmake --build vs make: O post original usava cmake --build . --config Release -j$(nproc), mas o que realmente funcionou no antiX 23.1 (linha 87 do histórico) foi simplesmente make -j$(nproc) após o cmake configurar o projeto. Use make — é mais direto e compatível.

5.4 — Identificando o binário correto após a compilação

Quando a compilação terminar, liste os executáveis que foram gerados:

ls bin

A saída que você vai ver é:

sd-cli sd-server
🔧 Correção crítica (linha 95 do terminal):
Quase todos os tutoriais sobre o stable-diffusion.cpp usam o comando ./bin/sd — mas esse binário não existe nas versões atuais do projeto. O binário correto é ./bin/sd-cli para geração de imagens e ./bin/sd-server para rodar como servidor HTTP. A linha 95 do histórico confirma isso com clareza: sd-cli sd-server. Se você rodar ./bin/sd, vai receber "No such file or directory".

5.5 — Baixando o modelo SD 1.5 quantizado

Você precisa do modelo no formato GGUF, que é o formato comprimido compatível com o stable-diffusion.cpp. O download direto via wget sem autenticação vai falhar no HuggingFace para repositórios que exigem conta. Siga o método correto:

Método 1 — Via huggingface-cli (recomendado, mais confiável):

# Passo 1 — Instalar o pip3 (se não estiver instalado) sudo apt install python3-pip -y # Passo 2 — Instalar o huggingface_hub com a flag correta para Debian/antiX pip3 install --user huggingface_hub --break-system-packages # Passo 3 — Recarregar o PATH (necessário para encontrar o huggingface-cli) source ~/.bashrc # Passo 4 — Criar a pasta dos modelos cd ~ mkdir -p sd-models && cd sd-models # Passo 5 — Baixar o modelo (~850 MB) huggingface-cli download leejet/ggml-stable-diffusion-v1-5 ggml-model-q4_0.gguf

Método 2 — Via wget com URL alternativa (funciona sem token):

cd ~/sd-models # URL alternativa que funciona sem autenticação HuggingFace wget -c https://huggingface.co/leejet/ggml-stable-diffusion-v1-5/resolve/main/ggml-model-q4_0.gguf
🔧 Erros encontrados no terminal real (linhas 47–76):

Erro 1 — wget sem token falhou: A linha 47 tentou baixar o modelo com wget direto e falhou. A linha 49 tentou com token Bearer mas também falhou (token expirado ou URL errada).

Erro 2 — pip não existe no antiX: As linhas 48, 53, 57 e 63 tentaram usar pip install e receberam "bash: pip: comando não encontrado". No antiX (e no Debian em geral) o comando é pip3, nunca apenas pip.

Erro 3 — pip3 sem a flag --break-system-packages: Em sistemas Debian modernos com PEP 668, instalar pacotes Python sem essa flag retorna erro de "externally managed environment". O comando correto é sempre pip3 install --user PACOTE --break-system-packages.

Solução que funcionou (linha 76): pip3 install --user huggingface_hub --break-system-packages seguido de huggingface-cli download leejet/ggml-stable-diffusion-v1-5 ggml-model-q4_0.gguf (linha 59 do histórico, após instalar corretamente).

5.6 — Gerando sua primeira imagem

Com a compilação pronta e o modelo baixado, execute o comando de geração. Atenção ao nome do binário — use sd-cli, não sd:

cd ~/stable-diffusion.cpp/build ./bin/sd-cli \ --model ~/sd-models/ggml-model-q4_0.gguf \ --prompt "a simple landscape with mountains and a river, digital art" \ --negative-prompt "blurry, low quality, ugly, distorted" \ --height 256 \ --width 256 \ --steps 20 \ --seed 42 \ --output ~/imagem_gerada.png

O que cada parâmetro faz:

  • --model — caminho completo para o arquivo .gguf baixado
  • --prompt — descrição do que gerar. Inglês funciona melhor com SD 1.5
  • --negative-prompt — o que você NÃO quer na imagem
  • --height / --width — 256×256 é muito mais rápido que 512×512. Comece aqui
  • --steps — iterações de refinamento. Menos = mais rápido, qualidade menor. 20 é bom equilíbrio
  • --seed — número fixo para reproduzir o mesmo resultado. Troque para variar a geração
  • --output — onde salvar o arquivo PNG gerado

5.7 — Monitorando a geração em tempo real

Enquanto a imagem é gerada, abra um segundo terminal e monitore a RAM:

watch -n 2 free -h

Você vai ver a RAM enchendo enquanto o modelo carrega (~850 MB) e depois estabilizar durante os steps de geração. Se a RAM física encher e o SWAP começar a ser usado, a geração fica ainda mais lenta mas não para — é exatamente para isso que criamos o SWAP de 8 GB no início.

5.8 — Usando o sd-server para geração via HTTP

Além do sd-cli, a compilação gerou também o sd-server, que sobe um servidor HTTP local e permite gerar imagens via requisição web — útil para integrar com scripts ou com a Web UI que vamos criar nas próximas partes:

cd ~/stable-diffusion.cpp/build # Inicia o servidor na porta 8080 ./bin/sd-server \ --model ~/sd-models/ggml-model-q4_0.gguf \ --port 8080 \ --host 0.0.0.0

Com o servidor rodando, você pode gerar imagens via curl de qualquer terminal ou dispositivo na rede local:

# Gerar imagem via curl (de qualquer máquina na rede) curl -X POST http://SEU_IP:8080/txt2img \ -H "Content-Type: application/json" \ -d '{ "prompt": "a futuristic city at night, digital art", "negative_prompt": "blurry, ugly", "width": 256, "height": 256, "sample_steps": 20, "seed": 42 }' \ --output imagem_via_api.png
✅ Dica importante: Não tente rodar o Ollama e o sd-server ao mesmo tempo no N3700 com 2 GB. Cada um já usa boa parte da RAM disponível. Pare o serviço do Ollama antes de iniciar o sd-server: sudo systemctl stop ollama

Parte 6 — Moondream2: análise de imagens com IA local

O Moondream2 é um VLM — Vision Language Model — com apenas 1,6 bilhões de parâmetros. Ele consegue olhar para uma imagem e fazer coisas que antes exigiam modelos enormes: descrever o que vê, responder perguntas sobre o conteúdo, identificar objetos, extrair texto presente na imagem e até interpretar gráficos simples. É um dos únicos modelos visuais que roda de forma aceitável dentro de 2 GB de RAM — e funciona 100% offline, sem mandar nada para nenhuma API.

A combinação mais interessante deste setup é gerar uma imagem com o stable-diffusion.cpp e depois analisá-la com o Moondream2 — dois modelos de IA diferentes trabalhando em sequência, tudo no mesmo N3700 de 2 GB. Lento, mas extraordinariamente capaz para o hardware.

6.1 — Instalando Python3 e pip3 corretamente no antiX

O antiX 23.1 já vem com Python 3, mas pode não ter o pip3 e o venv instalados. Execute:

sudo apt install -y python3 python3-pip python3-venv

Confirme que tudo está funcionando:

python3 --version pip3 --version

Ambos devem retornar versões. Se o pip3 der "command not found" após a instalação, feche e reabra o terminal ou execute source ~/.bashrc.

🔧 Regra crítica para pip3 no antiX / Debian moderno:
Em qualquer instalação Python em Debian, Ubuntu ou antiX modernos, sempre use a flag --break-system-packages junto com --user ao instalar pacotes fora de um virtualenv. Sem essa flag, o pip3 vai recusar a instalação com erro de "externally managed environment" (PEP 668).

Exemplo correto: pip3 install --user PACOTE --break-system-packages

6.2 — Criando um ambiente virtual isolado (virtualenv)

Para projetos Python com múltiplas dependências como o Moondream2, sempre crie um virtualenv. Isso isola as bibliotecas do projeto do resto do sistema, evita conflitos entre versões e facilita remover tudo se necessário:

cd ~ python3 -m venv moondream-env source moondream-env/bin/activate

Quando o virtualenv está ativo, o terminal mostra (moondream-env) no início da linha. Isso confirma que você está dentro do ambiente isolado. Dentro do virtualenv, o comando pip (sem o "3") funciona normalmente e você não precisa da flag --break-system-packages.

6.3 — Instalando o Moondream2 e dependências

Com o virtualenv ativo, instale o pacote principal e a biblioteca de imagens:

# Certifique-se que o virtualenv está ativo (deve mostrar "(moondream-env)" no prompt) source ~/moondream-env/bin/activate pip install moondream Pillow

O download das dependências pode levar alguns minutos dependendo da sua conexão. O próprio modelo (~1,7 GB) será baixado automaticamente na primeira execução do script — não durante o pip install.

6.4 — Criando o script de análise de imagem

Crie o arquivo Python com o nano:

nano ~/analisa_imagem.py

Cole o código abaixo integralmente. Para colar no terminal do antiX use Ctrl+Shift+V. Para salvar: Ctrl+O → Enter → Ctrl+X:

import moondream as md from PIL import Image import sys import os # ─── Verificação de argumentos ─────────────────────────────── if len(sys.argv) < 2: print("Uso: python3 analisa_imagem.py /caminho/para/imagem.jpg") print("Exemplo: python3 analisa_imagem.py ~/imagem_gerada.png") sys.exit(1) caminho_imagem = sys.argv[1] if not os.path.exists(caminho_imagem): print(f"Erro: arquivo '{caminho_imagem}' não encontrado.") sys.exit(1) # ─── Carregar modelo ───────────────────────────────────────── # Na primeira execução baixa ~1,7 GB automaticamente # Nas próximas usa o cache local em ~/.cache/moondream print("Carregando modelo Moondream2... aguarde (pode demorar na primeira vez).") try: model = md.vl(model="moondream-2b-int8.mf") except Exception as e: print(f"Erro ao carregar o modelo: {e}") sys.exit(1) # ─── Carregar e codificar a imagem ─────────────────────────── print(f"\nAnalisando: {caminho_imagem}") try: imagem = Image.open(caminho_imagem) imagem_enc = model.encode_image(imagem) except Exception as e: print(f"Erro ao abrir a imagem: {e}") sys.exit(1) # ─── Gerar descrição automática ────────────────────────────── print("\nGerando descrição da imagem...") try: descricao = model.caption(imagem_enc)["caption"] print("\n📷 Descrição:") print(descricao) except Exception as e: print(f"Erro na descrição: {e}") # ─── Loop interativo de perguntas ──────────────────────────── print("\n💬 Faça perguntas sobre a imagem.") print(" Digite 'sair' ou pressione Ctrl+C para encerrar.\n") while True: try: pergunta = input("Sua pergunta: ").strip() if not pergunta: continue if pergunta.lower() in ["sair", "exit", "quit", "q"]: print("Encerrando. Até mais!") break resposta = model.query(imagem_enc, pergunta)["answer"] print(f"Resposta: {resposta}\n") except KeyboardInterrupt: print("\nEncerrado pelo usuário.") break except Exception as e: print(f"Erro na pergunta: {e}")

6.5 — Rodando a análise de imagem

Ative o virtualenv e execute o script passando o caminho de qualquer imagem JPG ou PNG:

# Ativar o ambiente virtual source ~/moondream-env/bin/activate # Analisar a imagem gerada pelo Stable Diffusion na parte anterior python3 ~/analisa_imagem.py ~/imagem_gerada.png # Ou qualquer outra imagem do sistema python3 ~/analisa_imagem.py ~/Downloads/foto.jpg

Na primeira execução, o Moondream vai baixar automaticamente o modelo (~1,7 GB) para o cache em ~/.cache/moondream. Aguarde o download completo. Nas execuções seguintes ele carrega direto do cache, sem precisar de internet. A análise de uma imagem 256×256 leva de 1 a 3 minutos no N3700.

6.6 — Exemplos práticos de perguntas ao Moondream2

Depois que o modelo carrega e descreve a imagem, você entra no modo interativo. Alguns exemplos do que você pode perguntar:

Pergunta Tipo de análise Funciona bem?
"What colors are dominant in this image?" Análise de cor ✅ Sim
"Are there any people in this image?" Detecção de objetos ✅ Sim
"What text can you read in this image?" OCR / extração de texto ✅ Sim (texto legível)
"Describe the mood or atmosphere" Análise emocional/estética ✅ Razoável
"What is the main subject?" Identificação de foco ✅ Sim
"Is this a real photo or digital art?" Classificação de tipo ⚠️ Parcial
Perguntas muito complexas em português ⚠️ Inglês funciona melhor

6.7 — Liberando RAM entre as ferramentas

Com 2 GB de RAM, você não consegue rodar Ollama + Stable Diffusion + Moondream2 ao mesmo tempo. Use um de cada vez e gerencie a memória ativamente:

# Antes de usar o Moondream2, pare o Ollama sudo systemctl stop ollama # Verificar RAM disponível antes de começar free -h # Após terminar de usar o Moondream, sair do virtualenv deactivate # Reiniciar o Ollama se quiser voltar ao chat de texto sudo systemctl start ollama
⚠️ Atenção com o cache do Moondream: O modelo ocupa ~1,7 GB no disco em ~/.cache/moondream. Somado ao modelo do SD.cpp (~850 MB) e aos modelos do Ollama (variável), você pode encher rapidamente um disco pequeno. Verifique o espaço disponível periodicamente com df -h ~.

Parte 7 — Acessar o Ollama de outra máquina na rede local

Conteúdo novo

Por padrão, o Ollama escuta apenas em 127.0.0.1:11434 — ou seja, só a própria máquina consegue acessar. Se você quiser usar o chat a partir de outro computador, notebook ou celular conectado na mesma rede Wi-Fi ou cabo, precisa liberar o Ollama para escutar em todas as interfaces de rede. É um ajuste simples com duas abordagens: temporária ou permanente.

7.1 — Liberando o acesso na rede (método temporário)

Para testar sem alterar configurações permanentes, pare o serviço e suba manualmente com a variável de ambiente configurada:

# Parar o serviço atual sudo systemctl stop ollama # Subir manualmente escutando em todas as interfaces OLLAMA_HOST=0.0.0.0 ollama serve

Enquanto esse terminal estiver aberto, o Ollama aceita conexões de qualquer dispositivo na rede local. Fechar o terminal encerra o serviço.

7.2 — Liberando o acesso de forma permanente

Para que o Ollama sempre inicie escutando na rede, edite o arquivo de serviço systemd:

# Abrir o arquivo de configuração do serviço sudo nano /etc/systemd/system/ollama.service

Encontre a seção [Service] e adicione a linha de Environment logo abaixo dela:

[Service] Environment="OLLAMA_HOST=0.0.0.0" ExecStart=/usr/local/bin/ollama serve # ... resto do arquivo permanece igual

Salve com Ctrl+O → Enter → Ctrl+X e recarregue o serviço:

sudo systemctl daemon-reload sudo systemctl restart ollama # Confirmar que o serviço reiniciou corretamente sudo systemctl status ollama

7.3 — Descobrindo o IP da máquina antiX

Para acessar de outro dispositivo, você precisa saber o IP local da máquina com o Ollama:

ip a

Procure uma linha como inet 192.168.1.10/24 na interface eth0 (cabo) ou wlan0 (Wi-Fi). O IP nesse exemplo seria 192.168.1.10. Anote — você vai usar bastante nas próximas seções.

7.4 — Testando o acesso de outra máquina

De qualquer outro computador ou celular na mesma rede, teste no terminal ou navegador:

# Via curl (de outro terminal Linux/Mac/WSL) curl http://192.168.1.10:11434/api/tags # Via navegador — abra este endereço no browser de qualquer dispositivo da rede # http://192.168.1.10:11434

Se retornar um JSON com a lista de modelos instalados, está funcionando.

7.5 — Liberando a porta no firewall (se necessário)

Se o acesso não funcionar mesmo com o Ollama configurado, o firewall pode estar bloqueando. No antiX com ufw:

# Verificar se o ufw está ativo sudo ufw status # Se estiver ativo, liberar a porta do Ollama sudo ufw allow 11434 # Confirmar sudo ufw status verbose
⚠️ Segurança importante: Abrir o Ollama na rede local significa que qualquer dispositivo conectado ao seu roteador pode usar seus modelos de IA sem autenticação. Em redes domésticas isso é geralmente aceitável. Em redes corporativas ou públicas, considere restringir por IP no firewall ou usar um proxy com autenticação na frente do Ollama.

Parte 8 — Templates personalizados com Modelfile

Conteúdo novo

O Ollama permite criar modelos personalizados usando um Modelfile — um arquivo de configuração que define o modelo base, o prompt de sistema fixo, o formato de resposta e o "comportamento" do assistente. É como criar uma persona especializada em cima de um modelo existente. Você define uma vez e chama sempre com o mesmo nome customizado.

Um caso de uso prático: criar um assistente que sempre responde em português, sempre segue um formato específico de resposta e sempre assume um perfil técnico — sem precisar repetir isso em cada prompt manualmente.

8.1 — Criando seu primeiro Modelfile

Crie um arquivo chamado Modelfile em qualquer pasta:

nano ~/Modelfile

Cole o conteúdo abaixo. Este exemplo cria um assistente técnico que responde em português e sempre segue uma estrutura de 3 partes:

FROM smollm2:1.7b SYSTEM Você é um assistente técnico especializado. Sempre responda em português do Brasil. Siga obrigatoriamente este formato em todas as respostas: **Resumo:** uma frase curta respondendo diretamente **Explicação:** o detalhamento técnico completo **Próximo passo:** uma ação prática que o usuário pode tomar agora Se alguma informação estiver faltando para responder bem, peça antes de inventar. PARAMETER temperature 0.7 PARAMETER num_ctx 2048

O que cada instrução faz:

  • FROM — modelo base que você já tem instalado no Ollama
  • SYSTEM — prompt fixo que define o comportamento. O modelo lê isso antes de qualquer pergunta
  • PARAMETER temperature — controla criatividade. 0.0 = mais determinístico, 1.0 = mais criativo
  • PARAMETER num_ctx — tamanho da janela de contexto em tokens. 2048 é seguro para 2 GB

8.2 — Criando o modelo customizado

ollama create assistente-tecnico -f ~/Modelfile

O Ollama vai processar o Modelfile e criar o modelo com o nome assistente-tecnico — que agora aparece na sua lista de modelos local:

ollama list

8.3 — Usando o modelo customizado

# Abrir chat interativo com o modelo personalizado ollama run assistente-tecnico # Ou com prompt direto ollama run assistente-tecnico "como funciona o SWAP no Linux?"

8.4 — Exemplos de Modelfiles especializados

Você pode criar quantos modelos customizados quiser — cada um com um perfil diferente. Alguns exemplos úteis para o dia a dia:

Assistente de código Python:

FROM smollm2:1.7b SYSTEM Você é um especialista em Python. Quando gerar código: - Sempre inclua comentários explicativos em português - Sempre inclua tratamento de erros (try/except) - Sempre explique o que o código faz antes de mostrá-lo - Nunca use bibliotecas externas sem avisar que precisam ser instaladas PARAMETER temperature 0.3

Tradutor técnico EN↔PT:

FROM smollm2:1.7b SYSTEM Você é um tradutor técnico especializado em tecnologia e programação. Ao receber texto em inglês, traduza para português técnico brasileiro. Ao receber texto em português, traduza para inglês técnico. Preserve termos técnicos consagrados (como "commit", "deploy", "pipeline") quando não há tradução estabelecida em português. Nunca adicione explicações — só a tradução. PARAMETER temperature 0.1

Revisor de texto:

FROM smollm2:1.7b SYSTEM Você é um revisor de texto profissional para conteúdo técnico de blog. Ao receber um texto, identifique e corrija: 1. Erros gramaticais e ortográficos 2. Frases confusas ou longas demais 3. Repetições desnecessárias 4. Linguagem robótica ou corporativa Retorne o texto corrigido com as mudanças destacadas entre [colchetes]. PARAMETER temperature 0.2

8.5 — Gerenciando os modelos customizados

# Listar todos os modelos (incluindo os customizados) ollama list # Ver detalhes de um modelo customizado ollama show assistente-tecnico # Remover modelo customizado para liberar espaço ollama rm assistente-tecnico # Recriar após editar o Modelfile ollama create assistente-tecnico -f ~/Modelfile

8.6 — Simular "campos" com placeholders no prompt

Uma técnica prática é padronizar seus prompts com marcadores substituíveis — uma espécie de formulário em texto puro. Crie um script bash simples para isso:

nano ~/prompt_form.sh
#!/bin/bash # Script de formulário de prompt para Ollama — @CanalQb echo "=== Formulário de Prompt ===" read -p "Tarefa: " tarefa read -p "Contexto: " contexto read -p "Formato da resposta: " formato PROMPT="Tarefa: $tarefa Contexto: $contexto Formato desejado: $formato Responda seguindo exatamente o formato solicitado." echo "" echo "Enviando para o Ollama..." echo "" ollama run assistente-tecnico "$PROMPT"
chmod +x ~/prompt_form.sh ~/prompt_form.sh

Agora você tem um "formulário de terminal" que captura tarefa, contexto e formato antes de enviar para o modelo. É simples, mas resolve 80% dos casos de uso do dia a dia sem precisar de interface gráfica.


Parte 9 — Web UI com Flask: seu ChatGPT local

Conteúdo novo

Até aqui você usou o Ollama pelo terminal. Funciona, mas não é prático para o dia a dia — especialmente se você quiser acessar de outro dispositivo da rede ou manter histórico de conversas. A solução é criar uma interface web simples com Flask, o microframework Python mais leve e direto que existe. Em menos de 100 linhas você tem um chat funcional com memória, personas e templates salvos — rodando no antiX e acessível de qualquer browser na sua rede local.

9.1 — Instalando o Flask e dependências

Use o virtualenv que já criamos (ou crie um novo separado para o projeto web):

# Criar virtualenv dedicado para a Web UI cd ~ python3 -m venv webui-env source webui-env/bin/activate # Instalar Flask e requests pip install flask requests

9.2 — Estrutura do projeto

Crie a estrutura de pastas do projeto:

mkdir -p ~/ollama-webui/templates cd ~/ollama-webui

9.3 — Criando o arquivo de personas

As personas são perfis de assistente com instruções fixas. Crie o arquivo JSON:

nano ~/ollama-webui/personas.json
{ "Assistente Geral": "Você é um assistente prestativo que responde sempre em português do Brasil de forma clara e direta.", "Dev Python": "Você é um especialista em Python. Sempre inclua código com comentários em português e tratamento de erros.", "Tradutor EN-PT": "Traduza o texto recebido para português técnico brasileiro. Preserve termos técnicos consagrados.", "Revisor de Blog": "Você revisa textos de blog técnico. Corrija gramática, simplifique frases longas e elimine linguagem robótica.", "Analista Linux": "Você é um especialista em Linux/Debian. Explique comandos, diagnósticos e configurações de forma prática." }

9.4 — Criando o arquivo de templates de prompt

nano ~/ollama-webui/templates_prompt.json
{ "Resumo de texto": "Resuma o seguinte texto em no máximo 5 pontos principais:\n\n{{texto}}", "Explicação técnica": "Explique de forma clara e prática, com exemplos reais:\n\n{{conceito}}", "Revisão de código": "Revise este código, identifique problemas e sugira melhorias:\n\n{{codigo}}", "Tradução EN→PT": "Traduza para português técnico brasileiro:\n\n{{texto_ingles}}", "Geração de script bash": "Crie um script bash que faça o seguinte:\n\n{{descricao}}\n\nInclua comentários e tratamento de erros." }

9.5 — Backend principal (app.py)

nano ~/ollama-webui/app.py
from flask import Flask, render_template, request, jsonify, session import requests import json import os import uuid app = Flask(__name__) app.secret_key = os.urandom(24) OLLAMA_URL = "http://localhost:11434/api/generate" OLLAMA_CHAT = "http://localhost:11434/api/chat" MODEL_NAME = "assistente-tecnico" # ou smollm2:1.7b se não criou Modelfile # ─── Helpers para carregar JSONs ───────────────────────────── def load_json(filepath, default={}): try: with open(filepath) as f: return json.load(f) except Exception: return default # ─── Memória de conversa por sessão (em RAM) ───────────────── conversations = {} def get_history(session_id): return conversations.get(session_id, []) def add_message(session_id, role, content): if session_id not in conversations: conversations[session_id] = [] conversations[session_id].append({"role": role, "content": content}) # Manter apenas as últimas 20 mensagens para não explodir a RAM if len(conversations[session_id]) > 20: conversations[session_id] = conversations[session_id][-20:] # ─── Rota principal ────────────────────────────────────────── @app.route("/") def index(): personas = load_json("personas.json") templates_prompt = load_json("templates_prompt.json") if "session_id" not in session: session["session_id"] = str(uuid.uuid4()) return render_template( "index.html", personas=personas, templates_prompt=templates_prompt, history=get_history(session["session_id"]) ) # ─── API de chat ───────────────────────────────────────────── @app.route("/api/chat", methods=["POST"]) def chat(): data = request.json or {} message = data.get("message", "").strip() persona = data.get("persona", "Assistente Geral") session_id = session.get("session_id", "default") if not message: return jsonify({"error": "Mensagem vazia"}), 400 personas = load_json("personas.json") system = personas.get(persona, "Você é um assistente útil.") add_message(session_id, "user", message) # Montar histórico para o Ollama (formato /api/chat) messages = [{"role": "system", "content": system}] for msg in get_history(session_id): messages.append({"role": msg["role"], "content": msg["content"]}) try: res = requests.post( OLLAMA_CHAT, json={"model": MODEL_NAME, "messages": messages, "stream": False}, timeout=300 ) res.raise_for_status() answer = res.json().get("message", {}).get("content", "Sem resposta.") except requests.exceptions.ConnectionError: return jsonify({"error": "Ollama não está rodando. Execute: sudo systemctl start ollama"}), 503 except Exception as e: return jsonify({"error": str(e)}), 500 add_message(session_id, "assistant", answer) return jsonify({"response": answer, "persona": persona}) # ─── Limpar histórico da sessão ────────────────────────────── @app.route("/api/clear", methods=["POST"]) def clear_history(): session_id = session.get("session_id", "default") conversations.pop(session_id, None) return jsonify({"status": "ok"}) # ─── Listar modelos disponíveis ────────────────────────────── @app.route("/api/models") def list_models(): try: res = requests.get("http://localhost:11434/api/tags", timeout=5) models = [m["name"] for m in res.json().get("models", [])] return jsonify({"models": models}) except Exception: return jsonify({"models": [], "error": "Ollama offline"}) if __name__ == "__main__": print("=== @CanalQb Ollama Web UI ===") print("Acesse: http://localhost:5000") print("Rede local: http://SEU_IP:5000") app.run(host="0.0.0.0", port=5000, debug=False)

9.6 — Interface HTML (templates/index.html)

nano ~/ollama-webui/templates/index.html
<!DOCTYPE html> <html lang="pt-BR"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>Ollama Web UI — @CanalQb</title> <style> * { box-sizing: border-box; margin: 0; padding: 0; } body { font-family: 'Segoe UI', sans-serif; background: #0f172a; color: #e2e8f0; height: 100vh; display: flex; flex-direction: column; } header { background: #1e293b; padding: 14px 20px; display: flex; align-items: center; gap: 12px; border-bottom: 1px solid #334155; flex-shrink: 0; } header h1 { font-size: 1em; color: #28a745; } .controls { display: flex; gap: 8px; flex-wrap: wrap; } select { background: #334155; color: #e2e8f0; border: 1px solid #475569; border-radius: 6px; padding: 6px 10px; font-size: .85em; cursor: pointer; min-height: 36px; } .chat-area { flex: 1; overflow-y: auto; padding: 20px; display: flex; flex-direction: column; gap: 12px; } .msg { max-width: 80%; padding: 12px 16px; border-radius: 12px; line-height: 1.6; font-size: .92em; white-space: pre-wrap; word-break: break-word; } .msg-user { background: #1d4ed8; color: #fff; align-self: flex-end; border-radius: 12px 12px 2px 12px; } .msg-assistant { background: #1e293b; color: #e2e8f0; align-self: flex-start; border-radius: 2px 12px 12px 12px; border: 1px solid #334155; } .msg-system { background: rgba(40,167,69,.15); color: #86efac; align-self: center; font-size: .82em; padding: 6px 14px; border-radius: 20px; } .input-area { background: #1e293b; border-top: 1px solid #334155; padding: 14px 16px; display: flex; gap: 10px; flex-shrink: 0; } textarea { flex: 1; background: #334155; color: #e2e8f0; border: 1px solid #475569; border-radius: 8px; padding: 10px 14px; font-size: .92em; resize: none; min-height: 44px; max-height: 120px; font-family: inherit; } textarea:focus { outline: none; border-color: #28a745; } button { background: #28a745; color: #fff; border: none; border-radius: 8px; padding: 10px 20px; font-weight: 700; cursor: pointer; min-height: 44px; min-width: 44px; font-size: .92em; transition: background .2s; } button:hover { background: #1a7d35; } button:disabled { background: #475569; cursor: not-allowed; } .btn-clear { background: transparent; color: #94a3b8; border: 1px solid #475569; font-size: .82em; padding: 6px 12px; min-height: 36px; } .btn-clear:hover { background: #334155; color: #e2e8f0; } .typing { color: #94a3b8; font-size: .85em; font-style: italic; padding: 4px 16px; } @media (max-width: 600px) { .msg { max-width: 95%; } header { flex-direction: column; align-items: flex-start; gap: 8px; } } </style> </head> <body> <header> <h1>🤖 Ollama Web UI — @CanalQb</h1> <div class="controls"> <select id="sel-persona" aria-label="Selecionar persona"> {% for nome in personas %} <option value="{{ nome }}">{{ nome }}</option> {% endfor %} </select> <select id="sel-template" aria-label="Selecionar template" onchange="applyTemplate()"> <option value="">— Template —</option> {% for nome, tmpl in templates_prompt.items() %} <option value="{{ tmpl | e }}">{{ nome }}</option> {% endfor %} </select> <button class="btn-clear" onclick="clearHistory()">🗑 Limpar</button> </div> </header> <div class="chat-area" id="chat"> <div class="msg msg-system">Chat iniciado · {{ history | length }} mensagens no histórico</div> {% for msg in history %} <div class="msg msg-{{ msg.role }}">{{ msg.content }}</div> {% endfor %} </div> <div id="typing" class="typing" style="display:none">⏳ Gerando resposta...</div> <div class="input-area"> <textarea id="input" placeholder="Digite sua mensagem... (Enter envia, Shift+Enter nova linha)" rows="1" aria-label="Campo de mensagem"></textarea> <button id="btn-send" onclick="sendMessage()" aria-label="Enviar mensagem">Enviar</button> </div> <script> const chatEl = document.getElementById('chat'); const inputEl = document.getElementById('input'); const btnSend = document.getElementById('btn-send'); const typingEl = document.getElementById('typing'); function scrollBottom() { chatEl.scrollTop = chatEl.scrollHeight; } function addMessage(role, content) { const div = document.createElement('div'); div.className = 'msg msg-' + role; div.textContent = content; chatEl.appendChild(div); scrollBottom(); } function applyTemplate() { const sel = document.getElementById('sel-template'); if (sel.value) { inputEl.value = sel.value; inputEl.focus(); sel.value = ''; } } async function sendMessage() { const msg = inputEl.value.trim(); const persona = document.getElementById('sel-persona').value; if (!msg) return; addMessage('user', msg); inputEl.value = ''; btnSend.disabled = true; typingEl.style.display = 'block'; scrollBottom(); try { const res = await fetch('/api/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message: msg, persona: persona }) }); const data = await res.json(); if (data.error) { addMessage('system', '❌ Erro: ' + data.error); } else { addMessage('assistant', data.response); } } catch (e) { addMessage('system', '❌ Falha na conexão com o servidor.'); } finally { btnSend.disabled = false; typingEl.style.display = 'none'; } } async function clearHistory() { if (!confirm('Limpar todo o histórico desta sessão?')) return; await fetch('/api/clear', { method: 'POST' }); chatEl.innerHTML = '<div class="msg msg-system">Histórico limpo ✓</div>'; } inputEl.addEventListener('keydown', e => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMessage(); } }); scrollBottom(); </script> </body> </html>

9.7 — Iniciando a Web UI

# Ativar o virtualenv source ~/webui-env/bin/activate # Entrar na pasta do projeto cd ~/ollama-webui # Iniciar o servidor python3 app.py

O terminal vai mostrar o endereço de acesso. Abra no navegador do mesmo PC ou de qualquer dispositivo na rede:

# No mesmo computador http://localhost:5000 # De outro dispositivo na rede local (substitua pelo IP real) http://192.168.1.10:5000

Parte 10 — Agentes com internet e leitura de arquivos

Conteúdo novo

O passo final é dar ao seu assistente local acesso ao mundo externo: pesquisar na internet, ler arquivos do disco e executar comandos do sistema. Isso transforma o chat simples em um agente capaz de tomar ações reais — não apenas gerar texto.

10.1 — Instalando dependências dos agentes

source ~/webui-env/bin/activate pip install beautifulsoup4

10.2 — Criando o módulo de agentes (agents.py)

nano ~/ollama-webui/agents.py
import subprocess import requests import os from bs4 import BeautifulSoup # ─── Tool 1: Busca na web via DuckDuckGo ───────────────────── def search_web(query): """Busca os 5 primeiros resultados no DuckDuckGo sem tracker.""" try: url = f"https://duckduckgo.com/html/?q={requests.utils.quote(query)}" headers = {"User-Agent": "Mozilla/5.0 (X11; Linux x86_64)"} res = requests.get(url, headers=headers, timeout=10) soup = BeautifulSoup(res.text, "html.parser") results = [] for a in soup.select(".result__a")[:5]: title = a.get_text(strip=True) href = a.get("href", "") results.append(f"• {title}\n {href}") return "\n\n".join(results) if results else "Nenhum resultado encontrado." except Exception as e: return f"Erro na busca: {e}" # ─── Tool 2: Ler arquivo de texto ──────────────────────────── def read_file(path): """Lê e retorna o conteúdo de um arquivo de texto.""" path = path.strip().replace("~", os.path.expanduser("~")) try: if not os.path.exists(path): return f"Arquivo não encontrado: {path}" if os.path.getsize(path) > 50_000: return "Arquivo muito grande (>50KB). Leia por partes." with open(path, "r", encoding="utf-8", errors="replace") as f: return f.read() except Exception as e: return f"Erro ao ler arquivo: {e}" # ─── Tool 3: Executar comando do sistema ───────────────────── COMANDOS_PERMITIDOS = ["ls", "pwd", "free", "df", "uname", "cat", "echo", "date", "whoami", "ip", "ping", "ollama"] def run_command(cmd): """Executa um comando shell. Restrito a comandos da lista segura.""" cmd = cmd.strip() base_cmd = cmd.split()[0] if cmd else "" if base_cmd not in COMANDOS_PERMITIDOS: return f"Comando '{base_cmd}' não permitido. Permitidos: {', '.join(COMANDOS_PERMITIDOS)}" try: result = subprocess.run( cmd, shell=True, capture_output=True, text=True, timeout=10 ) output = result.stdout or result.stderr or "(sem saída)" return output[:2000] # limitar tamanho da resposta except subprocess.TimeoutExpired: return "Timeout: comando demorou mais de 10 segundos." except Exception as e: return f"Erro ao executar: {e}" # ─── Roteador de agente ────────────────────────────────────── def agent_router(message): """ Detecta comandos especiais na mensagem e roteia para a tool correta. Retorna None se não for um comando de agente (resposta normal via LLM). Comandos reconhecidos: buscar: → pesquisa DuckDuckGo arquivo: → lê arquivo do disco comando: → executa comando permitido """ msg = message.strip() lower = msg.lower() if lower.startswith("buscar:"): query = msg[7:].strip() if not query: return "Informe o que buscar. Exemplo: buscar: ollama linux 2025" return f"🌐 Resultados para '{query}':\n\n{search_web(query)}" if lower.startswith("arquivo:"): path = msg[8:].strip() if not path: return "Informe o caminho. Exemplo: arquivo: ~/meu_historico.txt" conteudo = read_file(path) return f"📄 Conteúdo de {path}:\n\n{conteudo}" if lower.startswith("comando:"): cmd = msg[8:].strip() if not cmd: return "Informe o comando. Exemplo: comando: free -h" return f"💻 Saída do comando '{cmd}':\n\n{run_command(cmd)}" return None # Sem comando especial — usa LLM normalmente

10.3 — Integrando os agentes no app.py

Edite o app.py para importar e usar o roteador de agentes na rota de chat:

# No topo do app.py, adicione o import: from agents import agent_router

E dentro da função chat(), antes da chamada ao Ollama, adicione:

# Verificar se é um comando de agente primeiro tool_result = agent_router(message) if tool_result: add_message(session_id, "user", message) add_message(session_id, "assistant", tool_result) return jsonify({"response": tool_result, "persona": "Agente"})

10.4 — Como usar os agentes no chat

Na interface web, use estes prefixos para acionar os agentes diretamente:

Prefixo O que faz Exemplo
buscar: Pesquisa no DuckDuckGo e retorna os 5 primeiros resultados buscar: ollama linux pc fraco 2025
arquivo: Lê e exibe o conteúdo de qualquer arquivo de texto do disco arquivo: ~/meu_historico.txt
comando: Executa um comando shell seguro e retorna a saída comando: free -h
(sem prefixo) Resposta normal via LLM com a persona selecionada como funciona o SWAP no Linux?
⚠️ Segurança dos agentes: O módulo já tem proteção contra comandos perigosos — apenas os da lista COMANDOS_PERMITIDOS são executados. Nunca adicione rm, sudo, chmod ou dd a essa lista. Se for expor a Web UI na internet (e não apenas na rede local), adicione autenticação antes de disponibilizar o endpoint de agentes.

10.5 — Resumo do que você construiu

🧠 Arquitetura completa do setup:

Navegador (qualquer dispositivo na rede local) ↓ Flask Web UI (porta 5000) ↓ Agent Router ├── buscar: → DuckDuckGo scraper ├── arquivo: → leitura de arquivos locais ├── comando: → shell com lista segura └── (chat) → Ollama API (porta 11434) ↓ Modelo LLM local (smollm2:1.7b / tinyllama / gemma3:1b) ↓ Resposta no chat

ℹ️ Nota Técnica: Todos os scripts, comandos e configurações deste guia são para fins educacionais e uso pessoal. Teste sempre em ambiente controlado antes de aplicar em produção. O autor não se responsabiliza por perda de dados durante particionamento/instalação ou por uso indevido dos agentes em redes não autorizadas.

Recursos e ferramentas oficiais

🐧 antiX Linux
antixlinux.com/download
ISO full 64-bit — base Debian Stable
🤖 Ollama
ollama.com
Gerenciador de LLMs local para Linux
🖼️ stable-diffusion.cpp
github.com/leejet/stable-diffusion.cpp
SD em CPU via C++ — binário: sd-cli
👁️ Moondream2
huggingface.co/vikhyatk/moondream2
VLM de 1,6B para análise de imagens
💾 Balena Etcher
etcher.balena.io
Gravação de ISO em pendrive
📦 Modelos GGUF — HuggingFace
HF — SD 1.5 GGUF
Modelos quantizados para CPU
🐍 Flask
flask.palletsprojects.com
Microframework web Python
📋 Ollama — Biblioteca de modelos
ollama.com/library
Catálogo completo de modelos disponíveis
▶ Ver tutoriais no @CanalQb    📚 Mais tutoriais no blog

#Linux #IA #InteligênciaArtificial #PCFraco #LLM #StableDiffusion #antiX #Ollama #Flask #Agentes #ChatGPTLocal
@CanalQb



Troubleshooting — Erros comuns e soluções exatas

Compilei aqui todos os erros que aparecem com maior frequência neste setup — seja pelos meus próprios testes, seja por relatos de quem tentou seguir tutoriais incompletos. Se travou em algum ponto, a resposta provavelmente está nesta seção.

Sintoma / Erro Causa provável Solução
ollama: command not found PATH não atualizado após instalação source ~/.bashrc ou feche e reabra o terminal
Error: model not found no ollama run Nome do modelo com typo ou modelo não baixado Verifique com ollama list. Use Tab para autocomplete após ollama run
Processo morto pelo sistema durante geração OOM killer — RAM esgotada sem SWAP Crie o SWAP de 8 GB conforme a Parte 3. Confirme com free -h
cmake: command not found cmake não instalado no antiX sudo apt install -y cmake
./bin/sd: No such file or directory Binário correto é sd-cli, não sd Use ./bin/sd-cli. Confirme com ls bin
pip: command not found No antiX/Debian o binário é pip3 Use pip3 sempre. Instale com sudo apt install python3-pip -y
Erro "externally managed environment" no pip3 PEP 668 — Debian bloqueia instalação global Adicione --user --break-system-packages ou use virtualenv
Download do HuggingFace falha com 401 Repositório requer autenticação Use huggingface-cli login com token ou tente URL alternativa com wget -c
Web UI retorna "Ollama não está rodando" Serviço Ollama parado sudo systemctl start ollama e aguarde 5 segundos
Web UI acessível localmente mas não da rede Ollama escutando só em 127.0.0.1 Configure OLLAMA_HOST=0.0.0.0 conforme a Parte 7
Geração de imagem demora mais de 1 hora SWAP sendo usado intensamente (HD lento) Reduza para --steps 10 e resolução --width 128 --height 128
No module named 'moondream' Virtualenv não ativado source ~/moondream-env/bin/activate antes de executar
Moondream não baixa o modelo automaticamente Sem conexão ou proxy bloqueando HuggingFace Teste conexão com curl -I https://huggingface.co
Flask retorna 500 Internal Server Error Erro no app.py ou Ollama offline Verifique o log no terminal onde rodou python3 app.py
antiX não inicia pelo pendrive Secure Boot ativo ou boot order errado Desative Secure Boot na BIOS (aba Security) e coloque USB como primeiro na ordem de boot

Referência rápida — todos os comandos essenciais

Um cartão de referência para consultar sem precisar rolar o post inteiro. Salve essa seção nos favoritos ou copie para um arquivo de texto na sua máquina.

Sistema e RAM

# Ver uso de RAM em tempo real watch -n 2 free -h # Ver espaço em disco df -h ~ # Ver processos consumindo mais RAM ps aux --sort=-%mem | head -10 # Ver tamanho dos modelos em disco du -sh ~/.ollama/models/ du -sh ~/.cache/moondream/ du -sh ~/sd-models/

Ollama

# Status do serviço sudo systemctl status ollama # Iniciar / parar / reiniciar sudo systemctl start ollama sudo systemctl stop ollama sudo systemctl restart ollama # Listar modelos instalados ollama list # Baixar modelo ollama pull smollm2:1.7b # Chat interativo ollama run smollm2:1.7b # Prompt direto (sem abrir chat) ollama run smollm2:1.7b "sua pergunta aqui" # Ver modelos em execução ollama ps # Remover modelo ollama rm tinyllama # Criar modelo customizado ollama create meu-modelo -f ~/Modelfile # Liberar para rede local (temporário) OLLAMA_HOST=0.0.0.0 ollama serve

Stable Diffusion (sd-cli)

# Gerar imagem 256x256 (rápido) cd ~/stable-diffusion.cpp/build ./bin/sd-cli \ --model ~/sd-models/ggml-model-q4_0.gguf \ --prompt "seu prompt aqui" \ --negative-prompt "blurry, low quality, ugly" \ --height 256 --width 256 \ --steps 20 --seed 42 \ --output ~/saida.png # Gerar imagem 512x512 (qualidade maior, muito mais lento) ./bin/sd-cli \ --model ~/sd-models/ggml-model-q4_0.gguf \ --prompt "seu prompt aqui" \ --height 512 --width 512 \ --steps 15 --seed 42 \ --output ~/saida_512.png # Iniciar servidor HTTP de geração ./bin/sd-server --model ~/sd-models/ggml-model-q4_0.gguf --port 8080 --host 0.0.0.0

Moondream2

# Ativar ambiente virtual source ~/moondream-env/bin/activate # Analisar imagem python3 ~/analisa_imagem.py ~/imagem.png # Desativar ambiente quando terminar deactivate

Web UI Flask

# Ativar ambiente e iniciar servidor source ~/webui-env/bin/activate cd ~/ollama-webui python3 app.py # Acessar no navegador # http://localhost:5000 (mesmo PC) # http://SEU_IP:5000 (outro dispositivo na rede)

Comandos de agente na Web UI

buscar: inteligência artificial 2025 tendências arquivo: ~/meu_historico.txt arquivo: /etc/os-release comando: free -h comando: ollama list comando: df -h

Script de diagnóstico automático do setup

Este script verifica automaticamente se todos os componentes do setup estão instalados e funcionando corretamente. Cole, salve e execute sempre que algo não estiver se comportando como esperado:

nano ~/verifica_setup.sh
#!/bin/bash # ───────────────────────────────────────────── # Script de diagnóstico do setup IA local # @CanalQb — antiX Linux + Ollama + SD.cpp + Moondream # ───────────────────────────────────────────── GREEN='\033[0;32m' RED='\033[0;31m' YELLOW='\033[1;33m' NC='\033[0m' # sem cor ok() { echo -e " ${GREEN}✅ $1${NC}"; } fail() { echo -e " ${RED}❌ $1${NC}"; } warn() { echo -e " ${YELLOW}⚠️ $1${NC}"; } echo "" echo "═══════════════════════════════════════════" echo " 🔍 Diagnóstico do Setup IA Local @CanalQb" echo "═══════════════════════════════════════════" # ── RAM e SWAP ────────────────────────────────── echo "" echo "[ RAM e SWAP ]" RAM_TOTAL=$(free -m | awk '/^Mem:/{print $2}') SWAP_TOTAL=$(free -m | awk '/^Swap:/{print $2}') RAM_LIVRE=$(free -m | awk '/^Mem:/{print $7}') echo " RAM total: ${RAM_TOTAL} MB" echo " RAM livre: ${RAM_LIVRE} MB" echo " SWAP total: ${SWAP_TOTAL} MB" [ "$SWAP_TOTAL" -ge 4096 ] && ok "SWAP adequado (${SWAP_TOTAL} MB)" || fail "SWAP insuficiente — crie pelo menos 4 GB (recomendado 8 GB)" [ "$RAM_LIVRE" -ge 500 ] && ok "RAM livre suficiente para iniciar modelos" || warn "RAM livre baixa (${RAM_LIVRE} MB) — feche outros programas" # ── Ollama ────────────────────────────────────── echo "" echo "[ Ollama ]" if command -v ollama &>/dev/null; then OLLAMA_VER=$(ollama --version 2>/dev/null | head -1) ok "Ollama instalado: $OLLAMA_VER" if systemctl is-active --quiet ollama 2>/dev/null; then ok "Serviço Ollama rodando" else fail "Serviço Ollama parado — execute: sudo systemctl start ollama" fi MODELOS=$(ollama list 2>/dev/null | grep -v NAME | wc -l) [ "$MODELOS" -gt 0 ] && ok "${MODELOS} modelo(s) instalado(s)" || warn "Nenhum modelo instalado — execute: ollama run smollm2:1.7b" else fail "Ollama não instalado — execute: curl -fsSL https://ollama.com/install.sh | sh" fi # ── Stable Diffusion ──────────────────────────── echo "" echo "[ Stable Diffusion ]" if [ -f ~/stable-diffusion.cpp/build/bin/sd-cli ]; then ok "sd-cli compilado com sucesso" else fail "sd-cli não encontrado em ~/stable-diffusion.cpp/build/bin/sd-cli" warn "Compile com: cd ~/stable-diffusion.cpp/build && make -j\$(nproc)" fi MODELO_SD=$(ls ~/sd-models/*.gguf 2>/dev/null | head -1) if [ -n "$MODELO_SD" ]; then TAMANHO=$(du -sh "$MODELO_SD" | cut -f1) ok "Modelo SD encontrado: $(basename $MODELO_SD) (${TAMANHO})" else fail "Nenhum modelo .gguf encontrado em ~/sd-models/" fi # ── Python e virtualenvs ───────────────────────── echo "" echo "[ Python e Ambientes Virtuais ]" if command -v python3 &>/dev/null; then PY_VER=$(python3 --version) ok "$PY_VER instalado" else fail "Python3 não instalado — execute: sudo apt install python3 -y" fi if command -v pip3 &>/dev/null; then ok "pip3 disponível" else fail "pip3 não encontrado — execute: sudo apt install python3-pip -y" fi [ -d ~/moondream-env ] && ok "Virtualenv moondream-env encontrado" || warn "Virtualenv moondream-env não criado" [ -d ~/webui-env ] && ok "Virtualenv webui-env encontrado" || warn "Virtualenv webui-env não criado" # ── Web UI ────────────────────────────────────── echo "" echo "[ Web UI Flask ]" if [ -f ~/ollama-webui/app.py ]; then ok "app.py encontrado" else warn "app.py não encontrado em ~/ollama-webui/" fi if ss -tlnp 2>/dev/null | grep -q ':5000'; then ok "Web UI rodando na porta 5000" else warn "Web UI não está rodando — execute: cd ~/ollama-webui && python3 app.py" fi # ── Rede ──────────────────────────────────────── echo "" echo "[ Rede ]" IP_LOCAL=$(ip -4 addr show scope global | grep -oP '(?<=inet )\d+\.\d+\.\d+\.\d+' | head -1) [ -n "$IP_LOCAL" ] && ok "IP local: ${IP_LOCAL}" || warn "IP local não detectado" if ss -tlnp 2>/dev/null | grep -q ':11434'; then ok "Ollama API acessível na porta 11434" else warn "Porta 11434 não está aberta" fi # ── Espaço em disco ───────────────────────────── echo "" echo "[ Espaço em Disco ]" DISCO_LIVRE=$(df -m ~ | awk 'NR==2{print $4}') echo " Espaço livre em ~: ${DISCO_LIVRE} MB" [ "$DISCO_LIVRE" -ge 5120 ] && ok "Espaço suficiente para modelos" || warn "Pouco espaço livre (${DISCO_LIVRE} MB) — considere liberar espaço" OLLAMA_SIZE=$(du -sh ~/.ollama/models/ 2>/dev/null | cut -f1) SD_SIZE=$(du -sh ~/sd-models/ 2>/dev/null | cut -f1) MOON_SIZE=$(du -sh ~/.cache/moondream/ 2>/dev/null | cut -f1) echo " Modelos Ollama: ${OLLAMA_SIZE:-0}" echo " Modelos SD: ${SD_SIZE:-0}" echo " Cache Moondream: ${MOON_SIZE:-0}" echo "" echo "═══════════════════════════════════════════" echo " Diagnóstico concluído." echo "═══════════════════════════════════════════" echo ""
chmod +x ~/verifica_setup.sh ~/verifica_setup.sh

O script verifica RAM, SWAP, Ollama (versão, serviço e modelos), sd-cli, virtualenvs, Web UI, rede e espaço em disco — tudo em segundos, com saída colorida mostrando o que está ok e o que precisa de atenção.


Iniciando a Web UI automaticamente com o sistema

Se você usa a Web UI com frequência, pode configurar para ela iniciar automaticamente toda vez que o antiX ligar — sem precisar abrir terminal e ativar o virtualenv manualmente. Existem duas formas de fazer isso: via serviço systemd (mais robusto) ou via script de inicialização do usuário (mais simples).

Método 1 — Script de inicialização do usuário (simples)

Crie um script de startup e adicione-o ao autostart do antiX:

nano ~/start_webui.sh
#!/bin/bash # Aguarda o Ollama subir completamente antes de iniciar a Web UI sleep 10 # Ativa o virtualenv e sobe o Flask em background source ~/webui-env/bin/activate cd ~/ollama-webui nohup python3 app.py > ~/webui.log 2>&1 & echo "Web UI iniciada. Log em ~/webui.log"
chmod +x ~/start_webui.sh

No antiX, adicione ao autostart via Menu → Preferências → Sessão e Inicialização ou edite diretamente o arquivo:

nano ~/.config/autostart/webui-ollama.desktop
[Desktop Entry] Type=Application Name=Ollama Web UI Exec=/home/SEU_USUARIO/start_webui.sh Hidden=false NoDisplay=false X-GNOME-Autostart-enabled=true
⚠️ Substitua SEU_USUARIO pelo seu nome de usuário real no antiX. Descubra com o comando whoami.

Método 2 — Serviço systemd (robusto)

Para um setup mais profissional, crie um serviço systemd que reinicia automaticamente em caso de falha:

sudo nano /etc/systemd/system/ollama-webui.service
[Unit] Description=Ollama Web UI @CanalQb After=network.target ollama.service Requires=ollama.service [Service] Type=simple User=SEU_USUARIO WorkingDirectory=/home/SEU_USUARIO/ollama-webui ExecStart=/home/SEU_USUARIO/webui-env/bin/python3 app.py Restart=on-failure RestartSec=5 StandardOutput=journal StandardError=journal [Install] WantedBy=multi-user.target
sudo systemctl daemon-reload sudo systemctl enable ollama-webui sudo systemctl start ollama-webui # Verificar status sudo systemctl status ollama-webui # Ver logs em tempo real sudo journalctl -u ollama-webui -f

Próximos passos — para onde evoluir este setup

Com tudo que foi feito até aqui você já tem um servidor de IA local funcional. Mas este setup é uma base — dá para evoluir bastante dependendo do que você precisar. Aqui estão as direções mais práticas e como chegar lá:

Hardware

Adicionar mais RAM

Se o seu N3700 aceitar expansão (verifique no manual), ir de 2 GB para 4 GB é a única atualização de hardware que muda completamente a experiência. Com 4 GB você roda o Gemma3 4B e o SmolLM2 1.7B sem tocar no SWAP, gerando respostas 3x mais rápidas.

Modelos

Explorar novos modelos leves

O ecossistema de modelos pequenos cresce todo mês. Acompanhe o ollama.com/library e filtre por tamanho. Modelos como Phi-3 mini e Qwen2.5 0.5B são candidatos promissores para 2 GB de RAM.

Python

Memória persistente com SQLite

A Web UI atual guarda o histórico apenas em RAM — ao reiniciar, perde tudo. O próximo passo natural é salvar as conversas em SQLite: pip install flask-sqlalchemy e criar um modelo de banco para as mensagens.

IA

RAG — Responder sobre seus documentos

RAG (Retrieval-Augmented Generation) permite que o modelo responda perguntas baseado em PDFs e documentos seus. Biblioteca indicada: chromadb + sentence-transformers com modelos de embedding leves como o all-MiniLM-L6-v2.

Rede

Acesso seguro pela internet

Para acessar sua Web UI de fora da rede local sem expor diretamente à internet, use um túnel seguro. O Cloudflare Tunnel (gratuito) ou o Tailscale são as opções mais simples — sem precisar abrir portas no roteador.

Integração

Extensão de browser com a API

O Ollama expõe uma API compatível com OpenAI em localhost:11434/v1. Qualquer ferramenta que aceita endpoint OpenAI customizado — como o Open WebUI, extensões de browser ou editores de código — consegue se conectar ao seu Ollama local.

✅ Compatibilidade OpenAI: O Ollama suporta a API compatível com OpenAI. Isso significa que você pode apontar qualquer app que usa a API da OpenAI para o seu servidor local trocando apenas a base URL: http://localhost:11434/v1 com API key qualquer (o Ollama não valida). Sem custo, sem limites de requisição, sem internet.


Perguntas frequentes (FAQ)

Antes de comentar com dúvidas, veja se ela já está respondida aqui — são as perguntas que mais aparecem sobre este tipo de setup.

❓ Precisa de GPU para rodar IA local?

Não. GPU acelera muito, mas não é obrigatória. Tudo neste guia roda 100% em CPU. O que muda sem GPU é a velocidade — textos levam minutos ao invés de segundos, imagens levam horas ao invés de minutos. Para aprendizado e uso pessoal esporádico, CPU é completamente viável.

❓ O Ollama funciona no antiX 32 bits?

Não. O Ollama só tem binários para arquitetura 64-bit (amd64). Use sempre a ISO antiX 23.1 x64. Se seu processador for de 32 bits, infelizmente o Ollama não tem suporte oficial — mas o stable-diffusion.cpp pode ser compilado manualmente para 32 bits com limitações.

❓ Posso usar o mesmo setup no Raspberry Pi?

Sim, com adaptações. O Ollama tem suporte para ARM (Raspberry Pi 3, 4 e 5). O Pi 4 com 4 GB de RAM funciona melhor que o N3700 para texto. Para imagem, o stable-diffusion.cpp compila normalmente em ARM também. O antiX não tem versão ARM, mas o Raspberry Pi OS (Debian-based) funciona como base equivalente.

❓ Os modelos falam português?

Modelos pequenos como SmolLM e TinyLlama entendem e respondem em português, mas com qualidade menor que em inglês — treinaram com menos dados em PT. Para melhores respostas em português, use o Gemma3 1B (melhor multilíngue nesta faixa) ou defina no Modelfile: "Sempre responda em português do Brasil."

❓ O setup funciona sem internet após instalar?

Sim, 100% offline depois que os modelos estão baixados. O Ollama não faz chamadas externas durante o uso — tudo fica local. A única exceção é a ferramenta de busca web do agente (buscar:), que obviamente precisa de internet para funcionar.

❓ Posso usar o Ollama com o VS Code?

Sim. Extensões como Continue e Ollama Autocomplete aceitam endpoint customizado. Configure http://localhost:11434 como base URL e selecione o modelo instalado. Você terá autocomplete de código local, sem enviar nada para a nuvem.

❓ O SWAP não deixa o SSD mais lento?

Com swappiness=10 (configurado na Parte 3), o sistema só usa SWAP em último caso. Na prática, para o SmolLM e TinyLlama, a RAM de 2 GB é suficiente e o SWAP raramente é acionado. Para Gemma3 e modelos maiores, o SWAP é usado e sim, o SSD trabalha mais — considere usar HD para o arquivo de swap se tiver.

❓ Como atualizo o Ollama para nova versão?

Execute o mesmo script de instalação novamente — ele detecta a versão anterior, para o serviço, substitui o binário e reinicia tudo automaticamente: curl -fsSL https://ollama.com/install.sh | sh Seus modelos baixados são preservados.


Benchmarks reais no Pentium N3700 com antiX 23.1

Números reais medidos durante os testes. Os tempos podem variar dependendo do armazenamento (SSD vs HD), temperatura do processador e carga de background. Todos os testes foram feitos com o sistema recém reiniciado e sem outros processos pesados rodando.

Modelos de texto (tokens/segundo)

Modelo Tempo de carga Velocidade (tok/s) Resposta curta (~50 tok) Resposta longa (~300 tok) SWAP usado?
SmolLM 135M ~5s 8–12 tok/s ~5–7s ~25–40s Não
SmolLM 360M ~8s 5–8 tok/s ~7–10s ~40–60s Não
SmolLM2 1.7B ~20s 2–4 tok/s ~15–25s ~75–150s Raramente
TinyLlama 1.1B ~25s 1–3 tok/s ~20–50s ~100–300s Raramente
Gemma3 1B ~35s 1–2 tok/s ~30–60s ~150–300s Às vezes
Gemma3 4B ~3–5min ~0.3–0.8 tok/s ~2–5min ~10–20min Sim (intenso)

Geração de imagens (stable-diffusion.cpp)

Resolução Steps Tempo estimado Qualidade RAM usada
128×128 10 5–10 min Baixa — para testar ~700 MB
256×256 20 15–40 min Razoável — uso normal ~900 MB
512×512 15 ~2–4 horas Boa ~1,4 GB + SWAP
512×512 30 ~4–8 horas Muito boa ~1,4 GB + SWAP

Análise de imagens (Moondream2)

Operação Tempo estimado RAM usada
Carregamento do modelo (1ª vez — inclui download) Download ~1,7 GB + ~3–5 min
Carregamento do modelo (cache local) ~60–120s ~1,4 GB
Encode de imagem (encode_image) ~30–60s ~1,5 GB
Geração de legenda (caption) ~60–120s ~1,5 GB
Pergunta sobre imagem (query) ~45–90s por pergunta ~1,5 GB

Usando o Ollama via API Python — automações e scripts

O Ollama expõe uma API REST em localhost:11434 que você pode usar em qualquer script Python para automatizar tarefas. Alguns exemplos práticos: resumir arquivos de log automaticamente, gerar descrições de imagens em lote, ou criar um bot de terminal que responde perguntas sem abrir o chat interativo.

Script 1 — Chat simples via API (sem biblioteca extra)

nano ~/ollama_api.py
#!/usr/bin/env python3 """ Exemplo de uso da API do Ollama via requests puro. Não precisa do pacote ollama — só requests. @CanalQb """ import requests import json import sys OLLAMA_URL = "http://localhost:11434/api/chat" MODELO = "smollm2:1.7b" # troque pelo modelo que tiver instalado def chat(mensagem, historico=None, sistema=None): """Envia mensagem para o Ollama e retorna a resposta.""" mensagens = [] if sistema: mensagens.append({"role": "system", "content": sistema}) if historico: mensagens.extend(historico) mensagens.append({"role": "user", "content": mensagem}) try: resposta = requests.post( OLLAMA_URL, json={"model": MODELO, "messages": mensagens, "stream": False}, timeout=300 ) resposta.raise_for_status() return resposta.json().get("message", {}).get("content", "") except requests.exceptions.ConnectionError: return "Erro: Ollama não está rodando. Execute: sudo systemctl start ollama" except Exception as e: return f"Erro: {e}" # ── Modo de uso ─────────────────────────────────────────────── if __name__ == "__main__": # Usa argumento da linha de comando se passado if len(sys.argv) > 1: pergunta = " ".join(sys.argv[1:]) print(chat(pergunta)) else: # Chat interativo com histórico print(f"Chat com {MODELO} | Ctrl+C para sair\n") historico = [] sistema = "Você é um assistente técnico que responde em português." while True: try: entrada = input("Você: ").strip() if not entrada: continue resposta = chat(entrada, historico, sistema) print(f"\nIA: {resposta}\n") historico.append({"role": "user", "content": entrada}) historico.append({"role": "assistant", "content": resposta}) # Manter histórico pequeno para não explodir a RAM if len(historico) > 10: historico = historico[-10:] except KeyboardInterrupt: print("\nAté mais!") break
# Usar como chat interativo python3 ~/ollama_api.py # Ou passar pergunta diretamente python3 ~/ollama_api.py "como verificar o uso de disco no Linux?"

Script 2 — Resumir arquivo de texto automaticamente

nano ~/resume_arquivo.py
#!/usr/bin/env python3 """ Resume qualquer arquivo de texto usando o Ollama local. Uso: python3 resume_arquivo.py /caminho/arquivo.txt @CanalQb """ import requests import sys import os OLLAMA_URL = "http://localhost:11434/api/generate" MODELO = "smollm2:1.7b" def resumir(texto, max_chars=3000): """Envia texto truncado para resumo — respeita limite de contexto.""" texto_trunc = texto[:max_chars] if len(texto) > max_chars: texto_trunc += f"\n\n[... texto truncado em {max_chars} caracteres ...]" prompt = f"""Resuma o seguinte texto em português, em no máximo 5 pontos principais. Seja direto e objetivo. TEXTO: {texto_trunc} RESUMO:""" try: res = requests.post( OLLAMA_URL, json={"model": MODELO, "prompt": prompt, "stream": False}, timeout=300 ) return res.json().get("response", "Sem resposta.") except Exception as e: return f"Erro: {e}" if __name__ == "__main__": if len(sys.argv) < 2: print("Uso: python3 resume_arquivo.py /caminho/arquivo.txt") sys.exit(1) caminho = sys.argv[1] if not os.path.exists(caminho): print(f"Arquivo não encontrado: {caminho}") sys.exit(1) with open(caminho, "r", encoding="utf-8", errors="replace") as f: conteudo = f.read() print(f"\nResumindo: {caminho} ({len(conteudo)} caracteres)\n") print("─" * 50) print(resumir(conteudo)) print("─" * 50)
# Exemplo — resumir seu próprio histórico do terminal python3 ~/resume_arquivo.py ~/meu_historico.txt

Script 3 — Geração de imagens em lote com sd-cli

nano ~/gera_lote.sh
#!/bin/bash # Gera múltiplas imagens em sequência com prompts diferentes # @CanalQb — stable-diffusion.cpp em lote SD_CLI=~/stable-diffusion.cpp/build/bin/sd-cli MODELO=~/sd-models/ggml-model-q4_0.gguf SAIDA=~/imagens_lote mkdir -p "$SAIDA" # Lista de prompts — adicione quantos quiser PROMPTS=( "a simple mountain landscape at sunset, digital art" "a futuristic city with flying cars, cyberpunk style" "a forest path with sunlight through trees, watercolor" "abstract geometric shapes in blue and gold, minimal" ) TOTAL=${#PROMPTS[@]} echo "Gerando $TOTAL imagens em lote..." echo "Pasta de saída: $SAIDA" echo "" for i in "${!PROMPTS[@]}"; do NUM=$((i + 1)) PROMPT="${PROMPTS[$i]}" ARQUIVO="$SAIDA/imagem_$(printf '%02d' $NUM).png" echo "[$NUM/$TOTAL] Gerando: $PROMPT" echo " Saída: $ARQUIVO" $SD_CLI \ --model "$MODELO" \ --prompt "$PROMPT" \ --negative-prompt "blurry, low quality, ugly, distorted" \ --height 256 --width 256 \ --steps 20 \ --seed $((RANDOM)) \ --output "$ARQUIVO" 2>/dev/null echo " ✅ Concluído" echo "" done echo "═══════════════════════════════" echo "Lote finalizado! $TOTAL imagens em $SAIDA" ls -lh "$SAIDA"
chmod +x ~/gera_lote.sh ~/gera_lote.sh
⚠️ Geração em lote no N3700: Com 4 prompts e 256×256 pixels, o lote completo pode levar de 1 a 3 horas dependendo do hardware. O script mostra progresso a cada imagem. Ideal para deixar rodando durante a noite.

Backup e migração do setup para outro computador

Se você quiser mover este setup para outro PC ou fazer backup dos modelos e configurações sem precisar baixar tudo novamente, aqui está o que você precisa preservar e como fazer.

O que fazer backup

O que Onde fica Tamanho típico Prioridade
Modelos Ollama ~/.ollama/models/ 600 MB – 3 GB por modelo Alta — demora muito baixar
Modelos SD (.gguf) ~/sd-models/ ~850 MB Alta
Cache Moondream ~/.cache/moondream/ ~1,7 GB Alta — demora muito baixar
Projeto Web UI ~/ollama-webui/ ~10–50 KB Alta — seu código
Modelfiles e scripts ~/Modelfile, ~/*.sh, ~/*.py Pequeno Alta — configurações
stable-diffusion.cpp (compilado) ~/stable-diffusion.cpp/ ~200–400 MB Média — pode recompilar
Virtualenvs ~/moondream-env/, ~/webui-env/ ~500 MB cada Baixa — pode recriar

Script de backup completo

nano ~/backup_ia.sh
#!/bin/bash # Backup do setup de IA local — @CanalQb # Ajuste DESTINO para seu pendrive ou HD externo DESTINO="/media/backup/ia_local_$(date +%Y%m%d)" mkdir -p "$DESTINO" echo "=== Backup IA Local @CanalQb ===" echo "Destino: $DESTINO" echo "" backup_item() { local ORIGEM="$1" local NOME="$2" if [ -e "$ORIGEM" ]; then echo "Copiando $NOME..." cp -r "$ORIGEM" "$DESTINO/" && echo " ✅ $NOME" || echo " ❌ Falhou: $NOME" else echo " ⚠️ Não encontrado: $ORIGEM (pulando)" fi } backup_item ~/.ollama/models "Modelos Ollama" backup_item ~/sd-models "Modelos SD" backup_item ~/.cache/moondream "Cache Moondream" backup_item ~/ollama-webui "Web UI" backup_item ~/Modelfile "Modelfile" backup_item ~/analisa_imagem.py "Script Moondream" backup_item ~/ollama_api.py "Script API Python" backup_item ~/verifica_setup.sh "Script diagnóstico" backup_item ~/prompt_form.sh "Script formulário" backup_item ~/personas.json "Personas" backup_item ~/start_webui.sh "Script autostart" echo "" echo "Tamanho total do backup:" du -sh "$DESTINO" echo "" echo "Backup concluído em $DESTINO"
chmod +x ~/backup_ia.sh # Ajuste o DESTINO no arquivo antes de executar ~/backup_ia.sh

Restaurando em outro computador

# Após instalar antiX + Ollama + dependências no novo PC: # 1. Restaurar modelos Ollama mkdir -p ~/.ollama/ cp -r /media/backup/ia_local_XXXXXXXX/models ~/.ollama/ # 2. Restaurar modelos SD mkdir -p ~/sd-models/ cp /media/backup/ia_local_XXXXXXXX/sd-models/*.gguf ~/sd-models/ # 3. Restaurar cache Moondream mkdir -p ~/.cache/ cp -r /media/backup/ia_local_XXXXXXXX/moondream ~/.cache/ # 4. Restaurar Web UI e scripts cp -r /media/backup/ia_local_XXXXXXXX/ollama-webui ~/ cp /media/backup/ia_local_XXXXXXXX/*.py ~/ cp /media/backup/ia_local_XXXXXXXX/*.sh ~/ chmod +x ~/*.sh # 5. Recriar virtualenvs (mais rápido do zero) python3 -m venv ~/moondream-env source ~/moondream-env/bin/activate pip install moondream Pillow deactivate python3 -m venv ~/webui-env source ~/webui-env/bin/activate pip install flask requests beautifulsoup4 deactivate

Glossário — termos técnicos usados neste guia

Se você chegou aqui sem familiaridade com alguns termos, este glossário explica de forma prática o que cada coisa significa — sem academicismo desnecessário.

🧠 LLM (Large Language Model)

Modelo de linguagem de grande escala — o tipo de IA por trás do ChatGPT, Gemini e afins. Funciona prevendo qual palavra/token vem depois de cada texto. "Large" refere-se ao número de parâmetros (pesos) treinados, não necessariamente ao tamanho do arquivo.

⚡ Quantização

Técnica de compressão que reduz a precisão dos pesos do modelo (de float32 para int8 ou int4). Um modelo quantizado em 4 bits (Q4) usa ~4x menos RAM e disco que o original, com pequena perda de qualidade. É o que torna possível rodar modelos em hardware modesto.

📄 GGUF

Formato de arquivo criado pelo projeto llama.cpp para armazenar modelos quantizados. Substitui o formato GGML anterior. É o padrão usado pelo Ollama e pelo stable-diffusion.cpp para modelos otimizados para CPU.

🔢 Token

A unidade básica de texto que um LLM processa. Não é necessariamente uma palavra — pode ser uma sílaba, pontuação ou parte de uma palavra. Em inglês, 1 token ≈ 0,75 palavras. Em português, um pouco menos eficiente. "Tokens por segundo" mede a velocidade de geração do modelo.

👁️ VLM (Vision Language Model)

LLM que também processa imagens como entrada, além de texto. O Moondream2 é um VLM — ele recebe uma imagem + uma pergunta em texto e responde sobre o conteúdo visual. É diferente de geração de imagem (que cria imagens a partir de texto).

🔄 RAG (Retrieval-Augmented Generation)

Técnica que combina busca vetorial com geração de texto. Você indexa seus documentos (PDFs, arquivos) em um banco vetorial, e quando faz uma pergunta, o sistema busca os trechos mais relevantes e os injeta no contexto do LLM antes de gerar a resposta. Resultado: o modelo responde com base nos seus dados, não só no treinamento.

🧩 Embedding

Representação numérica de um texto (ou imagem) em um espaço vetorial de alta dimensão. Textos com significados parecidos ficam "próximos" nesse espaço, o que permite busca semântica. É a base do RAG — você busca documentos por proximidade de significado, não por palavras exatas.

🔁 SWAP

Espaço em disco usado pelo Linux como extensão da RAM física quando esta esgota. Muito mais lento que RAM (HD: ~100x mais lento; SSD: ~10x mais lento), mas impede que processos sejam encerrados por falta de memória. Essencial para rodar modelos grandes em hardware com pouca RAM.

📋 Modelfile

Arquivo de configuração do Ollama que define um modelo customizado a partir de um modelo base. Especifica o prompt de sistema, parâmetros de geração (temperatura, tamanho de contexto) e comportamento padrão. Similar a um Dockerfile, mas para modelos de IA.

🌡️ Temperature

Parâmetro que controla o quão "criativo" ou "aleatório" é o modelo ao gerar texto. 0.0–0.3 = respostas mais determinísticas e previsíveis (bom para código e traduções). 0.7–1.0 = respostas mais criativas e variadas (bom para escrita criativa). Padrão do Ollama: 0.8.

📐 OpenBLAS

Biblioteca de álgebra linear otimizada para CPUs (Basic Linear Algebra Subprograms). Acelera operações de matriz que são o coração da inferência de modelos de IA e de geração de imagens. Compilar o stable-diffusion.cpp com OpenBLAS melhora a velocidade em CPUs Intel/AMD.

🐍 Virtualenv

Ambiente Python isolado que contém suas próprias instalações de pacotes, independente do Python do sistema. Evita conflitos entre versões de bibliotecas de projetos diferentes. Boa prática obrigatória para qualquer projeto Python sério. Criado com python3 -m venv nome e ativado com source nome/bin/activate.



Parte 11 — Bot do Telegram integrado ao Ollama

Conteúdo novo

Este é um dos usos mais práticos de todo o setup: criar um bot no Telegram que responde suas mensagens usando os modelos rodando no seu N3700. Você está no celular, manda uma pergunta, e em alguns minutos recebe a resposta processada localmente na sua máquina — sem passar por nenhuma API externa, sem custo, sem limite de mensagens.

Para isso funcionar, o antiX precisa ter acesso à internet (para receber as mensagens do Telegram via polling). O Ollama processa localmente — só a comunicação com o Telegram usa a rede.

11.1 — Criando o bot no Telegram

  1. Abra o Telegram e busque por @BotFather
  2. Envie o comando /newbot
  3. Escolha um nome para o bot (ex: Meu Assistente Local)
  4. Escolha um username que termine em bot (ex: meu_ollama_bot)
  5. O BotFather retorna um token no formato: 1234567890:ABCDefGhIJKlmNOPqrsTUVwxyz — guarde este token
  6. Para descobrir seu chat_id: envie qualquer mensagem para o bot e acesse no navegador: https://api.telegram.org/botSEU_TOKEN/getUpdates — o número em "id" dentro de "chat" é o seu chat_id
⚠️ Nunca compartilhe seu token do BotFather. Com ele qualquer pessoa pode controlar o bot e enviar comandos para o seu Ollama. Guarde-o como uma senha.

11.2 — Instalando a biblioteca do Telegram

source ~/webui-env/bin/activate pip install pyTelegramBotAPI

11.3 — Criando o bot (ollama_telegram_bot.py)

nano ~/ollama_telegram_bot.py
#!/usr/bin/env python3 """ Bot do Telegram integrado ao Ollama local. Envia mensagens pelo Telegram, recebe respostas do seu LLM no N3700. @CanalQb """ import telebot import requests import json import os # ─── Configuração ───────────────────────────────────────────── TELEGRAM_TOKEN = "SEU_TOKEN_AQUI" # token do BotFather CHAT_ID_PERMITIDO = 123456789 # seu chat_id — bloqueia outros usuários OLLAMA_URL = "http://localhost:11434/api/chat" MODELO = "smollm2:1.7b" SISTEMA = "Você é um assistente técnico que responde em português do Brasil. Seja direto e prático." MAX_HIST = 10 # mensagens no histórico por usuário bot = telebot.TeleBot(TELEGRAM_TOKEN) # ─── Histórico de conversa por usuário ─────────────────────── historicos = {} def get_historico(uid): return historicos.get(uid, []) def add_historico(uid, role, content): if uid not in historicos: historicos[uid] = [] historicos[uid].append({"role": role, "content": content}) if len(historicos[uid]) > MAX_HIST: historicos[uid] = historicos[uid][-MAX_HIST:] def limpar_historico(uid): historicos.pop(uid, None) # ─── Verificar autorização ─────────────────────────────────── def autorizado(message): if message.chat.id != CHAT_ID_PERMITIDO: bot.reply_to(message, "⛔ Acesso não autorizado.") return False return True # ─── Chamar o Ollama ───────────────────────────────────────── def chamar_ollama(uid, texto): add_historico(uid, "user", texto) mensagens = [{"role": "system", "content": SISTEMA}] mensagens.extend(get_historico(uid)) try: res = requests.post( OLLAMA_URL, json={"model": MODELO, "messages": mensagens, "stream": False}, timeout=300 ) resposta = res.json().get("message", {}).get("content", "Sem resposta.") add_historico(uid, "assistant", resposta) return resposta except requests.exceptions.ConnectionError: return "❌ Ollama offline. Execute: sudo systemctl start ollama" except Exception as e: return f"❌ Erro: {e}" # ─── Handlers ──────────────────────────────────────────────── @bot.message_handler(commands=["start", "help"]) def cmd_start(message): if not autorizado(message): return bot.reply_to(message, "🤖 *Ollama Bot — @CanalQb*\n\n" "Envie qualquer mensagem e o LLM local responde.\n\n" "Comandos:\n" "/limpar — apaga o histórico da conversa\n" "/modelo — mostra o modelo em uso\n" "/status — verifica se o Ollama está rodando", parse_mode="Markdown" ) @bot.message_handler(commands=["limpar"]) def cmd_limpar(message): if not autorizado(message): return limpar_historico(message.chat.id) bot.reply_to(message, "🗑 Histórico apagado.") @bot.message_handler(commands=["modelo"]) def cmd_modelo(message): if not autorizado(message): return bot.reply_to(message, f"🧠 Modelo em uso: `{MODELO}`", parse_mode="Markdown") @bot.message_handler(commands=["status"]) def cmd_status(message): if not autorizado(message): return try: res = requests.get("http://localhost:11434/api/tags", timeout=5) modelos = [m["name"] for m in res.json().get("models", [])] lista = "\n".join(f"• {m}" for m in modelos) if modelos else "Nenhum instalado" bot.reply_to(message, f"✅ Ollama online\n\nModelos instalados:\n{lista}") except Exception: bot.reply_to(message, "❌ Ollama offline ou inacessível.") @bot.message_handler(func=lambda m: True) def responder(message): if not autorizado(message): return uid = message.chat.id texto = message.text.strip() if not texto: return # Avisa que está processando processando = bot.reply_to(message, "⏳ Processando...") resposta = chamar_ollama(uid, texto) # Editar mensagem de "processando" com a resposta real try: bot.edit_message_text( resposta, chat_id=uid, message_id=processando.message_id ) except Exception: bot.reply_to(message, resposta) # ─── Iniciar polling ───────────────────────────────────────── if __name__ == "__main__": print(f"Bot Telegram iniciado — modelo: {MODELO}") print("Pressione Ctrl+C para parar.") try: bot.infinity_polling(timeout=30, long_polling_timeout=20) except KeyboardInterrupt: print("Bot encerrado.")

11.4 — Configurando e iniciando o bot

Edite o arquivo e substitua os dois valores de configuração:

# Substituir no arquivo: # TELEGRAM_TOKEN = "SEU_TOKEN_AQUI" → cole o token do BotFather # CHAT_ID_PERMITIDO = 123456789 → cole seu chat_id real # Ativar o virtualenv e rodar source ~/webui-env/bin/activate python3 ~/ollama_telegram_bot.py

Agora abra o Telegram, mande uma mensagem para o bot e aguarde. A primeira resposta pode demorar enquanto o modelo carrega — as seguintes são mais rápidas pois o modelo fica em memória.

11.5 — Rodar o bot como serviço systemd

sudo nano /etc/systemd/system/ollama-telegram.service
[Unit] Description=Ollama Telegram Bot @CanalQb After=network.target ollama.service Requires=ollama.service [Service] Type=simple User=SEU_USUARIO ExecStart=/home/SEU_USUARIO/webui-env/bin/python3 /home/SEU_USUARIO/ollama_telegram_bot.py Restart=on-failure RestartSec=10 StandardOutput=journal StandardError=journal [Install] WantedBy=multi-user.target
sudo systemctl daemon-reload sudo systemctl enable ollama-telegram sudo systemctl start ollama-telegram sudo systemctl status ollama-telegram

Parte 12 — Protegendo a Web UI com senha

Segurança

A Web UI Flask que criamos na Parte 9 não tem nenhuma proteção de acesso — qualquer dispositivo na rede que souber o IP e porta consegue usar. Para uso doméstico simples isso é aceitável, mas se você vai expor para internet ou quer mais controle, adicionar uma senha básica leva menos de 10 linhas de código.

12.1 — HTTP Basic Auth (simples, sem banco de dados)

Instale a extensão e adicione ao app.py:

source ~/webui-env/bin/activate pip install Flask-HTTPAuth

Edite o início do seu app.py adicionando as linhas marcadas:

from flask import Flask, render_template, request, jsonify, session from flask_httpauth import HTTPBasicAuth # ← adicionar from werkzeug.security import generate_password_hash, check_password_hash # ← adicionar import requests, json, os, uuid from agents import agent_router app = Flask(__name__) auth = HTTPBasicAuth() # ← adicionar app.secret_key = os.urandom(24) # ─── Usuários e senhas (troque os valores) ──────────────────── USUARIOS = { # ← adicionar "admin": generate_password_hash("sua_senha_aqui"), "convidado": generate_password_hash("outra_senha"), } @auth.verify_password # ← adicionar def verify_password(usuario, senha): if usuario in USUARIOS and check_password_hash(USUARIOS[usuario], senha): return usuario return None # Proteger a rota principal com @auth.login_required @app.route("/") @auth.login_required # ← adicionar esta linha def index(): # ... resto da função permanece igual pass # Proteger a API de chat também @app.route("/api/chat", methods=["POST"]) @auth.login_required # ← adicionar esta linha def chat(): # ... resto da função permanece igual pass

Reinicie o servidor. Ao abrir o navegador agora, ele pede usuário e senha antes de mostrar qualquer coisa. As credenciais ficam salvas no browser até você fechar a sessão.

12.2 — Gerar hash da senha sem deixar em texto puro

Para não deixar a senha em texto legível no código, gere o hash antes e cole direto:

source ~/webui-env/bin/activate python3 -c "from werkzeug.security import generate_password_hash; print(generate_password_hash('sua_senha_aqui'))"

O output é algo como scrypt:32768:8:1$..... — cole esse hash diretamente no dicionário USUARIOS no lugar da chamada generate_password_hash().

⚠️ HTTP Basic Auth não é criptografado sozinho. Para uso na internet, coloque um proxy Nginx com HTTPS na frente ou use Cloudflare Tunnel (gratuito) que adiciona TLS automaticamente. Na rede local sem internet, o Basic Auth já é suficiente.

Parte 13 — Automações com cron: IA trabalhando enquanto você dorme

Automação

O cron é o agendador de tarefas do Linux — permite executar scripts em horários fixos sem interação manual. Combinado com o Ollama, você pode criar rotinas automáticas que usam IA local para processar informações periodicamente: resumir logs, analisar arquivos novos, gerar relatórios. O N3700 fica "trabalhando" enquanto você não está usando o computador.

13.1 — Entendendo a sintaxe do cron

# Formato: minuto hora dia_do_mês mês dia_da_semana comando # ┌───── minuto (0-59) # │ ┌─── hora (0-23) # │ │ ┌─ dia do mês (1-31) # │ │ │ ┌ mês (1-12) # │ │ │ │ ┌ dia da semana (0=Dom, 1=Seg... 6=Sáb) # │ │ │ │ │ # * * * * * comando # Exemplos: # 0 8 * * * → todo dia às 08:00 # 0 22 * * 1-5 → dias úteis às 22:00 # 30 7 * * 0 → domingos às 07:30 # */30 * * * * → a cada 30 minutos

13.2 — Script: resumo diário dos logs do sistema

nano ~/cron_resumo_logs.sh
#!/bin/bash # Resumo diário dos logs do sistema via Ollama # @CanalQb — executado automaticamente pelo cron source ~/webui-env/bin/activate DATA=$(date +%Y-%m-%d) LOG_SAIDA=~/logs_resumidos/resumo_${DATA}.txt mkdir -p ~/logs_resumidos # Coletar logs das últimas 24 horas LOGS=$(journalctl --since "24 hours ago" --no-pager -p err..warning 2>/dev/null | tail -100) if [ -z "$LOGS" ]; then echo "$DATA — Nenhum erro ou aviso nos logs das últimas 24 horas." > "$LOG_SAIDA" exit 0 fi # Enviar para o Ollama via Python inline python3 - < "$LOG_SAIDA" import requests logs = """$LOGS""" prompt = f"""Analise estes logs do sistema Linux e gere um relatório em português com: 1. Principais erros encontrados 2. Possíveis causas 3. Ações recomendadas LOGS: {logs[:3000]} RELATÓRIO:""" try: res = requests.post( "http://localhost:11434/api/generate", json={"model": "smollm2:1.7b", "prompt": prompt, "stream": False}, timeout=300 ) print(f"=== Relatório de Logs — $(date +%Y-%m-%d) ===\n") print(res.json().get("response", "Sem resposta do modelo.")) except Exception as e: print(f"Erro ao contatar Ollama: {e}") EOF echo "Resumo gerado: $LOG_SAIDA"

13.3 — Script: análise automática de imagens numa pasta

nano ~/cron_analisa_pasta.sh
#!/bin/bash # Analisa automaticamente novas imagens numa pasta monitorada # Gera arquivo de descrição .txt para cada imagem nova # @CanalQb PASTA_ENTRADA=~/imagens_para_analisar PASTA_SAIDA=~/descricoes_geradas mkdir -p "$PASTA_ENTRADA" "$PASTA_SAIDA" source ~/moondream-env/bin/activate for IMAGEM in "$PASTA_ENTRADA"/*.{jpg,jpeg,png,webp}; do [ -f "$IMAGEM" ] || continue NOME=$(basename "$IMAGEM" | sed 's/\.[^.]*$//') SAIDA="$PASTA_SAIDA/${NOME}.txt" # Pular se já foi analisada [ -f "$SAIDA" ] && continue echo "Analisando: $IMAGEM" python3 - "$IMAGEM" > "$SAIDA" <<'EOF' import moondream as md from PIL import Image import sys try: model = md.vl(model="moondream-2b-int8.mf") img = Image.open(sys.argv[1]) enc = model.encode_image(img) desc = model.caption(enc)["caption"] print(desc) except Exception as e: print(f"Erro: {e}") EOF echo " Salvo em: $SAIDA" done deactivate echo "Análise em lote concluída."

13.4 — Agendando os scripts no cron

# Abrir o editor de cron do usuário atual crontab -e

Adicione as linhas abaixo ao final do arquivo. O cron usa o editor padrão do sistema (geralmente nano no antiX):

# Dar permissão de execução aos scripts primeiro chmod +x ~/cron_resumo_logs.sh chmod +x ~/cron_analisa_pasta.sh # Conteúdo a adicionar no crontab (crontab -e): # Resumo de logs todo dia às 07:00 0 7 * * * ~/cron_resumo_logs.sh >> ~/logs_resumidos/cron.log 2>&1 # Análise de imagens novas a cada 2 horas 0 */2 * * * ~/cron_analisa_pasta.sh >> ~/descricoes_geradas/cron.log 2>&1 # Verificação do setup toda segunda-feira às 09:00 0 9 * * 1 ~/verifica_setup.sh >> ~/verifica_setup.log 2>&1

Salve e feche. Confirme que o cron registrou as tarefas:

# Ver tarefas agendadas crontab -l # Ver se o serviço cron está ativo no antiX sudo systemctl status cron

Parte 14 — Open WebUI: interface profissional para o Ollama

Alternativa avançada

O Open WebUI é a interface web mais usada com o Ollama no mundo — open source, com visual estilo ChatGPT, suporte a múltiplos modelos, upload de arquivos, histórico persistente, suporte a RAG, múltiplos usuários e muito mais. É uma alternativa ao Flask caseiro que criamos, muito mais completa e polida.

A ressalva para o N3700 com 2 GB é clara: o Open WebUI em si consome de 300 a 600 MB de RAM apenas para rodar a interface — isso é RAM que vai fazer falta para os modelos. Avalie se o seu uso justifica o custo de memória.

Característica Flask caseiro (Parte 9) Open WebUI
RAM usada pela interface ~30–50 MB ~300–600 MB
Instalação ~10 min ~15–30 min
Histórico persistente Só na sessão Sim (SQLite)
Upload de arquivos Via agente Sim (nativo)
RAG embutido Não Sim
Multiusuário Básico Sim (com login)
Streaming de resposta Não Sim
Recomendado para N3700 2GB ✅ Sim ⚠️ Com limitações

14.1 — Instalando o Open WebUI sem Docker (direto no Python)

O método sem Docker usa menos RAM que a versão containerizada:

# Criar virtualenv dedicado python3 -m venv ~/openwebui-env source ~/openwebui-env/bin/activate # Instalar o Open WebUI pip install open-webui # Iniciar (na primeira vez baixa dependências adicionais) open-webui serve

Na primeira inicialização, ele cria o banco de dados e baixa alguns assets. Aguarde completar. Acesse em:

http://localhost:8080

Na tela inicial, crie sua conta de administrador. O Open WebUI detecta automaticamente o Ollama em localhost:11434 e lista todos os modelos instalados.

14.2 — Configurando a URL do Ollama no Open WebUI

Se o Open WebUI não detectar o Ollama automaticamente:

  1. Acesse Configurações → Conexões no menu do usuário
  2. Em URL da API Ollama, coloque: http://localhost:11434
  3. Clique em salvar e recarregue a página

14.3 — Rodando como serviço systemd

sudo nano /etc/systemd/system/open-webui.service
[Unit] Description=Open WebUI @CanalQb After=network.target ollama.service Requires=ollama.service [Service] Type=simple User=SEU_USUARIO Environment="PORT=8080" ExecStart=/home/SEU_USUARIO/openwebui-env/bin/open-webui serve Restart=on-failure RestartSec=10 StandardOutput=journal StandardError=journal [Install] WantedBy=multi-user.target
sudo systemctl daemon-reload sudo systemctl enable open-webui sudo systemctl start open-webui
⚠️ Monitoramento de RAM obrigatório: Ao usar o Open WebUI no N3700, monitore o consumo de RAM com watch -n 2 free -h. Se a RAM livre cair abaixo de 200 MB, reduza o modelo para SmolLM 135M ou pare o Open WebUI e use o Flask caseiro que consome muito menos memória.

Mapa de portas e serviços do setup completo

Com tudo instalado, seu antiX pode estar rodando vários serviços simultaneamente. Este é o mapa completo para você não se perder:

Serviço Porta URL de acesso RAM Iniciar
Ollama API 11434 http://IP:11434 ~50–200 MB sudo systemctl start ollama
Flask Web UI 5000 http://IP:5000 ~30–50 MB python3 app.py
sd-server (SD.cpp) 8080 http://IP:8080 ~900 MB ./bin/sd-server --port 8080
Open WebUI 8080 http://IP:8080 ~300–600 MB open-webui serve
Bot Telegram Via Telegram ~30 MB python3 ollama_telegram_bot.py
⚠️ Conflito de porta 8080: O sd-server e o Open WebUI usam a mesma porta 8080 por padrão. Nunca rode os dois ao mesmo tempo. Se precisar de ambos, mude a porta de um deles: ./bin/sd-server --port 8081 ou PORT=8090 open-webui serve.

Link para download do template do Stable Diffusion: https://canalqb.blogspot.com/?c=stablediffusion

Marcadores: Banco de Dados Blogger Cripto IA Jogos OpenWrt Python Script Sistemas Telegram Tutorial

© março 29, 2026 CanalQb — Python, Scripts, Automação, Airdrops e Criptomoedas | Web3 e Tech na Prática

Comentários