Production RAG na dużą skalę: Lekcje z przetwarzania ponad 10 000 ogłoszeń dziennie
Zbudowałem potok RAG dla portalu z ofertami pracy. W środowisku stagingowym działał bez zarzutu, ale sprawiał problemy pod realnym obciążeniem. Przetwarzanie tysięcy ogłoszeń dziennie wymaga czegoś więcej niż tylko dobrej bazy wektorowej. Musisz wiedzieć, w których miejscach system zawodzi.
Oto moje wnioski dotyczące chunkingu, embeddingów, kosztów i obserwowalności.
- Nie zgaduj strategii chunkingu
Większość tutoriali traktuje chunking jako proste ustawienie. W produkcji to właśnie strategia decyduje o dokładności i kosztach.
Przetestowałem trzy metody dla ogłoszeń o pracę:
- Fixed-size chunks (chunki o stałym rozmiarze): Ta metoda zawiodła. Dzieliła sekcje takie jak „wymagania” i „benefity” w losowych miejscach. Powodowało to szum podczas wyszukiwania (retrieval).
- Semantic chunking (chunking semantyczny): Był lepszy, ale niespójny. Niektóre chunki były zbyt długie, inne zbyt krótkie.
- Recursive character splitting with overlap (rekurencyjne dzielenie znaków z nakładaniem się): To zadziałało najlepiej. Dzieliłem tekst w oparciu o znaki nowej linii i zdania. Użyłem rozmiaru 400 tokenów z 50-tokenowym nakładaniem się (overlap). Dzięki temu zdania rozciągające się na dwa chunki pozostają ze sobą połączone.
Pro tip: Znormalizuj dane przed chunkingiem. Różne źródła, takie jak Greenhouse czy Lever, zwracają różne formaty. Najpierw wyczyść tekst, aby Twój chunker widział spójną strukturę.
- Embeddings: Koszt vs. Dokładność
Porównałem Llama 3.1 przez Ollama z OpenAI text-embedding-3-small. Lokalny model był darmowy, ale miał trudności z terminami specjalistycznymi, takimi jak „equity compensation”. Generował zaszumione wyniki. OpenAI kosztowało więcej, ale zapewniało dokładne dopasowania. Wybrałem OpenAI, ponieważ słabe wyszukiwanie (retrieval) generuje wyższe koszty przy późniejszych wywołaniach LLM.
Aby zaoszczędzić czas, grupuję zapytania (batching). Wysyłam do 100 chunków w jednym wywołaniu. Zmniejsza to opóźnienia (latency) i sprawia, że potok działa szybko.
- Kompromis w wyborze bazy wektorowej (Vector Store)
Do prototypowania użyłem Pinecone, ponieważ konfiguracja jest bardzo szybka. Jednak przy dużej skali koszty stały się zbyt wysokie.
Przeszedłem na pgvector wewnątrz PostgreSQL.
- Konfiguracja wymagała więcej pracy.
- Pozwoliło to zaoszczędzić ogromne kwoty pieniędzy.
- Zapewniło spójność transakcyjną. Ponieważ embeddingi znajdują się w tej samej bazie danych co dane o ofertach pracy, masz jedno źródło prawdy (single source of truth). Nie musisz synchronizować dwóch różnych systemów.
- Kontrola kosztów LLM
Ocenianie każdego ogłoszenia za pomocą GPT-4o jest kosztowne. Zastosowałem trzy taktyki, aby obniżyć koszty:
- OpenAI Batch API: Zadania związane z ocenianiem przetwarzam w nocy. Pozwala to na uzyskanie dużego rabatu.
- Caching (cache'owanie): Przechowuję wyniki w pamięci podręcznej dla powtarzających się profili kandydatów.
- Model tiering (warstwowanie modeli): Używam GPT-4o-mini dla powszechnych stanowisk, takich jak „Sales Representative”. GPT-4o używam tylko w przypadku niszowych ról, gdzie precyzja jest kluczowa.
- Najpierw zbuduj obserwowalność (Observability)
Mój potok kiedyś zawiódł po cichu. Nieprawidłowe dane powodowały powstawanie pustych chunków, które system pomijał bez zgłaszania błędu.
Naprawiłem to, dodając ustrukturyzowane logowanie z identyfikatorem korelacji (correlation ID). Pozwoliło mi to prześledzić pojedyncze ogłoszenie od momentu wprowadzenia danych (ingestion) aż po ocenę. W końcu mogłem zobaczyć, które źródła danych powodują błędy.
Najważniejsza lekcja: Większość problemów wynika z nieuporządkowanych danych, a nie z AI. Najpierw napraw „hydraulikę” swoich danych.
Optional learning community: https://t.me/GyaanSetuAi
