Este repositório é um serviço de RAG (Retrieval-Augmented Generation) exposto como API HTTP. Em termos simples:
- Ingestão: você coloca documentos (PDF, texto, etc.) em uma pasta de dados; o sistema lê esses arquivos, divide em trechos (chunks), calcula embeddings e grava os vetores em um banco PostgreSQL com a extensão pgvector.
- Consulta: você envia uma pergunta em linguagem natural; o sistema recupera os trechos mais semelhantes no Postgres e envia esse contexto a um LLM (OpenAI por padrão no chat) para gerar uma resposta fundamentada nos documentos.
Além do RAG, a API inclui autenticação: usuários se registram e fazem login; recebem um JWT. As rotas de ingestão e de pergunta exigem esse token. Os dados dos usuários (e-mail, hash da senha) ficam na mesma instância PostgreSQL (tabela dedicada), configurável via SQLAlchemy.
O código está organizado em camadas:
| Pacote / arquivo | Função |
|---|---|
app.py |
Aplicação FastAPI: inicialização (lifespan), registro de routers, uvicorn app:app. |
api/ |
Camada HTTP: routers (/auth, /health, /query, …), configuração (config.py), dependências (dependencies.py), schemas Pydantic por área, serviço que constrói o RAGService. |
auth/ |
Domínio de identidade: modelos ORM, JWT, hash de senhas, sessão com o DB, get_current_user. |
rag/ |
Domínio RAG: embeddings, Postgres/PGVector, indexação LlamaIndex, ingestão, query engine, pipeline opcional em CLI. |
cli.py |
Entrada alternativa à API: executa a pipeline rag.pipeline na linha de comando (útil para indexar ou testar sem subir o servidor). |
Ou seja: dá para usar só a API (cliente HTTP ou frontend) ou só a CLI para tarefas em lote; o núcleo RAG é compartilhado.
- Python 3.11 ou superior (recomendado 3.12).
- PostgreSQL com a extensão pgvector instalada e ativa no banco de dados que você for usar — ou Docker + Docker Compose, para subir o Postgres com pgvector via
docker-compose.yml(passo 4). - Conta/chave OpenAI (ou configuração alinhada ao que está em
rag/): para respostas de chat, o fluxo atual espera a API OpenAI; os embeddings podem ser locais (Hugging Face) ou da OpenAI, conforme as variáveis de ambiente.
git clone <url-do-seu-repositório>.git
cd <pasta-do-projeto>Linux / macOS:
python3 -m venv venv
source venv/bin/activateWindows (PowerShell):
python -m venv venv
.\venv\Scripts\Activate.ps1Verifique se o python aponta para o ambiente virtual (which python ou where python).
python -m pip install --upgrade pip
pip install -r requirements.txtO arquivo requirements.txt inclui, entre outras: FastAPI, Uvicorn, LlamaIndex, integração Postgres/PGVector, SQLAlchemy, PyJWT, bcrypt, embeddings OpenAI e Hugging Face, leitores de arquivo e, para desenvolvimento, pytest e httpx.
Você pode subir o Postgres com Docker Compose (recomendado para desenvolvimento local) ou usar uma instância já existente.
-
Instale Docker e o plugin Docker Compose (v2: comando
docker compose). -
Na raiz do projeto existe o arquivo
docker-compose.yml: serviçopgvector-db, imagempgvector/pgvector:pg17, bancoappdb, usuário e senhapostgres/postgres. -
O arquivo
init.sqlé montado em/docker-entrypoint-initdb.d/e, na primeira inicialização do volume, executaCREATE EXTENSION IF NOT EXISTS vector;automaticamente. -
Se a porta 5432 no seu computador já estiver em uso, defina outra antes de subir o container. O Compose interpola
${PGPORT}a partir do arquivo.envna raiz do projeto (o mesmo do passo 5) ou do ambiente do shell:# exemplo no .env (pode criar o .env antes, copiando .env_example) PGPORT=5433 -
Suba o serviço em segundo plano:
docker compose up -d
-
Confira se o container está ativo:
docker compose ps. Logs:docker compose logs -f pgvector-db. -
No
.env, alinhe a conexão da aplicação com o que o Compose expõe (valores padrão do projeto):Variável Valor típico (Compose local) PGHOSTlocalhostPGPORTo mesmo que você definiu (ex.: 5432ou5433)POSTGRES_DBappdbPOSTGRES_USERpostgresPOSTGRES_PASSWORDpostgresNota: dados ficam no volume Docker
pgvector_data. Para recomeçar do zero (apaga dados):docker compose down -ve depoisdocker compose up -dde novo.
- Crie um banco de dados (ex.:
appdb). - Conecte-se com um usuário que tenha permissão e execute:
CREATE EXTENSION IF NOT EXISTS vector;- Garanta que as variáveis
PGHOST,PGPORT,POSTGRES_DB,POSTGRES_USERePOSTGRES_PASSWORDno.envbatem com essa instância (ou useAUTH_DATABASE_URLse quiser uma URL completa só para a camada auth — verauth/database.py).
Na primeira vez que a API sobe, a tabela de usuários é criada automaticamente (init_db).
Não envie segredos ao Git. Copie o modelo e edite:
cp .env_example .envPreencha no mínimo:
| Variável | Descrição |
|---|---|
JWT_SECRET_KEY |
Obrigatória para a API subir. Crie um segredo forte, ex.: openssl rand -hex 32. |
OPENAI_API_KEY |
Chave OpenAI (LLM e, se for o caso, embeddings). |
POSTGRES_* / PGHOST / PGPORT |
Conexão com o Postgres (e o mesmo banco usado pelo vector store, salvo AUTH_DATABASE_URL). |
USE_LOCAL_EMBEDDINGS |
1 para embeddings Hugging Face locais; 0 para embeddings OpenAI. |
RAG_DATA_DIR |
Pasta onde ficam os documentos para ingestão (caminho relativo ou absoluto; é resolvido com Path.resolve()). Padrão: ./data. |
RAG_SIMILARITY_TOP_K |
Sem rerank: quantidade de trechos do PGVector que vão ao LLM (similarity_top_k). Padrão: 3. |
RAG_RERANK_ENABLED |
1 / true / yes / on liga rerank com cross-encoder após a busca vectorial (exige torch e sentence-transformers). |
RAG_RETRIEVAL_TOP_K |
Com rerank: candidatos trazidos do Postgres antes do rerank. Padrão: 20 (nunca menor que RAG_RERANK_TOP_N na prática). |
RAG_RERANK_TOP_N |
Com rerank: trechos enviados ao LLM depois da reordenação. Padrão: 5. |
RAG_RERANK_MODEL |
Nome do modelo cross-encoder no Hugging Face. Padrão: cross-encoder/ms-marco-MiniLM-L-6-v2. |
RAG_RERANK_DEVICE |
Opcional: cpu, cuda, etc. Vazio = detecção automática. |
Consulte os comentários em .env_example para PGVECTOR_TABLE, MOVE_PROCESSED_FILES, API_HOST, API_PORT, JWT_EXPIRE_MINUTES, etc.
Crie a pasta de ingestão (se usar o caminho padrão):
mkdir -p dataColoque nela os arquivos que deseja indexar (os tipos suportados dependem de llama-index-readers-file e da configuração do projeto).
uvicorn app:app --host 0.0.0.0 --port 8000Ou, a partir da raiz com o ambiente ativo:
python app.pyDocumentação interativa OpenAPI: http://127.0.0.1:8000/docs
sequenceDiagram
participant Client as Cliente_HTTP
participant API as FastAPI_app
participant Auth as auth_JWT_DB
participant RAG as RAGService
participant PG as Postgres_pgvector
Client->>API: POST /auth/register ou /auth/login
API->>Auth: persistencia_validacao
Auth-->>Client: JWT
Client->>API: POST /ingest ou /query com Bearer
API->>Auth: valida JWT
API->>RAG: get_rag_service
RAG->>PG: vector_store_index
RAG-->>Client: JSON resposta
- Inicialização (
lifespan): carrega o.env, valida a configuração de JWT, inicializa o banco de usuários, monta oRAGService: embeddings, tabela lógica PGVector, índice LlamaIndex a partir do store, query engine. - Registro / login: cria ou verifica o usuário; devolve
access_token(JWT) eexpires_in. - Rotas RAG: dependem de
get_current_user; o serviço emapp.state.ragexecuta ingestão (leitura deRAG_DATA_DIR) ou query (recuperação + LLM). - Rerank (opcional): com
RAG_RERANK_ENABLED, o retriever traz mais trechos do Postgres (RAG_RETRIEVAL_TOP_K); um postprocessorSentenceTransformerRerankreordena e corta emRAG_RERANK_TOP_Nantes da síntese pelo LLM. Nos JSON cominclude_sources=true, o camposcorede cada fonte reflete o score do cross-encoder.
A pipeline python cli.py usa rag/pipeline.py com caminhos padrão próprios; para ficar alinhado à API, use a mesma RAG_DATA_DIR e as mesmas variáveis Postgres no .env.
O pacote rag/ implementa o fluxo ponta a ponta. A API (RAGService) e a CLI (rag/pipeline.py) reutilizam as mesmas peças; a ordem lógica é a seguinte.
rag/embeddings.py escolhe o modelo que transforma texto em vetores:
USE_LOCAL_EMBEDDINGS=1: Hugging Face (LOCAL_EMBEDDING_MODEL), sem custo de API por token de embedding.- Caso contrário: OpenAI (
OPENAI_EMBEDDING_MODEL, ex.text-embedding-3-small), exigeOPENAI_API_KEY.
A dimensão do vetor (ex. 384, 1536) é obtida com uma embedding de teste e deve ser consistente com a tabela PGVector usada.
rag/postgres_store.py define o nome lógico da tabela (PGVECTOR_TABLE ou documents_<dim> por padrão) e cria o PGVectorStore: ligação ao Postgres, dimensão dos vetores e persistência dos chunks.
No banco, a tabela física segue o padrão do conector (ex. public.data_<nome_lógico_em_minúsculas>). É necessário a extensão vector (ver secção Docker / Postgres).
O StorageContext do LlamaIndex associa o vector store ao fluxo de indexação: ao indexar, os nós gerados são enviados ao Postgres.
- Resolve
RAG_DATA_DIR(API e CLI) como raiz dos arquivos. - Calcula a pasta processados (
PROCESSED_SUBDIRouPROCESSED_DIR) e padrões de exclusão para o leitor não reindexar o que já foi arquivado. - Usa
SimpleDirectoryReader(e extrator PDF quando disponível) para carregar arquivos; o particionamento em nós/chunks é feito pelo LlamaIndex na indexação (configuração padrão do índice, salvo ajustes futuros no código). - Remove bytes NUL dos textos antes de indexar; opcionalmente move arquivos indexados para processados (
MOVE_PROCESSED_FILES).
Ingestão via API (/ingest, /ingest/file) segue a mesma lógica sobre data_dir e locks no serviço.
rag/indexing.py — build_or_load_index:
- Com documentos novos:
VectorStoreIndex.from_documentsgera embeddings, insere no PGVector e (na CLI) pode mover arquivos para processados. - Sem documentos:
VectorStoreIndex.from_vector_storesó reabre o índice a partir do que já está no Postgres.
Na API, após cada ingestão o serviço recarrega o índice em memória a partir do store para o query engine ver dados atualizados.
- Cria o
RetrieverQueryEnginecom LLM OpenAI (OPENAI_CHAT_MODEL). similarity_top_k: quantidade de trechos candidatos vindos do Postgres (RAG_SIMILARITY_TOP_K, ou com rerankRAG_RETRIEVAL_TOP_K/maxcomRAG_RERANK_TOP_N).- Rerank opcional:
SentenceTransformerRerankreduz e reordena candidatos antes da resposta (RAG_RERANK_*).
Para cada pergunta:
- A pergunta é embedada com o mesmo modelo de embeddings do índice.
- O retriever executa busca por similaridade no PGVector (top‑K).
- Se o rerank estiver ligado, os nós são reordenados e cortados em top‑N.
- O response synthesizer envia os trechos escolhidos ao LLM (OpenAI), que gera a resposta final.
- A API devolve JSON via
response_to_payload(answer,source_count, opcionalmentesourcescom scores e metadados convertidos para JSON — sem tipos NumPy crus).
| Método | Caminho | Descrição |
|---|---|---|
GET |
/health |
Liveness: confirma que o processo responde (não valida Postgres). |
Exemplo:
curl -s http://127.0.0.1:8000/healthResposta típica: {"status":"ok"}.
| Método | Caminho | Corpo JSON | Respostas notáveis |
|---|---|---|---|
POST |
/auth/register |
{"email": "...", "password": "..."} |
201 usuário criado; 409 e-mail já registrado. Senha: 8–128 caracteres. |
POST |
/auth/login |
{"email": "...", "password": "..."} |
200 com access_token, token_type, expires_in; 401 credenciais inválidas. |
Exemplo de registro:
curl -s -X POST http://127.0.0.1:8000/auth/register \
-H "Content-Type: application/json" \
-d '{"email":"usuario@exemplo.com","password":"senhaSegura8"}'Exemplo de login:
curl -s -X POST http://127.0.0.1:8000/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"usuario@exemplo.com","password":"senhaSegura8"}'Guarde o access_token para as rotas seguintes.
| Método | Caminho | Corpo JSON | Descrição |
|---|---|---|---|
POST |
/query |
{"question": "...", "include_sources": false} |
Pergunta ao RAG; opcionalmente inclui trechos das fontes (ou via SHOW_RETRIEVAL_SOURCES=1 no ambiente). 503 se o serviço RAG falhar (ex.: LLM indisponível). |
POST |
/ingest |
{} |
Indexa recursivamente arquivos sob RAG_DATA_DIR (detalhes em rag/, pastas de processados, etc.). |
POST |
/ingest/file |
{"filename": "relativo/a/data/doc.pdf"} |
Indexa um único arquivo; caminho relativo à pasta de dados, com proteção contra path traversal. 404 se não existir; 400 se inválido. |
Exemplo de pergunta:
TOKEN="<cole_o_access_token_aqui>"
curl -s -X POST http://127.0.0.1:8000/query \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"question":"Resuma o que os documentos dizem sobre o tema X.","include_sources":false}'Exemplo de ingestão completa:
curl -s -X POST http://127.0.0.1:8000/ingest \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{}'O script scripts/api_test.sh automatiza chamadas com curl (registro, login, export do token, health, query, ingest).
chmod +x scripts/api_test.sh
./scripts/api_test.sh help
./scripts/api_test.sh health
./scripts/api_test.sh register user@mail.com 'senhaSegura8'
eval "$(./scripts/api_test.sh login-export user@mail.com 'senhaSegura8')"
./scripts/api_test.sh query "Sua pergunta aqui."Defina BASE_URL se o servidor não estiver em http://localhost:8000.
Com o ambiente virtual ativo:
pytest tests/ -qOs testes de fumaça usam mocks para não exigir Postgres nem RAG real no /health e verificam 401 em /query sem token.
Com o servidor em execução, use a interface Swagger em /docs ou o schema OpenAPI em /openapi.json.
| Caminho | Papel |
|---|---|
app.py |
FastAPI + lifespan + uvicorn em modo __main__. |
cli.py |
Pipeline RAG em linha de comando. |
api/ |
Routers, config, dependencies, schemas, services, core. |
auth/ |
Usuários, JWT, banco. |
rag/ |
Pipeline RAG, Postgres, LlamaIndex. |
tests/ |
Testes pytest. |
scripts/api_test.sh |
Chamadas HTTP de exemplo. |
docker-compose.yml |
Postgres 17 + pgvector para desenvolvimento local. |
init.sql |
Cria a extensão vector na primeira subida do container. |
- Schemas Pydantic: rotas
/auth/*usam modelos emauth/schemas.py; corpos das rotas RAG emapi/schemas/(ex.:QueryBody,IngestFileBody). api/core/: ponto de entrada para verificações na subida (ex.: JWT configurado).
Veja o arquivo LICENSE no repositório (MIT, conforme o seu projeto no GitHub).