433 lines
13 KiB
Markdown
433 lines
13 KiB
Markdown
---
|
||
type: Note
|
||
---
|
||
# Artykuł: „Jak zbudować projekt Generative AI (LLM + RAG) w sposób produkcyjny”
|
||
|
||
Pewnie — zrobię z tej struktury **(1) artykuł** (czytelny, edukacyjny) oraz **(2) procedurę operacyjną (SOP/runbook)**, tak żeby dało się to wdrożyć i utrzymywać w zespole.
|
||
|
||
***
|
||
|
||
# 1) Artykuł: „Jak zbudować projekt Generative AI (LLM + RAG) w sposób produkcyjny”
|
||
|
||
## Wstęp
|
||
|
||
Zbudowanie działającego prototypu opartego na modelu językowym (LLM) i architekturze RAG (Retrieval-Augmented Generation) w notatniku Jupyter zajmuje dziś zaledwie kilka godzin. Jednak droga od "działającego dema" do stabilnego, skalowalnego i bezpiecznego systemu produkcyjnego to zupełnie inne wyzwanie techniczne.
|
||
|
||
Wiele projektów Generative AI umiera na etapie Proof of Concept (PoC) lub szybko przeradza się w trudną do utrzymania plątaninę skryptów. Dlaczego? Ponieważ na etapie skalowania zaczyna brakować w nich **wyraźnego podziału odpowiedzialności**, **powtarzalnego pipeline'u danych**, **wersjonowania promptów** oraz **twardych zasad testowania i automatyzacji**.
|
||
|
||
W tym artykule omówimy, jak ustrukturyzować produkcyjne repozytorium dla projektu AI. Pokażę Ci sprawdzony szkielet architektury, który:
|
||
- **bezboleśnie obsługuje wielu dostawców modeli** (OpenAI, Anthropic, modele lokalne),
|
||
- **posiada dedykowany moduł RAG** i uporządkowany pipeline ekstrakcji danych,
|
||
- **separuje warstwę inżynierii promptów** od logiki biznesowej aplikacji,
|
||
- **jest w pełni gotowy do konteneryzacji** (Docker) i wdrażania w cyklach CI/CD.
|
||
|
||
Jeśli chcesz, aby Twoje systemy sztucznej inteligencji opierały się na solidnych fundamentach inżynierii oprogramowania, a nie tylko "magii promptów" – ten przewodnik jest dla Ciebie.
|
||
|
||
***
|
||
|
||
## Cel architektury: porządek zamiast „magii”
|
||
|
||
W systemach GenAI najbardziej kosztowne błędy biorą się z:
|
||
|
||
1. braku wersjonowania i powtarzalności danych / indeksów,
|
||
2. braku kontroli nad promptami (zmiany „na żywo” bez śladu),
|
||
3. braku testów regresji (model/prompt/dane zmieniają zachowanie),
|
||
4. mieszania logiki RAG z logiką aplikacji.
|
||
|
||
Ta struktura rozwiązuje to przez **jasne granice** między warstwami.
|
||
|
||
***
|
||
|
||
## Omówienie katalogów – „co gdzie mieszka i dlaczego”
|
||
|
||
## `config/` – konfiguracja projektu
|
||
|
||
Tu trzymasz pliki konfiguracyjne:
|
||
|
||
- `model_config.yaml` – wybór modelu, ustawienia (temperatura, max tokens), klucze API lub referencje do secret managera,
|
||
- `logging_config.yaml` – format logów, poziomy, destynacje, korelacja requestów.
|
||
|
||
**Dlaczego to ważne?**\
|
||
\
|
||
Konfiguracja nie powinna być „zaszyta” w kodzie, bo będziesz przełączać modele i tryby (dev/stage/prod), a logowanie jest podstawą operacyjności.
|
||
|
||
***
|
||
|
||
## `data/` – dane projektu (artefakty)
|
||
|
||
Podział:
|
||
|
||
- `cache/` – przetworzone dane, cache odpowiedzi, snapshoty,
|
||
- `embeddings/` – wygenerowane embeddingi (np. batch do dalszego indeksowania),
|
||
- `vectordb/` – pliki/bazy vector store (w zależności od technologii).
|
||
|
||
**Zasada**: to nie jest „źródło prawdy” (source of truth), tylko **artefakty** powstające z danych wejściowych i pipeline’u.\
|
||
\
|
||
W produkcji warto dodać:
|
||
|
||
- politykę retencji,
|
||
- wersjonowanie indeksów,
|
||
- metadane: data budowy, model embeddingów, parametry chunkingu.
|
||
|
||
***
|
||
|
||
## `src/` – kod aplikacji (serce systemu)
|
||
|
||
### `src/core/` – abstrakcja LLM i integracje
|
||
|
||
Pliki typu:
|
||
|
||
- `base_llm.py` – wspólny interfejs (send(), generate(), stream(), itp.),
|
||
- `gpt_client.py` – klient do OpenAI,
|
||
- `claude_client.py` – klient do Anthropic,
|
||
- `local_llm.py` – klient do modeli lokalnych,
|
||
- `model_factory.py` – fabryka wybierająca backend na podstawie configu.
|
||
|
||
**Korzyść**: aplikacja nie jest „przyspawana” do jednego dostawcy. Zmiana modelu = zmiana configu.
|
||
|
||
***
|
||
|
||
### `src/prompts/` – narzędzia do promptów
|
||
|
||
- `templates.py` – szablony promptów (np. Jinja), stałe sekcje system/developer/user,
|
||
- `chain.py` – chaining: wieloetapowe promptowanie (np. plan → wykonanie → walidacja).
|
||
|
||
**Dyscyplina**: prompty są „kodem”, więc:
|
||
|
||
- powinny mieć wersjonowanie,
|
||
- powinny mieć testy,
|
||
- powinny mieć style guide.
|
||
|
||
***
|
||
|
||
### `src/rag/` – moduł Retrieval-Augmented Generation
|
||
|
||
- `embedder.py` – generowanie embeddingów dla chunków,
|
||
- `retriever.py` – wyszukiwanie dokumentów (top-k, MMR, filtry),
|
||
- `vector_store.py` – wrapper na bazę wektorową,
|
||
- `indexer.py` – indeksowanie dokumentów (ETL → chunk → embed → store).
|
||
|
||
**To jest warstwa**, która „przynosi wiedzę” do LLM.\
|
||
\
|
||
RAG powinien być **odseparowany**, bo ma osobny cykl życia: indeksy budujesz/odświeżasz niezależnie od inference.
|
||
|
||
***
|
||
|
||
### `src/processing/` – przygotowanie danych (pipeline)
|
||
|
||
- `chunking.py` – dzielenie tekstu na segmenty,
|
||
- `tokenizer.py` – pomocnicze funkcje tokenizacji,
|
||
- `preprocessor.py` – czyszczenie danych (usuwanie śmieci, normalizacja, deduplikacja).
|
||
|
||
**Dlaczego osobno?**\
|
||
\
|
||
Bo zmiany w chunkingu/tokenizacji potrafią totalnie zmienić jakość RAG i koszt embeddingów. To musi być testowalne i mierzalne.
|
||
|
||
***
|
||
|
||
### `src/inference/` – wykonanie i format odpowiedzi
|
||
|
||
- `inference_engine.py` – spina prompt + RAG + LLM, zarządza parametrami,
|
||
- `response_parser.py` – walidacja/formatowanie outputu (np. JSON schema, ekstrakcja pól).
|
||
|
||
**Klucz**: inference to osobny etap od budowy indeksu.
|
||
|
||
***
|
||
|
||
## `tests/` – testowanie (w tym regresja promptów)
|
||
|
||
- `unit/`
|
||
- `test_llm_clients.py` – testy klientów (mocki, retry, limity),
|
||
- `test_prompts.py` – testy promptów (np. czy zawierają wymagane sekcje).
|
||
- `integration/`
|
||
- `test_end_to_end.py` – e2e: pipeline od query do odpowiedzi,
|
||
- `test_api_integration.py` – testy integracji API.
|
||
|
||
W GenAI testy powinny obejmować:
|
||
|
||
- „czy to się uruchamia” (klasyczne),
|
||
- „czy to daje sensowny wynik” (regresja jakości, choćby heurystyczna),
|
||
- „czy output ma poprawny format” (parser/validator).
|
||
|
||
***
|
||
|
||
## `scripts/` – automatyzacja (operacyjnie krytyczna)
|
||
|
||
- `setup_env.sh` – setup środowiska,
|
||
- `run_tests.sh` – uruchomienie testów,
|
||
- `build_embeddings.py` – budowa embeddingów,
|
||
- `cleanup.py` – sprzątanie artefaktów.
|
||
|
||
Ten katalog to most między repo a operacjami: CI/CD, cron, joby w platformie.
|
||
|
||
***
|
||
|
||
## Pliki root: `.gitignore`, `Dockerfile`, `docker-compose.yml`, `requirements.txt`
|
||
|
||
To zapewnia:
|
||
|
||
- przenośność,
|
||
- powtarzalność środowiska,
|
||
- szybki start dla dev/test.
|
||
|
||
***
|
||
|
||
## Podsumowanie: jak myśleć o tym repo
|
||
|
||
Masz tu trzy kluczowe „pętle życia”:
|
||
|
||
1. **Data/Index lifecycle**: processing → embeddings → vectordb
|
||
2. **Inference lifecycle**: query → retriever → prompt → LLM → response_parser
|
||
3. **Ops lifecycle**: config → logging → scripts → tests → docker/compose
|
||
|
||
Takie podejście skraca czas debugowania, ogranicza ryzyko regresji i ułatwia wejście nowych osób do projektu.
|
||
|
||
***
|
||
|
||
***
|
||
|
||
# 2) Procedura operacyjna (SOP): Utrzymanie i rozwój projektu Generative AI (LLM + RAG)
|
||
|
||
Poniższe SOP jest „do użycia” — możesz wkleić do wewnętrznej dokumentacji zespołu.
|
||
|
||
## 2.1 Cel SOP
|
||
|
||
Zapewnić powtarzalny, bezpieczny i kontrolowany proces:
|
||
|
||
- budowy indeksów RAG,
|
||
- uruchamiania inference,
|
||
- wdrażania zmian w promptach/modelach,
|
||
- monitorowania i diagnostyki.
|
||
|
||
## 2.2 Zakres
|
||
|
||
Dotyczy repozytorium o strukturze:
|
||
|
||
- `config/`, `data/`, `src/`, `tests/`, `scripts/` oraz plików root.
|
||
|
||
## 2.3 Role i odpowiedzialności
|
||
|
||
- **Operator/On-call**: uruchamia procedury, reaguje na alerty, wykonuje rollback.
|
||
- **Maintainer**: zatwierdza zmiany w promptach/pipeline, dba o standardy testów.
|
||
- **Owner produktu**: zatwierdza zmiany jakościowe (np. zmiana modelu).
|
||
|
||
## 2.4 Definicje
|
||
|
||
- **Index build**: proces zasilenia `vectordb/` na podstawie dokumentów.
|
||
- **Embedding model**: model użyty do wektorów (wpływa na kompatybilność indeksu).
|
||
- **Chunking params**: parametry dzielenia tekstu (wpływ na recall/precision).
|
||
|
||
***
|
||
|
||
## 2.5 Procedura: Setup środowiska (pierwsze uruchomienie)
|
||
|
||
**Wejście**: czyste środowisko dev/stage/prod\
|
||
\
|
||
**Wyjście**: działające środowisko uruchomieniowe
|
||
|
||
1. Sklonuj repo i przejdź do katalogu projektu.
|
||
2. Uruchom:
|
||
|
||
```bash
|
||
scripts/setup_env.sh
|
||
```
|
||
|
||
1. Zweryfikuj, że konfiguracje istnieją:
|
||
- `config/model_config.yaml`
|
||
- `config/logging_config.yaml`
|
||
2. Uruchom testy smoke:
|
||
|
||
```bash
|
||
scripts/run_tests.sh
|
||
```
|
||
|
||
**Kryterium akceptacji**: testy przechodzą; logi zapisują się zgodnie z config.
|
||
|
||
***
|
||
|
||
## 2.6 Procedura: Budowa / odświeżenie indeksu RAG
|
||
|
||
**Wejście**: dane źródłowe (dokumenty), parametry chunkingu, model embeddingów\
|
||
\
|
||
**Wyjście**: nowy indeks w `data/vectordb/` oraz artefakty w `data/embeddings/`
|
||
|
||
### Krok po kroku
|
||
|
||
1. **Pre-flight** (kontrola przed startem):
|
||
- sprawdź wolne miejsce na dysku,
|
||
- potwierdź wybrany embedding model w `model_config.yaml`,
|
||
- sprawdź parametry chunkingu w `src/processing/chunking.py` (lub config jeśli tak ustalisz).
|
||
2. Uruchom budowę embeddingów:
|
||
|
||
```bash
|
||
python scripts/build_embeddings.py
|
||
```
|
||
|
||
1. Walidacja:
|
||
- czy `data/embeddings/` nie jest puste,
|
||
- czy `data/vectordb/` zawiera nowy stan indeksu,
|
||
- czy logi nie pokazują błędów rate-limit/timeout.
|
||
2. (Opcjonalnie) Test integracyjny RAG:
|
||
|
||
```bash
|
||
pytest -q tests/integration/test_end_to_end.py
|
||
```
|
||
|
||
**Kryterium akceptacji**: indeks zbudowany, test e2e przechodzi.
|
||
|
||
### Rollback
|
||
|
||
- zachowuj poprzedni katalog indeksu jako `vectordb_prev/` (lub snapshot wolumenu).
|
||
- rollback polega na podmianie wskazania na poprzedni indeks (symlink/konfiguracja).
|
||
|
||
***
|
||
|
||
## 2.7 Procedura: Uruchomienie inference (obsługa zapytań)
|
||
|
||
**Wejście**: zapytanie użytkownika, konfiguracja modelu, dostęp do vectordb\
|
||
\
|
||
**Wyjście**: odpowiedź sformatowana przez `response_parser.py`
|
||
|
||
1. Sprawdź, czy indeks istnieje i jest dostępny.
|
||
2. Sprawdź, jaki model jest aktywny (config).
|
||
3. Uruchom komponent aplikacji (zależnie od tego, jak startujesz projekt: CLI/API).
|
||
4. W logach weryfikuj:
|
||
- czas retrieval,
|
||
- liczba pobranych dokumentów (top-k),
|
||
- czas odpowiedzi LLM,
|
||
- błędy parsera odpowiedzi.
|
||
|
||
**Kryterium akceptacji**: odpowiedź spełnia format, brak błędów w logach.
|
||
|
||
***
|
||
|
||
## 2.8 Procedura: Zmiana promptów (kontrolowana)
|
||
|
||
**Ryzyko**: prompt changes = natychmiastowa zmiana zachowania systemu.
|
||
|
||
### Zasady
|
||
|
||
1. Każda zmiana w `src/prompts/` wymaga:
|
||
- PR,
|
||
- testu `tests/unit/test_prompts.py`,
|
||
- krótkiego opisu „dlaczego zmiana” i „jak mierzymy efekt”.
|
||
2. Minimalna walidacja:
|
||
- „zachowanie” na 5–10 przykładowych zapytaniach (golden set),
|
||
- poprawność formatu outputu (parser/validator).
|
||
|
||
### Kroki
|
||
|
||
1. Zmiana w `templates.py` / `chain.py`.
|
||
2. Testy:
|
||
|
||
```bash
|
||
scripts/run_tests.sh
|
||
```
|
||
|
||
1. E2E:
|
||
|
||
```bash
|
||
pytest -q tests/integration/test_end_to_end.py
|
||
```
|
||
|
||
1. Deploy (zgodnie z pipeline).
|
||
2. Monitoring po wdrożeniu: error-rate i czas odpowiedzi.
|
||
|
||
### Rollback
|
||
|
||
- revert commit / przywrócenie poprzednich promptów,
|
||
- jeśli zmiana promptów wymagała zmiany parsera — rollback obu.
|
||
|
||
***
|
||
|
||
## 2.9 Procedura: Zmiana modelu (GPT ↔ Claude ↔ Local)
|
||
|
||
**Ryzyko**: inne modele mają inne limity, styl, deterministykę.
|
||
|
||
1. Zmiana tylko w `config/model_config.yaml` (preferowane).
|
||
2. Wykonaj test klientów:
|
||
|
||
```bash
|
||
pytest -q tests/unit/test_llm_clients.py
|
||
```
|
||
|
||
1. Sprawdź rate limit / retry policy w logach.
|
||
2. Walidacja jakości na golden set.
|
||
3. Deploy etapami (canary jeśli możesz).
|
||
|
||
Rollback: powrót do poprzedniego configu.
|
||
|
||
***
|
||
|
||
## 2.10 Monitoring i diagnostyka (minimum operacyjne)
|
||
|
||
W logach muszą się pojawić (jako pola lub jednoznaczne wpisy):
|
||
|
||
- `request_id` / korelacja,
|
||
- czas: `retrieval_ms`, `llm_ms`, `total_ms`,
|
||
- `top_k`, `source_count`,
|
||
- `model_id`, `embedding_model_id`,
|
||
- błędy parsera i błędy narzędzi.
|
||
|
||
**W razie incydentu**:
|
||
|
||
1. Sprawdź logi `inference_engine.py` (czy problem po stronie RAG czy LLM).
|
||
2. Jeśli RAG:
|
||
- czy retriever zwraca wyniki,
|
||
- czy vectordb nie jest uszkodzone,
|
||
- czy filtry nie wykluczają wszystkiego.
|
||
3. Jeśli LLM:
|
||
- rate limit / auth / timeout,
|
||
- zmiana modelu lub parametrów.
|
||
|
||
***
|
||
|
||
## 2.11 Sprzątanie (retencja)
|
||
|
||
1. Uruchom:
|
||
|
||
```bash
|
||
python scripts/cleanup.py
|
||
```
|
||
|
||
1. Zasady:
|
||
- cache starszy niż X dni usuwać,
|
||
- embeddings trzymać wersjami (jeśli wspierasz multi-index),
|
||
- vectordb snapshotować przed nadpisaniem.
|
||
|
||
***
|
||
|
||
## 2.12 Checklisty (do wykorzystania „na szybko”)
|
||
|
||
### Checklist: Index build
|
||
|
||
- [ ] Dysk OK / miejsce OK
|
||
- [ ] Embedding model potwierdzony
|
||
- [ ] Chunking parametry potwierdzone
|
||
- [ ] Build embeddings wykonany
|
||
- [ ] Vectordb zaktualizowane
|
||
- [ ] Test e2e OK
|
||
- [ ] Snapshot/rollback przygotowany
|
||
|
||
### Checklist: Prompt deploy
|
||
|
||
- [ ] PR + opis zmiany
|
||
- [ ] Unit test promptów OK
|
||
- [ ] Parser kompatybilny
|
||
- [ ] Golden set OK
|
||
- [ ] Monitoring po deploy OK
|
||
- [ ] Plan rollback gotowy
|
||
|
||
***
|
||
|
||
## Co dalej?
|
||
|
||
Jeśli chcesz, dopracuję to pod Twoje standardy operacyjne:
|
||
|
||
1. **Wersja w org-mode** (wiem, że to Twój „single source of truth”).
|
||
2. Dodanie sekcji „Bezpieczeństwo i sekrety”: gdzie trzymać API keys (vault/ENV), co w `.gitignore`.
|
||
3. Dodanie „Quality Gates”: golden set, metryki RAG (recall@k), regresja promptów.
|
||
4. Gotowy „Runbook incidentowy”: *RAG returns empty*, *LLM timeouts*, *parser errors*, *vectordb corruption*.
|
||
|
||
Powiedz tylko: **czy to ma być SOP pod dev/test**, czy **pod produkcję (on-call, incident response, change management)** i w jakim środowisku odpalasz (Docker/VM/K8s)?
|