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