Skip to content

Repository files navigation

Document RAG API

OCR/VLM RAG API: aplikacja typu REST API w FastAPI, która przyjmuje obrazy dokumentów (faktury, paragony, formularze), odczytuje ich zawartość za pomocą modelu GLM (vision-language) przez OpenRouter, a następnie pozwala wyszukiwać informacje i zadawać pytania z wykorzystaniem podejścia RAG.

Architektura

Projekt składa się z czterech serwisów:

Serwis Rola
api FastAPI — obsługuje endpointy REST, upload obrazów, indeksowanie w ChromaDB, wyszukiwanie i odpowiedzi RAG
worker Celery — przetwarza dokumenty w tle (wywołuje GLM przez OpenRouter, zapisuje wyniki do SQLite)
redis Broker wiadomości Celery + backend wyników
chroma ChromaDB w trybie server — baza wektorowa do indeksu semantycznego

Przepływ danych

obraz dokumentu → POST /documents/upload → Celery task → GLM (OpenRouter) → tekst + JSON
→ POST /documents/{id}/index → embedding (OpenAI) → ChromaDB
→ POST /rag/search lub /rag/answer → wyszukiwanie semantyczne → odpowiedź RAG (GPT-4.1)

Struktura projektu

document_rag/
├── api/                      # Serwis FastAPI
│   ├── main.py               # Punkt wejścia aplikacji
│   ├── config.py             # Konfiguracja (zmienne środowiskowe)
│   ├── database.py           # SQLAlchemy engine + session
│   ├── models.py             # Model ORM (Document)
│   ├── schemas.py            # Schematy Pydantic (request/response)
│   ├── celery_app.py         # Klient Celery (send_task)
│   ├── rag.py                # Logika RAG (ChromaDB + LangChain)
│   ├── endpoints/            # Endpointy API
│   │   ├── health.py         # GET /health
│   │   ├── documents.py      # POST /upload, GET /{id}, POST /{id}/index
│   │   └── rag.py            # POST /search, POST /answer
│   ├── Dockerfile
│   └── requirements.txt
├── worker/                   # Serwis Celery worker
│   ├── tasks.py              # Task: process_document
│   ├── vlm.py                # Integracja z OpenRouter GLM
│   ├── celery_app.py         # Instancja workera Celery
│   ├── config.py             # Konfiguracja workera
│   ├── _models.py            # Model SQLAlchemy (mirror api/models.py)
│   ├── database.py           # Połączenie z SQLite
│   ├── Dockerfile
│   └── requirements.txt
├── k8s/                      # Manifesty Kubernetes
├── docker-compose.yml
├── .dockerignore
├── .env.example
├── download_invoices.py      # Skrypt pobierania danych z HuggingFace
└── prd.md                    # Specyfikacja projektu

Endpointy API

Metoda Ścieżka Opis Status HTTP
GET /health Sprawdzenie stanu aplikacji 200
POST /documents/upload Upload obrazu dokumentu 202 Accepted
GET /documents/{document_id} Status przetwarzania dokumentu 200 / 404
POST /documents/{document_id}/index Dodanie dokumentu do indeksu RAG 200 / 404 / 409
POST /rag/search Wyszukiwanie semantyczne w dokumentach 200
POST /rag/answer Pytanie do dokumentów z odpowiedzią RAG 200

Wymagania

  • Docker + Docker Compose
  • Klucz API OpenAI (embeddingi + GPT-4o-mini)
  • Klucz API OpenRouter (model GLM do ekstrakcji dokumentów)

Pobieranie danych (faktury)

Projekt korzysta z datasetu katanaml-org/invoices-donut-data-v1.

pip install datasets Pillow
python download_invoices.py

Obrazy zostaną zapisane w katalogu data/.

Uruchomienie — Docker Compose

1. Konfiguracja

Skopiuj plik .env.example do .env i uzupełnij klucze API:

cp .env.example .env
# Edytuj .env — wpisz OPENAI_API_KEY i OPENROUTER_API_KEY

2. Budowanie i uruchomienie

docker compose up --build

Aplikacja będzie dostępna pod adresem: http://localhost:8000

Dokumentacja Swagger UI: http://localhost:8000/docs

3. Przykłady użycia

# Upload dokumentu
curl -X POST http://localhost:8000/documents/upload \
  -F "file=@data/0000.png"

# Sprawdzenie statusu
curl http://localhost:8000/documents/{document_id}

# Indeksowanie dokumentu (po zakończeniu przetwarzania)
curl -X POST http://localhost:8000/documents/{document_id}/index

# Wyszukiwanie
curl -X POST http://localhost:8000/rag/search \
  -H "Content-Type: application/json" \
  -d '{"query": "Jaka jest kwota brutto?", "top_k": 5}'

# Pytanie z odpowiedzią RAG
curl -X POST http://localhost:8000/rag/answer \
  -H "Content-Type: application/json" \
  -d '{"question": "Kto jest sprzedawcą na fakturze?"}'

4. Zatrzymanie

docker compose down

Aby usunąć również wolumeny (dane):

docker compose down -v

Uruchomienie — Kubernetes (Minikube)

