Podstawowe wdrożenie RAG: samouczek krok po kroku

Ten przyjazny dla początkujących samouczek wyjaśnia, jak działa podstawowe wdrożenie RAG, omawia najlepsze praktyki i prowadzi krok po kroku przez budowę działającego systemu RAG z interfejsem wiersza poleceń przy użyciu Pythona, ChromaDB oraz modeli embeddingów i modeli językowych Perplexity.

Basic RAG implementation process diagram: the interface sends input to the orchestrator, which retrieves data from a vector database, builds the input context for the LLM, and returns the output to the interface.

RAG to kluczowy element nowoczesnej infrastruktury opartej na LLM. Umożliwiając modelom AI dostęp do danych zewnętrznych, pomaga rozwiązywać problemy takie jak halucynacje czy niekompletne dane treningowe. Dzięki temu firmy mogą też korzystać z wewnętrznych baz wiedzy zawierających informacje poufne lub zastrzeżone.

Wdrożenie RAG może być bardzo złożone. Podstawowy system RAG jest jednak zaskakująco łatwy do zbudowania. Dla początkujących stworzenie prostej architektury to dobry sposób, by zrozumieć, jak RAG działa w praktyce, a także punkt wyjścia do dalszych eksperymentów.

W tym artykule przedstawimy podstawowe wdrożenie RAG, omówimy najlepsze praktyki, a następnie zbudujemy działający system przy użyciu Pythona, ChromaDB i dwóch modeli Perplexity – do tworzenia embeddingów i generowania odpowiedzi.

Proces podstawowego wdrożenia RAG: ogólny przegląd

Podstawowy system RAG składa się z trzech głównych komponentów: orkiestratora, źródła informacji i LLM. W typowym przepływie pracy użytkownik wpisuje prompt, a orkiestrator modyfikuje go, aby przeszukać wszystkie podłączone źródła informacji. Pobrane informacje są następnie dodawane do pierwotnego kontekstu wejściowego i wysyłane do LLM, który generuje odpowiedź.

Budując własne wdrożenie RAG, musisz uwzględnić następujące etapy:

  1. Indeksowanie i tworzenie embeddingów: Na tym etapie dokumenty są przekształcane w źródło informacji, które orkiestrator może przeszukiwać. Jednym z najczęstszych sposobów jest zbudowanie przeszukiwalnego indeksu wektorowego. Użyjemy dedykowanego modelu Perplexity, aby przekształcić fragmenty dokumentów w embeddingi (osadzenia wektorowe). Następnie zapiszemy je w ChromaDB, popularnej wektorowej bazie danych open source.
  2. Orkiestracja: To aplikacja, która pośredniczy między zewnętrznym źródłem informacji (w naszym przypadku wektorową bazą danych) a LLM. Jako orkiestrator posłuży nam prosty skrypt w Pythonie.
  3. Wyszukiwanie i generowanie: Gdy użytkownik zada pytanie w interfejsie czatu, orkiestrator je przejmie, przeprowadzi wyszukiwanie wektorowe i przekaże rozszerzone dane wejściowe do LLM. Zamiast budować dedykowany frontend, będziemy komunikować się z orkiestratorem w Pythonie za pomocą wiersza poleceń.

Najlepsze praktyki: co decyduje o skutecznym wdrożeniu RAG?

Istnieje wiele typów architektury RAG, a poszczególne przypadki użycia wymagają różnych podejść. Mimo to szereg najlepszych praktyk sprawdza się w każdym przypadku i pozwala tworzyć systemy dokładne, opłacalne, skalowalne i łatwe w utrzymaniu.

