מימוש RAG בסיסי: מדריך שלב אחר שלב
מדריך ידידותי למתחילים זה מסביר כיצד פועל מימוש RAG בסיסי, סוקר שיטות עבודה מומלצות ומלווה אתכם בבניית מערכת RAG עובדת בשורת הפקודה באמצעות Python, ChromaDB ומודלי ההטמעה והשפה של Perplexity.

RAG הוא רכיב חיוני בתשתית מודרנית מבוססת LLM. הוא מאפשר למודלי AI לגשת לנתונים חיצוניים, ובכך מסייע להתמודד עם בעיות כמו הזיות ונתוני אימון חלקיים. בנוסף, בזכותו חברות יכולות להשתמש במאגרי ידע פנימיים המכילים מידע רגיש או קנייני.
מימוש RAG עשוי להיות מורכב מאוד. עם זאת, בניית מערכת RAG בסיסית קלה באופן מפתיע. עבור מתחילים, יצירת ארכיטקטורה פשוטה יכולה להמחיש כיצד RAG פועל בעולם האמיתי ולספק קרש קפיצה להתנסות נוספת.
במאמר זה נשרטט מימוש RAG בסיסי, נסקור שיטות עבודה מומלצות, ולאחר מכן נבנה מערכת עובדת באמצעות Python, ChromaDB ושני מודלים של Perplexity להטמעה וליצירת תשובות.
תהליך מימוש RAG בסיסי: סקירה כללית
למערכת RAG בסיסית יש שלושה רכיבים עיקריים: מתזמר (orchestrator), מקור מידע ו-LLM. בתהליך עבודה טיפוסי, המשתמש מזין הנחיה, והמתזמר מתאים אותה כדי לחפש בכל מקורות המידע המחוברים. לאחר מכן, המידע שאוחזר מתווסף להקשר הקלט המקורי ונשלח ל-LLM כדי שניתן יהיה ליצור תשובה.
בעת בניית מימוש ה-RAG שלכם, תצטרכו להביא בחשבון את השלבים הבאים:
- יצירת אינדקס והטמעה: בשלב זה, המסמכים שלכם מומרים למקור מידע שהמתזמר יכול לחפש בו. אחת הדרכים הנפוצות ביותר לעשות זאת היא לבנות אינדקס וקטורי שניתן לחפש בו. נשתמש במודל ייעודי של Perplexity כדי להמיר את מקטעי המסמכים שלכם להטמעות. לאחר מכן נאחסן את ההטמעות ב-ChromaDB, מסד נתונים וקטורי פופולרי בקוד פתוח.
- תזמור: זהו היישום שנמצא בין מקור המידע החיצוני (במקרה שלנו, מסד נתונים וקטורי) לבין ה-LLM. ניצור סקריפט Python פשוט שישמש כמתזמר.
- אחזור ויצירה: כאשר משתמש שואל שאלה דרך ממשק הצ׳אט, המתזמר יקבל אותה, יריץ חיפוש וקטורי ויזין את הקלט המורחב ל-LLM. במקום לבנות ממשק משתמש ייעודי, נשתמש בשורת הפקודה כדי לתקשר עם המתזמר שלנו ב-Python.
שיטות עבודה מומלצות: מה הופך מימוש RAG לאפקטיבי?
קיימים סוגים רבים של ארכיטקטורות RAG, ותרחישי שימוש שונים מחייבים גישות שונות. עם זאת, מספר שיטות עבודה מומלצות חלות בכל המקרים ומבטיחות מערכות מדויקות, חסכוניות, ניתנות להרחבה וקלות לתחזוקה.
שיטות העבודה המומלצות הבאות חלות על כל סוגי מימושי ה-RAG:
- הקפידו על צינור קליטה נקי: נתוני המקור שלכם צריכים להיות נקיים ככל האפשר לפני יצירת האינדקס. כמו כן, כדאי להשתמש במודל הטמעה שמתאים לתוכן שלכם.
- השתמשו בחיפוש היברידי: שיטות האחזור הטובות ביותר, בין אם חיפוש במסד נתונים וקטורי, חיפוש באינטרנט מבוסס API, סריקה של גרף ידע, חיפוש לפי מילות מפתח או שיטות אחרות, משתנות בהתאם לתרחיש השימוש. בחירת השילוב הנכון חשובה לצמצום העלויות.
- העריכו את המערכת שלכם: לאחר המימוש, כדאי להעריך את המערכת לפי שלושה מדדי ליבה: דיוק, רלוונטיות וזמן השהיה. דיוק מתייחס לנכונות העובדתית, רלוונטיות מתייחסת למידת ההתאמה של המידע שאוחזר לשאילתות המשתמשים, וזמן השהיה מתייחס למהירות שבה המידע מאוחזר.
- טפלו במידע באופן מאובטח: אם מערכת ה-RAG שלכם נשענת על מידע פנימי פרטי, ודאו שהוא מאוחסן באופן מאובטח ושהגישה לממשק המערכת מנוהלת. לעולם אין לשתף מפתחות API.
מימוש RAG בסיסי: מדריך ב-7 שלבים
במדריך זה נבנה יישום שורת פקודה שעונה על שאלות לגבי מסמך יחיד. נשתמש ב-Python, ב-ChromaDB וב-API של Perplexity.
המטרה אינה לבנות מערכת RAG מוכנה לסביבת ייצור, אלא לספק מדריך ידידותי למתחילים שנועד להמחיש כיצד ארכיטקטורת RAG בסיסית עובדת בפועל.
שלב 1: התקנת תוכנה
ודאו שהרכיבים הבאים מותקנים במחשב שלכם ושיש לכם חשבון Perplexity API:
- Python: זוהי שפת התכנות שבה נשתמש כדי לבנות את המתזמר, שיתאם את זרימת הנתונים בין מודל ההטמעה, ChromaDB ו-Perplexity.
- Visual Studio: נשתמש ב-Visual Studio כעורך הקוד. לאחר ההתקנה, פתחו את החלונית Extensions והורידו את ההרחבה Python.
- חשבון Perplexity API: Perplexity תספק גם את מודל ההטמעה (שהופך את הנתונים שלכם להטמעות וקטוריות) וגם את ה-LLM שיעבד את ההנחיה המורחבת. אין צורך בחשבון בתשלום כדי לעשות זאת, אם כי תהיה עלות קטנה בקרדיטים.
שלב 2: יצירת תיקיית פרויקט
התחילו ביצירת תיקייה בשם rag-project ופתחו אותה ב-VS Code על ידי בחירה ב-File > Open Folder. לאחר מכן פתחו את הטרמינל המשולב דרך Terminal > New Terminal.
הזינו את הפקודה הבאה כדי ליצור סביבת Python וירטואלית:
py -3.12 -m venv .venvכעת עליכם להתקין ארבע ספריות Python: perplexityai, chromadb, python-dotenv ו-numpy. ה-SDK של Perplexity יאפשר לכם להתחבר למודלים של Perplexity דרך API. ChromaDB בונה את מסד הנתונים הווקטורי ויפעל באופן מקומי. Python-dotenv טוענת את מפתחות ה-API שלכם באופן מאובטח מקובץ מקומי, ו-NumPy נדרשת במהלך תהליך הקליטה.
התקינו אותן באמצעות הפקודה הבאה:
.\.venv\Scripts\python.exe -m pip install perplexityai chromadb python-dotenv numpyשלב 3: הגדרת הקבצים
הוסיפו את הקבצים הבאים ל-rag-project (תיקיית השורש של הפרויקט) על ידי בחירה ב-New File… בסרגל הצד של Explorer:
- index.py: זהו היישום שייצור אינדקס למסמך שלכם. אפשר להריץ אותו שוב כדי להוסיף טקסט נוסף.
- rag.py: זהו יישום ה-Python שישמש כמתזמר.
- document.txt: קובץ זה יכיל את התיעוד שתמירו למסד נתונים וקטורי.
- .env: כאן תשמרו את מפתח ה-API של Perplexity.
בקובץ .env הזינו את הטקסט הבא:
PERPLEXITY_API_KEY=your_api_key_hereהחליפו את your_api_key_here במפתח האמיתי שלכם. לשם כך, היכנסו ללוח הבקרה הראשי של Perplexity API Console ובחרו ב-Create an API key. שמרו את מפתח ה-API הזה בסוד.
נשתמש בשני מודלים: pplx-embed-v1-0.6b של Perplexity, כדי להטמיע את התיעוד שלנו לפני ההעלאה ל-ChromaDB, ו-GPT-5.6-Luna (שהגישה אליו נעשית דרך ה-API של Perplexity), שיעבד את המידע שאוחזר.
בקובץ document.txt, הוסיפו את הטקסט שצינור ה-RAG שלכם יאחזר. אם אין לכם מסמך זמין, השתמשו לצורך המדריך ב-Songs of Innocence and of Experience של ויליאם בלייק מ-Project Gutenberg. זהו טקסט קצר וקל להבנה שנמצא בנחלת הכלל.
שלב 4: כתיבת התוכנית ליצירת האינדקס
העתיקו והדביקו את הקוד שלהלן לקובץ index.py. כל כותרת משנה מספקת הסבר קצר על מה שעושה קטע הקוד שאחריה. זוהי התוכנית שתמיר את חומר המקור שלכם להטמעות וקטוריות ותאחסן אותן במסד נתונים של ChromaDB באמצעות מודל ההטמעה של Perplexity, pplx-embed-v1-0.6b, וספריית NumPy.
זה מה שהתוכנית עושה:
- פותחת את document.txt.
- מפצלת את הטקסט למקטעים חופפים.
- שולחת את המקטעים האלה למודל ההטמעה pplx-embed-v1-0.6b של Perplexity.
- מפענחת ומנרמלת את הווקטורים שהוחזרו באמצעות NumPy, ספריית Python של פונקציות חישוב.
- מאחסנת את הווקטורים ואת המקטעים המקוריים במסד נתונים של ChromaDB (תראו אותו מופיע בתיקיית הפרויקט שלכם).
התוכנית מוגדרת כך שלא תיצור מחדש אינדקס למסמך שלכם בכל פעם שתריצו אותה. אם תוסיפו מידע ל-document.txt, היא תטמיע רק את הטקסט החדש.
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())שלב 5. כתיבת המתזמר למערכת ה-RAG
העתיקו והדביקו את הקוד שלהלן לקובץ rag.py. זהו מימוש RAG שלם אך מינימלי, שמשתמש ב-SDK של Perplexity ובפונקציונליות מסד הנתונים הווקטורי המקומי של ChromaDB.
זה מה שיקרה כשתריצו את התוכנית:
- מתחברת ל-Perplexity ולמסד הנתונים החדש שלכם ב-ChromaDB.
- בודקת שמסד הנתונים מכיל מסמכים שעברו אינדוקס.
- מקבלת שאלה מהמשתמש.
- שולחת את השאלה למודל ההטמעה של Perplexity.
- מפענחת ומנרמלת את ההטמעה שהתקבלה באמצעות NumPy.
- מחפשת ב-ChromaDB את שלושת מקטעי המסמך הדומים ביותר.
- בונה הנחיה שמכילה את המקטעים שאוחזרו ואת שאלת המשתמש.
- שולחת את ההנחיה ל-GPT-5.6 Luna דרך ה-Agent API של Perplexity.
- מציגה את התשובה וממתינה לשאלה נוספת.
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)שלב 6: הרצת היישומים
בטרמינל PowerShell של VS Code, הזינו את הפקודה הבאה כדי להטמיע את התוכן של document.txt:
.\.venv\Scripts\python.exe index.pyאם יצירת האינדקס למסמך שלכם הצליחה, אמור להופיע פלט דומה לזה:
Indexing complete!
Unchanged chunks: 0
New or modified chunks: 58
Removed chunks: 0
Total stored chunks: 58עכשיו הגיע הזמן להריץ את יישום המתזמר הראשי. הזינו את הפקודה הבאה:
.\.venv\Scripts\python.exe rag.pyאם ההרצה תצליח, יוצג הפלט הבא:
Connected to existing ChromaDB database.
Stored chunks: 58
Ask a question (or type exit): כעת תוכלו לשאול שאלות שהתשובות להן יתבססו על מסד הנתונים הווקטורי שיצרתם, למשל:
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.שלב 7: הבנת מה שבניתם
המערכת שבניתם מכילה את שני צינורות הליבה שקיימים בכל ארכיטקטורת RAG: צינור יצירת אינדקס, או „קליטה”, וצינור אחזור ויצירה.
מימוש ה-RAG הבסיסי שלכם מבצע כעת את כל הפעולות הבאות:
- אחסון וקטורי: ההטמעות, המבוססות על קובץ הידע שלכם, נשמרות במסד הנתונים של ChromaDB בסביבה המקומית שלכם.
- יצירת אינדקס מצטברת: מקטעי מסמך שלא השתנו אינם מוטמעים שוב, ותוכלו לעדכן את המסמך מתי שתרצו.
- אחזור סמנטי: ChromaDB מאחזר מקטעים על סמך דמיון וקטורי (ביוזמת המתזמר).
- יצירת תשובות מבוססות מקורות: ה-LLM מקבל את הקטעים שאוחזרו כהקשר ומשלב אותם בתשובה.
- יצירת אינדקס ושאילתות באופן עצמאי: תוכלו לשאול שאלות בלי לעבד מחדש את כל הטקסט שבקובץ הידע שלכם.
- חיבור למודל באמצעות API: כל התשובות נוצרות באמצעות GPT-5.6 Luna דרך Perplexity, ולא באמצעות מודל שפועל במחשב שלכם.
השלבים הבאים: איך להרחיב את המימוש הזה
מטרת המדריך הזה היא לבנות מימוש בסיסי ופשוט. הוא מכסה חלק מרכיבי הליבה של ארכיטקטורת RAG שקיימים במערכות מודרניות. אם תרצו להתקדם, יש דרכים שונות להרחיב אותו.
הנה כמה רעיונות להרחבת היקף המימוש שלכם:
- התנסו ב-LangChain: LangChain היא אחת ממסגרות התזמור הפופולריות ביותר בקוד פתוח. היא כוללת רכיבים מוכנים מראש לבניית יישומים שמסוגלים להריץ צינורות RAG מורכבים.
- הרחיבו את יכולות החיפוש: שלבו חיפוש וקטורי צפוף עם חיפוש לקסיקלי (לפי מילות מפתח), כמו BM25. הוספת חיפוש באינטרנט באמצעות Perplexity Search API תאפשר לכם גם לבסס את התשובות על נתונים עדכניים.
- הוסיפו מודל דירוג מחדש: שלבו מודל דירוג מחדש שמדרג את הקטעים שאוחזרו ביחס להנחיה הראשונית ובוחר את הרלוונטיים ביותר להכללה בהקשר הקלט שמסופק ל-LLM.
- בנו מערכת RAG אדפטיבית: RAG אדפטיבי משתמש ב-LLM או במסווג (רכיב בתוך המתזמר) כדי לקבוע באיזו שיטת אחזור להשתמש במערכת RAG מודולרית. בניית מערכת כזו היא דרך טובה לחקור מימושי RAG מתוחכמים יותר.
אמנם כל זה עשוי להישמע מורכב, אבל זכרו שפלטפורמות AI נפוצות כמו Perplexity יכולות לספק סיוע מתקדם בכתיבת קוד. הן מצוינות לפרויקטים אישיים ולהתנסות, ואפשר לשמור על עלויות נמוכות יחסית.
הערה: מדריך זה משתמש ב-GPT-5.6 Luna. זמינות המודלים עשויה להשתנות, כך שייתכן שתצטרכו לעדכן את קריאת ה-API.