13 KiB
type
| 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:
- braku wersjonowania i powtarzalności danych / indeksów,
- braku kontroli nad promptami (zmiany „na żywo” bez śladu),
- braku testów regresji (model/prompt/dane zmieniają zachowanie),
- 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”:
- Data/Index lifecycle: processing → embeddings → vectordb
- Inference lifecycle: query → retriever → prompt → LLM → response_parser
- 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
- Sklonuj repo i przejdź do katalogu projektu.
- Uruchom:
scripts/setup_env.sh
- Zweryfikuj, że konfiguracje istnieją:
config/model_config.yamlconfig/logging_config.yaml
- Uruchom testy smoke:
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
- 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).
- Uruchom budowę embeddingów:
python scripts/build_embeddings.py
- Walidacja:
- czy
data/embeddings/nie jest puste, - czy
data/vectordb/zawiera nowy stan indeksu, - czy logi nie pokazują błędów rate-limit/timeout.
- czy
- (Opcjonalnie) Test integracyjny RAG:
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
- Sprawdź, czy indeks istnieje i jest dostępny.
- Sprawdź, jaki model jest aktywny (config).
- Uruchom komponent aplikacji (zależnie od tego, jak startujesz projekt: CLI/API).
- 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
- Każda zmiana w
src/prompts/wymaga:- PR,
- testu
tests/unit/test_prompts.py, - krótkiego opisu „dlaczego zmiana” i „jak mierzymy efekt”.
- Minimalna walidacja:
- „zachowanie” na 5–10 przykładowych zapytaniach (golden set),
- poprawność formatu outputu (parser/validator).
Kroki
- Zmiana w
templates.py/chain.py. - Testy:
scripts/run_tests.sh
- E2E:
pytest -q tests/integration/test_end_to_end.py
- Deploy (zgodnie z pipeline).
- 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ę.
- Zmiana tylko w
config/model_config.yaml(preferowane). - Wykonaj test klientów:
pytest -q tests/unit/test_llm_clients.py
- Sprawdź rate limit / retry policy w logach.
- Walidacja jakości na golden set.
- 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:
- Sprawdź logi
inference_engine.py(czy problem po stronie RAG czy LLM). - Jeśli RAG:
- czy retriever zwraca wyniki,
- czy vectordb nie jest uszkodzone,
- czy filtry nie wykluczają wszystkiego.
- Jeśli LLM:
- rate limit / auth / timeout,
- zmiana modelu lub parametrów.
2.11 Sprzątanie (retencja)
- Uruchom:
python scripts/cleanup.py
- 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:
- Wersja w org-mode (wiem, że to Twój „single source of truth”).
- Dodanie sekcji „Bezpieczeństwo i sekrety”: gdzie trzymać API keys (vault/ENV), co w
.gitignore. - Dodanie „Quality Gates”: golden set, metryki RAG (recall@k), regresja promptów.
- 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)?