Poniższe najlepsze praktyki dotyczą wszystkich typów wdrożeń RAG:

  • Zadbaj o czysty potok pozyskiwania danych: Przed indeksowaniem dane źródłowe powinny być jak najczystsze. Warto też używać modelu embeddingów dopasowanego do Twoich treści.
  • Korzystaj z wyszukiwania hybrydowego: Najlepsze metody wyszukiwania – czy to przeszukiwanie wektorowej bazy danych, wyszukiwanie w sieci przez API, przechodzenie po grafie wiedzy, wyszukiwanie według słów kluczowych, czy inne – zależą od przypadku użycia. Dobór właściwego połączenia metod jest ważny dla minimalizacji kosztów.
  • Oceniaj swój system: Po wdrożeniu warto ocenić system pod kątem trzech podstawowych wskaźników: dokładności, trafności i opóźnienia. Dokładność oznacza poprawność merytoryczną, trafność – adekwatność pobranych informacji do zapytań użytkowników, a opóźnienie – szybkość, z jaką informacje są pobierane.
  • Dbaj o bezpieczeństwo informacji: Jeśli Twój system RAG korzysta z prywatnych informacji wewnętrznych, upewnij się, że są one bezpiecznie przechowywane, a dostęp do interfejsu systemu jest kontrolowany. Kluczy API nigdy nie należy udostępniać.

Podstawowe wdrożenie RAG: samouczek w 7 krokach

W tym samouczku zbudujemy aplikację wiersza poleceń, która odpowiada na pytania dotyczące jednego dokumentu. Użyjemy Pythona, ChromaDB i API Perplexity.

Celem nie jest zbudowanie systemu RAG gotowego do użytku produkcyjnego. To raczej przyjazny dla początkujących samouczek, który ma pokazać, jak podstawowa architektura RAG działa w praktyce.

Krok 1: Zainstaluj oprogramowanie

Upewnij się, że na Twoim komputerze są zainstalowane poniższe narzędzia i że masz konto API Perplexity:

  • Python: To język programowania, w którym zbudujemy orkiestrator koordynujący przepływ danych między modelem embeddingów, ChromaDB a Perplexity.
  • Visual Studio: Jako edytora kodu użyjemy Visual Studio. Po instalacji otwórz panel Extensions i pobierz rozszerzenie Python.
  • Konto API Perplexity: Perplexity zapewni zarówno model embeddingów (który przekształca dane w embeddingi wektorowe), jak i LLM, który przetworzy rozszerzony prompt. Nie potrzebujesz do tego płatnego konta, choć wiąże się to z niewielkim zużyciem kredytów.

Krok 2: Utwórz folder projektu

Zacznij od utworzenia folderu o nazwie rag-project i otwarcia go w VS Code, wybierając File > Open Folder. Następnie otwórz zintegrowany terminal, wybierając Terminal > New Terminal.

Wpisz poniższe polecenie, aby utworzyć wirtualne środowisko Pythona:

bash
py -3.12 -m venv .venv

Teraz musisz zainstalować cztery biblioteki Pythona: perplexityai, chromadb, python-dotenv i numpy. SDK Perplexity umożliwia łączenie się z modelami Perplexity przez API. ChromaDB tworzy wektorową bazę danych i będzie działać lokalnie. Python-dotenv bezpiecznie wczytuje klucze API z lokalnego pliku, a NumPy jest potrzebny w procesie pozyskiwania danych.

Zainstaluj je za pomocą następującego polecenia:

bash
.\.venv\Scripts\python.exe -m pip install perplexityai chromadb python-dotenv numpy

Krok 3: Przygotuj pliki

Dodaj poniższe pliki do folderu rag-project (katalogu głównego projektu), wybierając New File… na pasku bocznym Eksploratora:

  • index.py: To aplikacja, która zaindeksuje Twój dokument. Możesz uruchomić ją ponownie, aby dodać więcej tekstu.
  • rag.py: To aplikacja w Pythonie, która będzie pełnić funkcję orkiestratora.
  • document.txt: Ten plik będzie zawierał dokumentację, którą przekształcisz w wektorową bazę danych.
  • .env: Tutaj będziesz przechowywać klucz API Perplexity.

W pliku .env wpisz następującą treść:

plaintext
PERPLEXITY_API_KEY=your_api_key_here

Zastąp your_api_key_here swoim rzeczywistym kluczem. Aby go uzyskać, przejdź do pulpitu głównego Perplexity API Console i wybierz Create an API key. Zachowaj ten klucz API w tajemnicy.

