Skip to content

Waelson/RAG-as-a-Service

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

RAG as a Service (LlamaIndex + FastAPI)

O que é este projeto

Este repositório é um serviço de RAG (Retrieval-Augmented Generation) exposto como API HTTP. Em termos simples:

  1. 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.
  2. 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.


Pré-requisitos

  • 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.

Configuração passo a passo

1. Clonar o repositório

git clone <url-do-seu-repositório>.git
cd <pasta-do-projeto>

2. Criar e ativar o ambiente virtual

Linux / macOS:

python3 -m venv venv
source venv/bin/activate

Windows (PowerShell):

python -m venv venv
.\venv\Scripts\Activate.ps1

Verifique se o python aponta para o ambiente virtual (which python ou where python).

3. Atualizar o pip e instalar dependências

python -m pip install --upgrade pip
pip install -r requirements.txt

O 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.

4. Banco de dados PostgreSQL

Você pode subir o Postgres com Docker Compose (recomendado para desenvolvimento local) ou usar uma instância já existente.

Opção A — Docker Compose

  1. Instale Docker e o plugin Docker Compose (v2: comando docker compose).

  2. Na raiz do projeto existe o arquivo docker-compose.yml: serviço pgvector-db, imagem pgvector/pgvector:pg17, banco appdb, usuário e senha postgres / postgres.

  3. O arquivo init.sql é montado em /docker-entrypoint-initdb.d/ e, na primeira inicialização do volume, executa CREATE EXTENSION IF NOT EXISTS vector; automaticamente.

  4. 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 .env na 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
  5. Suba o serviço em segundo plano:

    docker compose up -d
  6. Confira se o container está ativo: docker compose ps. Logs: docker compose logs -f pgvector-db.

  7. 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)
    PGHOST localhost
    PGPORT o mesmo que você definiu (ex.: 5432 ou 5433)
    POSTGRES_DB appdb
    POSTGRES_USER postgres
    POSTGRES_PASSWORD postgres

    Nota: dados ficam no volume Docker pgvector_data. Para recomeçar do zero (apaga dados): docker compose down -v e depois docker compose up -d de novo.

Opção B — PostgreSQL instalado manualmente ou gerenciado

  1. Crie um banco de dados (ex.: appdb).
  2. Conecte-se com um usuário que tenha permissão e execute:
CREATE EXTENSION IF NOT EXISTS vector;
  1. Garanta que as variáveis PGHOST, PGPORT, POSTGRES_DB, POSTGRES_USER e POSTGRES_PASSWORD no .env batem com essa instância (ou use AUTH_DATABASE_URL se quiser uma URL completa só para a camada auth — ver auth/database.py).

Na primeira vez que a API sobe, a tabela de usuários é criada automaticamente (init_db).

5. Arquivo de ambiente .env

Não envie segredos ao Git. Copie o modelo e edite:

cp .env_example .env

Preencha 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.

6. Pasta de dados

Crie a pasta de ingestão (se usar o caminho padrão):

mkdir -p data

Coloque nela os arquivos que deseja indexar (os tipos suportados dependem de llama-index-readers-file e da configuração do projeto).

7. Subir a API

uvicorn app:app --host 0.0.0.0 --port 8000

Ou, a partir da raiz com o ambiente ativo:

python app.py

Documentação interativa OpenAPI: http://127.0.0.1:8000/docs


Como funciona (fluxo)

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
Loading
  1. Inicialização (lifespan): carrega o .env, valida a configuração de JWT, inicializa o banco de usuários, monta o RAGService: embeddings, tabela lógica PGVector, índice LlamaIndex a partir do store, query engine.
  2. Registro / login: cria ou verifica o usuário; devolve access_token (JWT) e expires_in.
  3. Rotas RAG: dependem de get_current_user; o serviço em app.state.rag executa ingestão (leitura de RAG_DATA_DIR) ou query (recuperação + LLM).
  4. Rerank (opcional): com RAG_RERANK_ENABLED, o retriever traz mais trechos do Postgres (RAG_RETRIEVAL_TOP_K); um postprocessor SentenceTransformerRerank reordena e corta em RAG_RERANK_TOP_N antes da síntese pelo LLM. Nos JSON com include_sources=true, o campo score de 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.


Pipeline RAG (etapas)

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.

1. Modelo de embeddings

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), exige OPENAI_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.

2. Postgres e PGVector

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).

3. Contexto de armazenamento

O StorageContext do LlamaIndex associa o vector store ao fluxo de indexação: ao indexar, os nós gerados são enviados ao Postgres.

4. Leitura e preparação dos documentos

rag/ingestion.py:

  • Resolve RAG_DATA_DIR (API e CLI) como raiz dos arquivos.
  • Calcula a pasta processados (PROCESSED_SUBDIR ou PROCESSED_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.

5. Indexação (escrita no índice)

rag/indexing.pybuild_or_load_index:

  • Com documentos novos: VectorStoreIndex.from_documents gera embeddings, insere no PGVector e (na CLI) pode mover arquivos para processados.
  • Sem documentos: VectorStoreIndex.from_vector_storereabre 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.

6. Arranque do query engine

rag/querying.py:

  • Cria o RetrieverQueryEngine com LLM OpenAI (OPENAI_CHAT_MODEL).
  • similarity_top_k: quantidade de trechos candidatos vindos do Postgres (RAG_SIMILARITY_TOP_K, ou com rerank RAG_RETRIEVAL_TOP_K / max com RAG_RERANK_TOP_N).
  • Rerank opcional: SentenceTransformerRerank reduz e reordena candidatos antes da resposta (RAG_RERANK_*).

7. Consulta (pergunta → resposta)

Para cada pergunta:

  1. A pergunta é embedada com o mesmo modelo de embeddings do índice.
  2. O retriever executa busca por similaridade no PGVector (top‑K).
  3. Se o rerank estiver ligado, os nós são reordenados e cortados em top‑N.
  4. O response synthesizer envia os trechos escolhidos ao LLM (OpenAI), que gera a resposta final.
  5. A API devolve JSON via response_to_payload (answer, source_count, opcionalmente sources com scores e metadados convertidos para JSON — sem tipos NumPy crus).

Endpoints

Saúde (público)

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/health

Resposta típica: {"status":"ok"}.


Autenticação (público)

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.


RAG (requer Authorization: Bearer <token>)

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 '{}'

Script de testes rápidos

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.


Testes automáticos

Com o ambiente virtual ativo:

pytest tests/ -q

Os testes de fumaça usam mocks para não exigir Postgres nem RAG real no /health e verificam 401 em /query sem token.


Documentação da API

Com o servidor em execução, use a interface Swagger em /docs ou o schema OpenAPI em /openapi.json.


Estrutura resumida do repositório

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.

Convenções úteis

  • Schemas Pydantic: rotas /auth/* usam modelos em auth/schemas.py; corpos das rotas RAG em api/schemas/ (ex.: QueryBody, IngestFileBody).
  • api/core/: ponto de entrada para verificações na subida (ex.: JWT configurado).

Licença

Veja o arquivo LICENSE no repositório (MIT, conforme o seu projeto no GitHub).

About

API REST em FastAPI para RAG com LlamaIndex: ingestão de documentos, consultas com LLM, vector store em Postgres (PGVector), autenticação JWT e registo de utilizadores. Inclui pipeline opcional em linha de comando.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors