8.2 KiB
Pastor Online
Sistema web RAG para consulta bíblica. Lê PDFs da Bíblia, indexa o conteúdo em um banco vetorial e responde em chat com um agente pastoral ancorado exclusivamente nos versículos recuperados — com as referências sempre à vista.
Containerizado com Docker Compose: frontend React, backend FastAPI, ChromaDB e (opcionalmente) Ollama local.
Como funciona
./PDF/*.pdf ──▶ PyMuPDF + OCR (Tesseract) ──▶ parser bíblico ──▶ embeddings
│
navegador ◀── SSE ── FastAPI ◀── busca + validação
│
ChromaDB
Fidelidade ao texto. A resposta só é gerada quando há evidência recuperada do corpus. As citações são conferidas no servidor contra as fontes consultadas: uma referência que o modelo inventou é removida antes de chegar à tela. Sem evidência, o sistema responde com uma mensagem fixa e não chama o LLM.
Extração. Se a camada textual do PDF é utilizável, ela é usada (rápido). Se o PDF usa fontes sem mapeamento Unicode — caso comum em PDFs de Bíblia digitalizados — o sistema detecta a corrupção e aplica OCR na página. O resultado do OCR é cacheado por página: reindexações não repetem o trabalho.
Requisitos
- Docker com Compose v2 (ou Podman com
podman-compose) - ~2 GB de RAM livres para o backend
- ~1,5 GB de disco para o modelo de embeddings e o banco vetorial
Início rápido
1. Coloque os PDFs
Copie seus PDFs da Bíblia para a pasta ./PDF. O diretório já contém o
biblia.pdf desta edição da Almeida Revista e Corrigida.
A pasta
./pdfs(minúscula) também funciona: basta apontarPDF_DIRpara ela nodocker-compose.yml. O Compose monta/data/pdfs.
2. Configure o provedor de LLM
cp .env.example .env
Edite o .env. Três caminhos possíveis:
| Provedor | Configuração | Observação |
|---|---|---|
| Ollama (local) | LLM_PROVIDER=ollama |
Subir com --profile local; o modelo é baixado na primeira execução |
| OpenAI | LLM_PROVIDER=openai + OPENAI_API_KEY |
|
| Groq | LLM_PROVIDER=groq + GROQ_API_KEY |
|
| Anthropic | LLM_PROVIDER=anthropic + ANTHROPIC_API_KEY |
3. Suba o sistema
# Com Ollama local:
docker compose --profile local up --build -d
# Sem Ollama (usando OpenAI, Groq ou Anthropic):
docker compose up --build -d
Acesse http://localhost:8080.
4. Acompanhe a indexação
A primeira indexação faz OCR de todas as páginas do PDF — são ~3.400 páginas, e o tempo típico fica entre 15 e 30 minutos em CPU. O painel lateral mostra o progresso (PDFs lidos e vetores indexados) e o chat já responde durante a indexação.
Para acompanhar os logs:
docker compose logs -f backend
Comandos úteis
# Reiniciar só o frontend (após mudanças no React)
docker compose up -d --build frontend
# Forçar a reindexação dos PDFs
curl -X POST http://localhost:8080/api/reindex
# Ver o estado da indexação
curl http://localhost:8080/api/status
# Rodar os testes do backend
docker compose exec backend pytest tests/ -v
# Rodar os testes do frontend
docker compose exec frontend npm test
API
| Rota | Método | Descrição |
|---|---|---|
/api/health |
GET | Saúde do serviço |
/api/status |
GET | PDFs lidos, vetores indexados, erros |
/api/reindex |
POST | Força a reindexação (202). 409 se já em andamento |
/api/chat |
POST | Chat em streaming (SSE) |
/api/sources |
GET | Trechos consultados na última resposta |
O chat responde em Server-Sent Events:
event: sources
data: {"sources":[{"reference":"Gênesis 1:1","text":"...","page":6}]}
event: token
data: {"text":"No princípio criou Deus os céus e a terra..."}
event: done
data: {"citations":["Gênesis 1:1"]}
O frontend consome o stream com fetch + ReadableStream, porque EventSource não
aceita POST.
Configuração
Todas as variáveis ficam no .env. As principais:
| Variável | Padrão | Efeito |
|---|---|---|
LLM_PROVIDER |
ollama |
ollama, openai, groq ou anthropic |
EMBEDDING_MODEL |
paraphrase-multilingual-MiniLM-L12-v2 |
all-MiniLM-L6-v2 é menor e mais rápido |
TOP_K |
6 |
Quantos trechos são recuperados por pergunta |
MIN_SIMILARITY |
0.35 |
Corte de relevância; acima disso, o sistema responde que o texto não aborda o assunto |
OCR_DPI |
200 |
Resolução do OCR |
OCR_LANGUAGE |
por+eng |
Idiomas do Tesseract |
REINDEX_TOKEN |
vazio | Se preenchido, /api/reindex exige o header X-Reindex-Token |
CORS_ORIGINS |
http://localhost:8080 |
Origens permitidas |
AUTO_INDEX |
true |
Indexa automaticamente na inicialização |
Solução de problemas
A indexação não termina / fica em "Indexando".
O OCR é o gargalo. Verifique docker compose logs backend. Se o OCR estiver lento,
aumente OCR_DPI não ajuda — reduza para 150 para acelerar, com perda de acurácia.
Para retomar após uma interrupção, basta reiniciar o container: o cache de OCR é
preservado no volume e só as páginas faltantes são reprocessadas.
O chat responde que o texto "não aborda diretamente o assunto".
O índice pode estar vazio (/api/status) ou a pergunta pode estar longe do corpus.
Reduza MIN_SIMILARITY para 0.25, ou increase TOP_K para 10. Verifique também se a
pergunta cita uma referência explícita — nesse caso a busca é determinística e não
depende do limiar.
"Nenhum versículo pôde ser extraído".
O PDF pode não ser de uma Bíblia com capítulos e versículos numerados, ou o OCR
pode ter falhado. Inspecione o cache de OCR em /data/vector_db/ocr_cache/ para ver
o texto que foi extraído de cada página.
Ollama não sobe.
docker compose --profile local up -d. O serviço ollama-pull baixa o modelo
indicado em OLLAMA_MODEL na primeira execução. Para outro modelo:
OLLAMA_MODEL=llama3.2:3b docker compose --profile local up -d.
Porta 8080 já em uso.
FRONTEND_PORT=8090 docker compose up -d. Atualize também CORS_ORIGINS.
Estrutura do projeto
.
├── docker-compose.yml
├── .env.example
├── PDF/ # PDFs da Bíblia (entrada, somente-leitura)
├── backend/
│ ├── Dockerfile
│ ├── requirements.txt
│ ├── app/
│ │ ├── config.py # settings via pydantic-settings
│ │ ├── models.py # schemas da API
│ │ ├── extraction.py # PDF → texto (PyMuPDF + OCR + cache)
│ │ ├── bible_parser.py # livro / capítulo / versículo
│ │ ├── embedding.py # sentence-transformers
│ │ ├── vector_store.py # ChromaDB
│ │ ├── indexer.py # manifesto incremental
│ │ ├── llm.py # adaptadores Ollama/OpenAI/Groq/Anthropic
│ │ ├── chat.py # contexto, validação de citações
│ │ └── main.py # rotas FastAPI
│ └── tests/
├── frontend/
│ ├── Dockerfile
│ ├── nginx.conf
│ └── src/
│ ├── App.tsx
│ ├── api.ts
│ ├── lib/sse.ts
│ └── components/
└── docs/superpowers/ # design e plano de implementação
Segurança
- As chaves de API vivem apenas no backend, via variáveis de ambiente. Nunca chegam ao bundle do navegador.
- Os PDFs são montados somente-leitura; a escrita fica restrita ao volume
/data. - O conteúdo indexado é entregue ao modelo como dado delimitado, nunca como instrução — o que neutraliza tentativas de prompt injection via corpus.
- Mensagens acima de 4.000 caracteres são recusadas; o histórico é limitado às últimas 10 trocas.
- O endpoint de reindexação rejeita chamadas concorrentes (409) e aceita um token
opcional (
REINDEX_TOKEN).
A aplicação é local, sem autenticação. Não a exponha à internet sem colocar um proxy com autenticação à frente.