Użyjemy dwóch modeli: pplx-embed-v1-0.6b od Perplexity do tworzenia embeddingów dokumentacji przed przesłaniem jej do ChromaDB oraz GPT-5.6-Luna (dostępnego przez API Perplexity), który przetworzy pobrane informacje.

W pliku document.txt dodaj tekst, który będzie pobierał Twój potok RAG. Jeśli nie masz pod ręką żadnego dokumentu, na potrzeby tego samouczka użyj zbioru Songs of Innocence and of Experience Williama Blake’a z Projektu Gutenberg. To krótki, przystępny tekst należący do domeny publicznej.

Krok 4: Napisz program do indeksowania

Skopiuj poniższy kod i wklej go do pliku index.py. Każdy śródtytuł zawiera krótkie wyjaśnienie działania następującego po nim fragmentu kodu. Ten program przekształci materiał źródłowy w embeddingi wektorowe i zapisze je w bazie danych ChromaDB, korzystając z modelu embeddingów Perplexity pplx-embed-v1-0.6b oraz biblioteki NumPy.

Oto, co robi program:

  1. Otwiera plik document.txt.
  2. Dzieli tekst na nakładające się fragmenty.
  3. Wysyła te fragmenty do modelu embeddingów Perplexity pplx-embed-v1-0.6b.
  4. Dekoduje i normalizuje zwrócone wektory za pomocą NumPy, biblioteki Pythona z funkcjami obliczeniowymi.
  5. Zapisuje wektory i oryginalne fragmenty w bazie danych ChromaDB (zobaczysz ją w katalogu projektu).

Program skonfigurowano tak, aby nie indeksował dokumentu od nowa przy każdym uruchomieniu. Jeśli dodasz informacje do pliku document.txt, utworzy embeddingi tylko dla nowego tekstu.

python
from pathlib import Path from collections import defaultdict, deque from hashlib import sha256 import base64 import numpy as np import chromadb from dotenv import load_dotenv from perplexity import Perplexity # 1. Connect to Perplexity and ChromaDB load_dotenv() ai = Perplexity() db = chromadb.PersistentClient(path="./rag_database_perplexity") collection = db.get_or_create_collection( name="documents", metadata={"hnsw:space": "cosine"} ) # Convert Perplexity's encoded embeddings into numerical vectors def decode_embedding(encoded): vector = np.frombuffer(base64.b64decode(encoded), dtype=np.int8).astype(np.float32) norm = np.linalg.norm(vector) if norm == 0: raise ValueError("Received a zero-length embedding vector.") return (vector / norm).tolist() # 2. Read the document and split it into chunks text = Path("document.txt").read_text(encoding="utf-8") words = text.split() chunk_size = 180 overlap = 30 step = chunk_size - overlap chunks = [" ".join(words[i:i + chunk_size]) for i in range(0, len(words), step)] if not chunks: raise ValueError("document.txt is empty.") # 3. Retrieve the chunks already stored in ChromaDB existing = collection.get(include=["documents"]) stored_chunks = defaultdict(deque) existing_ids = set(existing["ids"]) for chunk_id, document in zip(existing["ids"], existing["documents"]): stored_chunks[document].append(chunk_id) # 4. Identify unchanged and new chunks new_chunks = [] new_ids = [] kept_ids = set() unchanged = 0 for chunk in chunks: if stored_chunks[chunk]: chunk_id = stored_chunks[chunk].popleft() kept_ids.add(chunk_id) unchanged += 1 else: chunk_hash = sha256(chunk.encode("utf-8")).hexdigest() chunk_id = f"chunk-{chunk_hash}" suffix = 1 while chunk_id in existing_ids or chunk_id in kept_ids or chunk_id in new_ids: chunk_id = f"chunk-{chunk_hash}-{suffix}" suffix += 1 new_chunks.append(chunk) new_ids.append(chunk_id) kept_ids.add(chunk_id) # 5. Generate embeddings in batches of up to 100 chunks batch_size = 100 for i in range(0, len(new_chunks), batch_size): batch_chunks = new_chunks[i:i + batch_size] batch_ids = new_ids[i:i + batch_size] embeddings = ai.embeddings.create( model="pplx-embed-v1-0.6b", input=batch_chunks, encoding_format="base64_int8" ) vectors = [decode_embedding(item.embedding) for item in embeddings.data] # 6. Store each batch in ChromaDB collection.upsert( ids=batch_ids, documents=batch_chunks, embeddings=vectors ) print(f"Indexed batch {(i // batch_size) + 1}") # 7. Remove outdated chunks old_ids = [chunk_id for chunk_id in existing["ids"] if chunk_id not in kept_ids] if old_ids: collection.delete(ids=old_ids) # 8. Display indexing results print("Indexing complete!") print("Unchanged chunks:", unchanged) print("New or modified chunks:", len(new_chunks)) print("Removed chunks:", len(old_ids)) print("Total stored chunks:", collection.count())