1. Przygotowanie klastra

minikube start
eval $(minikube docker-env)

2. Budowanie obrazów

docker build -t document-rag-api:latest -f api/Dockerfile .
docker build -t document-rag-worker:latest -f worker/Dockerfile .

3. Konfiguracja secretów

Edytuj k8s/secret.yaml — wpisz swoje klucze API, a następnie:

kubectl apply -f k8s/namespace.yaml
kubectl apply -f k8s/secret.yaml
kubectl apply -f k8s/configmap.yaml
kubectl apply -f k8s/

4. Dostęp do API

minikube service api -n document-rag

5. Przykłady użycia

kubectl port-forward -n document-rag svc/api 8000:8000

# Upload dokumentu
curl -X POST http://localhost:8000/documents/upload \
  -F "file=@data/0000.png"

# Sprawdzenie statusu
curl http://localhost:8000/documents/{document_id}

# Indeksowanie dokumentu (po zakończeniu przetwarzania)
curl -X POST http://localhost:8000/documents/{document_id}/index

# Wyszukiwanie
curl -X POST http://localhost:8000/rag/search \
  -H "Content-Type: application/json" \
  -d '{"query": "Jaka jest kwota brutto?", "top_k": 5}'

# Pytanie z odpowiedzią RAG
curl -X POST http://localhost:8000/rag/answer \
  -H "Content-Type: application/json" \
  -d '{"question": "Kto jest sprzedawcą na fakturze?"}'

Docker — wyjaśnienia

Czym jest Dockerfile

Dockerfile to plik tekstowy zawierający instrukcje do automatycznego budowania obrazu Docker. Każda instrukcja (FROM, COPY, RUN, CMD) definiuje jeden krok budowy. Docker czyta Dockerfile od góry do dołu i wykonuje instrukcje sekwencyjnie, tworząc obraz warstwa po warstwie.

Czym jest .dockerignore

Plik .dockerignore działa analogicznie do .gitignore — określa, które pliki i katalogi mają być wykluczone z kontekstu budowania (build context) wysyłanego do demona Docker. Dzięki temu:

  • Obraz nie zawiera niepotrzebnych plików (np. .git, .venv, data/)
  • Budowanie jest szybsze (mniejszy kontekst)
  • Obraz jest mniejszy i bezpieczniejszy (np. nie zawiera .env z kluczami API)

Czym jest docker context

Docker context (kontekst budowania) to zestaw plików i katalogów, które są wysyłane do demona Docker podczas budowania obrazu. Domyślnie jest to katalog, w którym znajduje się Dockerfile (lub katalog podany jako argument docker build). Wszystkie instrukcje COPY i ADD odwołują się do plików w ramach tego kontekstu.

W naszym projekcie kontekstem budowania jest katalog główny (document_rag/), a Dockerfile wskazujemy flagą -f:

docker build -t document-rag-api:latest -f api/Dockerfile .

Jak działają warstwy obrazu

Każda instrukcja w Dockerfile tworzy nową warstwę (layer) obrazu. Warstwy są cachowane — jeśli instrukcja i jej dane wejściowe się nie zmieniły, Docker używa wersji z cache zamiast wykonywać ją ponownie.

Warstwy są addytywne: każda kolejna dodaje zmiany (pliki, pakiety) na wierzch poprzedniej. Finalny obraz to złożenie wszystkich warstw.

Jak zoptymalizować czas budowy obrazu

  1. Wykorzystuj cache warstw — instrukcje, które zmieniają się rzadko (np. COPY requirements.txt + RUN pip install), umieszczaj wcześniej. Instrukcje zmieniające się często (np. COPY . . z kodem źródłowym) — na końcu.

  2. Używaj .dockerignore — zmniejsz kontekst budowania, wykluczając niepotrzebne pliki.

  3. Używaj --no-cache-dir w pip — zapobiega zapisywaniu cache pip wewnątrz obrazu.

  4. Wybieraj lekkie obrazy bazowe — np. python:3.11-slim zamiast python:3.11.

  5. Łącz instrukcje RUN — mniejsza liczba warstw, np. RUN apt-get update && apt-get install -y gcc && rm -rf /var/lib/apt/lists/*.

Dlaczego kolejność instrukcji w Dockerfile ma znaczenie

Docker buduje obraz warstwa po warstwie. Jeśli jedna warstwa się zmieni, wszystkie kolejne muszą być przebudowane (cache jest unieważniany od tego miejsca w dół).

Dlatego w naszych Dockerfile:

# 1. Kopiujemy TYLKO requirements.txt
COPY api/requirements.txt .
# 2. Instalujemy zależności (ta warstwa jest cachowana,
#    dopóki requirements.txt się nie zmieni)
RUN pip install --no-cache-dir -r requirements.txt
# 3. Dopiero teraz kopiujemy kod źródłowy
COPY api/ ./api/

Gdybyśmy skopiowali cały kod przed instalacją zależności (COPY . .RUN pip install), każda zmiana w kodzie wymuszałaby ponowną instalację wszystkich pakietów. Dzięki rozdzieleniu tych kroków, zmiana kodu nie unieważnia cache warstwy z zależnościami.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages