feat: add Docker Compose, environment template and README
This commit is contained in:
1 parent
7f4d4bde66
commit
b25bf8ff7d
4 files changed
+412
No files matched your search
@@ -0,0 +1,14 @@
|
|||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
.superpowers
|
||||||
|
docs
|
||||||
|
PDF
|
||||||
|
vector_db
|
||||||
|
**/__pycache__
|
||||||
|
**/*.pyc
|
||||||
|
**/.pytest_cache
|
||||||
|
**/node_modules
|
||||||
|
**/dist
|
||||||
|
**/.venv
|
||||||
|
.env
|
||||||
|
*.log
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Pastor Online — configuração
|
||||||
|
# Copie este arquivo para `.env` e ajuste os valores:
|
||||||
|
# cp .env.example .env
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Porta do frontend no host (http://localhost:8080)
|
||||||
|
FRONTEND_PORT=8080
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Provedor de LLM: ollama | openai | groq | anthropic
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
LLM_PROVIDER=ollama
|
||||||
|
|
||||||
|
# --- Ollama (local, requer o profile "local": docker compose --profile local up) ---
|
||||||
|
OLLAMA_BASE_URL=http://ollama:11434
|
||||||
|
OLLAMA_MODEL=qwen2.5:1.5b
|
||||||
|
|
||||||
|
# --- OpenAI ---
|
||||||
|
OPENAI_BASE_URL=https://api.openai.com/v1
|
||||||
|
OPENAI_MODEL=gpt-4o-mini
|
||||||
|
OPENAI_API_KEY=
|
||||||
|
|
||||||
|
# --- Groq (compatível com a API da OpenAI) ---
|
||||||
|
GROQ_BASE_URL=https://api.groq.com/openai/v1
|
||||||
|
GROQ_MODEL=llama-3.3-70b-versatile
|
||||||
|
GROQ_API_KEY=
|
||||||
|
|
||||||
|
# --- Anthropic ---
|
||||||
|
ANTHROPIC_BASE_URL=https://api.anthropic.com
|
||||||
|
ANTHROPIC_MODEL=claude-sonnet-4-5
|
||||||
|
ANTHROPIC_API_KEY=
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Embeddings (CPU)
|
||||||
|
# paraphrase-multilingual-MiniLM-L12-v2: multilíngue, ~470 MB
|
||||||
|
# all-MiniLM-L6-v2: menor e mais rápido, ~90 MB, apenas inglês
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
EMBEDDING_MODEL=paraphrase-multilingual-MiniLM-L12-v2
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# Busca
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
TOP_K=6
|
||||||
|
MIN_SIMILARITY=0.35
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# OCR (necessário quando o PDF não tem camada textual utilizável)
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
OCR_DPI=200
|
||||||
|
OCR_LANGUAGE=por+eng
|
||||||
|
OCR_TIMEOUT=120
|
||||||
|
OCR_WORKERS=4
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# API
|
||||||
|
# CORS_ORIGINS=http://localhost:8080,http://127.0.0.1:8080
|
||||||
|
# REINDEX_TOKEN=troque-por-um-segredo
|
||||||
|
# AUTO_INDEX=true
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
CORS_ORIGINS=http://localhost:8080,http://127.0.0.1:8080
|
||||||
|
REINDEX_TOKEN=
|
||||||
|
AUTO_INDEX=true
|
||||||
@@ -0,0 +1,237 @@
|
|||||||
|
# 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 apontar `PDF_DIR` para ela no
|
||||||
|
> `docker-compose.yml`. O Compose monta `./PDF` como somente-leitura em `/data/pdfs`.
|
||||||
|
|
||||||
|
### 2. Configure o provedor de LLM
|
||||||
|
|
||||||
|
```bash
|
||||||
|
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
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 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:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose logs -f backend
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Comandos úteis
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 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.
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
services:
|
||||||
|
backend:
|
||||||
|
build:
|
||||||
|
context: ./backend
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
image: pastor-online-backend:latest
|
||||||
|
container_name: pastor-online-backend
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
PDF_DIR: /data/pdfs
|
||||||
|
VECTOR_DB_DIR: /data/vector_db
|
||||||
|
LLM_PROVIDER: ${LLM_PROVIDER:-ollama}
|
||||||
|
OLLAMA_BASE_URL: ${OLLAMA_BASE_URL:-http://ollama:11434}
|
||||||
|
OLLAMA_MODEL: ${OLLAMA_MODEL:-qwen2.5:1.5b}
|
||||||
|
OPENAI_BASE_URL: ${OPENAI_BASE_URL:-https://api.openai.com/v1}
|
||||||
|
OPENAI_MODEL: ${OPENAI_MODEL:-gpt-4o-mini}
|
||||||
|
OPENAI_API_KEY: ${OPENAI_API_KEY:-}
|
||||||
|
GROQ_BASE_URL: ${GROQ_BASE_URL:-https://api.groq.com/openai/v1}
|
||||||
|
GROQ_MODEL: ${GROQ_MODEL:-llama-3.3-70b-versatile}
|
||||||
|
GROQ_API_KEY: ${GROQ_API_KEY:-}
|
||||||
|
ANTHROPIC_BASE_URL: ${ANTHROPIC_BASE_URL:-https://api.anthropic.com}
|
||||||
|
ANTHROPIC_MODEL: ${ANTHROPIC_MODEL:-claude-sonnet-4-5}
|
||||||
|
ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-}
|
||||||
|
EMBEDDING_MODEL: ${EMBEDDING_MODEL:-paraphrase-multilingual-MiniLM-L12-v2}
|
||||||
|
TOP_K: ${TOP_K:-6}
|
||||||
|
MIN_SIMILARITY: ${MIN_SIMILARITY:-0.35}
|
||||||
|
OCR_DPI: ${OCR_DPI:-200}
|
||||||
|
OCR_LANGUAGE: ${OCR_LANGUAGE:-por+eng}
|
||||||
|
OCR_TIMEOUT: ${OCR_TIMEOUT:-120}
|
||||||
|
OCR_WORKERS: ${OCR_WORKERS:-4}
|
||||||
|
CORS_ORIGINS: ${CORS_ORIGINS:-http://localhost:8080,http://127.0.0.1:8080}
|
||||||
|
REINDEX_TOKEN: ${REINDEX_TOKEN:-}
|
||||||
|
AUTO_INDEX: ${AUTO_INDEX:-true}
|
||||||
|
HF_HOME: /data/models
|
||||||
|
volumes:
|
||||||
|
- ./PDF:/data/pdfs:ro
|
||||||
|
- vector_db:/data/vector_db
|
||||||
|
networks:
|
||||||
|
- pastor
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-fsS", "http://localhost:8000/api/health"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 3
|
||||||
|
start_period: 30s
|
||||||
|
|
||||||
|
frontend:
|
||||||
|
build:
|
||||||
|
context: ./frontend
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
image: pastor-online-frontend:latest
|
||||||
|
container_name: pastor-online-frontend
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
backend:
|
||||||
|
condition: service_started
|
||||||
|
ports:
|
||||||
|
- "${FRONTEND_PORT:-8080}:80"
|
||||||
|
networks:
|
||||||
|
- pastor
|
||||||
|
|
||||||
|
ollama:
|
||||||
|
image: ollama/ollama:latest
|
||||||
|
container_name: pastor-online-ollama
|
||||||
|
restart: unless-stopped
|
||||||
|
profiles: ["local"]
|
||||||
|
volumes:
|
||||||
|
- ollama:/root/.ollama
|
||||||
|
networks:
|
||||||
|
- pastor
|
||||||
|
|
||||||
|
ollama-pull:
|
||||||
|
image: ollama/ollama:latest
|
||||||
|
container_name: pastor-online-ollama-pull
|
||||||
|
profiles: ["local"]
|
||||||
|
depends_on:
|
||||||
|
- ollama
|
||||||
|
environment:
|
||||||
|
OLLAMA_HOST: http://ollama:11434
|
||||||
|
OLLAMA_MODEL: ${OLLAMA_MODEL:-qwen2.5:1.5b}
|
||||||
|
entrypoint: ["/bin/sh", "-c"]
|
||||||
|
command:
|
||||||
|
- |
|
||||||
|
until ollama list >/dev/null 2>&1; do
|
||||||
|
echo "aguardando o Ollama..."
|
||||||
|
sleep 3
|
||||||
|
done
|
||||||
|
ollama pull "$${OLLAMA_MODEL}"
|
||||||
|
networks:
|
||||||
|
- pastor
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
vector_db:
|
||||||
|
ollama:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
pastor:
|
||||||
|
driver: bridge
|
||||||
Reference in new issue
Block a user