Krok 5. Napisz orkiestrator systemu RAG

Skopiuj poniższy kod i wklej go do pliku rag.py. To kompletne, choć minimalne wdrożenie RAG, które korzysta z SDK Perplexity i funkcji lokalnej wektorowej bazy danych ChromaDB.

Oto, co się stanie po uruchomieniu programu:

  1. Łączy się z Perplexity i nową bazą danych ChromaDB.
  2. Sprawdza, czy baza danych zawiera zaindeksowane dokumenty.
  3. Przyjmuje pytanie od użytkownika.
  4. Wysyła pytanie do modelu embeddingów Perplexity.
  5. Dekoduje i normalizuje uzyskany embedding za pomocą NumPy.
  6. Wyszukuje w ChromaDB trzy najbardziej podobne fragmenty dokumentu.
  7. Tworzy prompt zawierający pobrane fragmenty i pytanie użytkownika.
  8. Wysyła prompt do GPT-5.6 Luna przez Agent API Perplexity.
  9. Wyświetla odpowiedź i czeka na kolejne pytanie.
python
import base64 import numpy as np import chromadb from dotenv import load_dotenv from perplexity import Perplexity # 1. Connect to Perplexity and the existing ChromaDB database load_dotenv() ai = Perplexity() db = chromadb.PersistentClient(path="./rag_database_perplexity") collection = db.get_collection("documents") # Convert Perplexity's encoded embeddings into numerical vectors def decode_embedding(encoded): vector = np.frombuffer(base64.b64decode(encoded), dtype=np.int8).astype(np.float32) norm = np.linalg.norm(vector) if norm == 0: raise ValueError("Received a zero-length embedding vector.") return (vector / norm).tolist() # 2. Check that the database contains indexed documents count = collection.count() if count == 0: raise ValueError("The database is empty. Run index.py first.") print("Connected to existing ChromaDB database.") print("Stored chunks:", count) # 3. Accept questions from the user while True: question = input("\nAsk a question (or type exit): ") if question.lower().strip() == "exit": break if not question.strip(): continue # 4. Convert the question into a Perplexity embedding question_response = ai.embeddings.create( model="pplx-embed-v1-0.6b", input=[question], encoding_format="base64_int8" ) question_vector = decode_embedding(question_response.data[0].embedding) # 5. Retrieve the most similar document chunks from ChromaDB results = collection.query( query_embeddings=[question_vector], n_results=min(3, count) ) context = "\n\n".join(results["documents"][0]) # 6. Construct a prompt and send it to the fixed LLM response = ai.responses.create( model="openai/gpt-5.6-luna", instructions=( "Answer using only the supplied document excerpts. " "Do not use external information. " "If the answer is not present, say you don't know." ), input=f"Document excerpts:\n{context}\n\nQuestion: {question}" ) # 7. Display the answer print("\nAnswer:\n", response.output_text)

Krok 6: Uruchom aplikacje

W terminalu PowerShell w VS Code wpisz poniższe polecenie, aby utworzyć embeddingi treści z pliku document.txt:

bash
.\.venv\Scripts\python.exe index.py

Jeśli dokument został pomyślnie zaindeksowany, zobaczysz dane wyjściowe podobne do poniższych:

plaintext
Indexing complete! Unchanged chunks: 0 New or modified chunks: 58 Removed chunks: 0 Total stored chunks: 58

Teraz czas uruchomić główną aplikację orkiestratora. Wpisz to polecenie:

bash
.\.venv\Scripts\python.exe rag.py

Jeśli wszystko przebiegnie pomyślnie, zobaczysz następujący komunikat:

plaintext
Connected to existing ChromaDB database. Stored chunks: 58 Ask a question (or type exit):

Możesz teraz zadawać pytania, na które odpowiedzi będą czerpane z utworzonej przez Ciebie wektorowej bazy danych, na przykład:

plaintext
Ask a question (or type exit): What animals does William Blake talk about? Answer: The excerpts mention sheep, lambs, birds, beasts, wolves, tigers, and a lion.

Krok 7: Zrozum, co udało Ci się zbudować

Zbudowany przez Ciebie system zawiera dwa podstawowe potoki obecne w każdej architekturze RAG: potok indeksowania (inaczej „pozyskiwania danych”) oraz potok wyszukiwania i generowania.

Twoje podstawowe wdrożenie RAG realizuje teraz wszystkie poniższe funkcje:

  • Przechowywanie wektorów: Embeddingi utworzone na podstawie pliku wiedzy są zapisywane w bazie danych ChromaDB w Twoim środowisku lokalnym.
  • Indeksowanie przyrostowe: Dla niezmienionych fragmentów dokumentu embeddingi nie są tworzone ponownie, a sam dokument możesz aktualizować w dowolnym momencie.
  • Wyszukiwanie semantyczne: ChromaDB pobiera fragmenty na podstawie podobieństwa wektorów (na żądanie orkiestratora).
  • Generowanie odpowiedzi opartych na źródłach: LLM otrzymuje pobrane fragmenty jako kontekst i uwzględnia je w odpowiedzi.
  • Niezależne indeksowanie i odpytywanie: Możesz zadawać pytania bez ponownego przetwarzania całego tekstu z pliku wiedzy.
  • Połączenie z modelem przez API: Wszystkie odpowiedzi są generowane przez GPT-5.6 Luna za pośrednictwem Perplexity, a nie przez model działający na Twoim komputerze.

Kolejne kroki: jak rozwinąć to wdrożenie

Celem tego samouczka jest zbudowanie prostego, podstawowego wdrożenia. Obejmuje ono niektóre z kluczowych komponentów architektury RAG obecnych w nowoczesnych systemach. Jeśli chcesz pójść dalej, możesz je rozbudować na różne sposoby.

Oto kilka pomysłów na rozszerzenie zakresu wdrożenia:

  • Eksperymentuj z LangChain: LangChain to jeden z najpopularniejszych frameworków orkiestracji typu open source. Zawiera gotowe komponenty do tworzenia aplikacji zdolnych do obsługi złożonych potoków RAG.
  • Rozszerz funkcje wyszukiwania: Połącz gęste wyszukiwanie wektorowe z wyszukiwaniem leksykalnym (według słów kluczowych), np. BM25. Dodanie wyszukiwania w sieci za pomocą Perplexity Search API pozwoliłoby też opierać odpowiedzi na aktualnych danych.
  • Dodaj model rerankingu: Zintegruj model rerankingu, który oceni pobrane fragmenty pod kątem pierwotnego promptu i wybierze najtrafniejsze z nich do kontekstu wejściowego przekazywanego do LLM.
  • Zbuduj adaptacyjny system RAG: Adaptacyjny RAG wykorzystuje LLM lub klasyfikator (komponent orkiestratora) do określania, której metody wyszukiwania użyć w modułowym systemie RAG. Zbudowanie takiego systemu to dobry sposób na poznanie bardziej zaawansowanych wdrożeń RAG.

Choć może to brzmieć skomplikowanie, pamiętaj, że popularne platformy AI, takie jak Perplexity, oferują zaawansowaną pomoc w programowaniu. Doskonale sprawdzają się w projektach osobistych i eksperymentach, a koszty można utrzymać na stosunkowo niskim poziomie.

Uwaga: w tym samouczku używamy GPT-5.6 Luna. Dostępność modeli może się zmieniać, więc może być konieczna aktualizacja wywołania API.