RAG Systems —
מ-0 לProduction
RAG (Retrieval Augmented Generation) הוא הדרך להפוך LLM לכלי שעונה על שאלות לפי המסמכים שלך. כך בונים Chatbot שיודע הכל על החברה שלך — ולא ממציא תשובות.
מה זה RAG ולמה צריך את זה?
LLMs כמו GPT-4 ו-Claude אימנו על מידע כללי מהאינטרנט עד לתאריך cut-off מסוים. הם לא יודעים כלום על המסמכים הפנימיים של החברה שלך, על הנהלים העדכניים, על בסיס הידע של התמיכה שלך — ובגלל זה הם "ממציאים" תשובות כשהם לא יודעים, תופעה שנקראת Hallucination.
RAG פותר את הבעיה הזאת: במקום לסמוך על הידע המאומן של המודל, אנחנו שולפים בזמן אמת את המידע הרלוונטי ממסמכים שלנו, מכניסים אותו ל-Prompt, ומבקשים מהמודל לענות על בסיס המידע הזה בלבד. התוצאה היא מערכת שעונה על שאלות בצורה מדויקת, מבוססת מקורות, ועדכנית.
- •המודל לא יודע על מסמכי החברה
- •Hallucinations — תשובות ממוצאות
- •ידע קפוא לתאריך אימון
- •אין מקורות לאימות
- •מבוסס על המסמכים שלך בדיוק
- •מקורות לכל תשובה
- •עדכני כמו ה-Index שלך
- •"אני לא יודע" במקום ניחוש
ל-RAG Pipeline יש שני שלבים מרכזיים:
טען מסמכים ← חתוך לחלקים (chunks) ← צור Embeddings ← שמור ב-Vector DB
שאלה ← צור Embedding ← חפש similarity ← קבל context ← גנרט תשובה
ארכיטקטורה מלאה
לפני שנגיע לקוד, חשוב להבין את כל הרכיבים ואיך הם מתחברים. הדיאגרמה הבאה מציגה את ה-Pipeline המלא — משלב טעינת המסמכים ועד ליצירת התשובה הסופית למשתמש.
[Documents: PDF / Word / Web / CSV]
|
v
[Document Loader] <-- LangChain loaders
|
v
[Raw Text]
|
v
[Text Splitter] <-- RecursiveCharacterTextSplitter
(chunks ~1000 chars)
|
v
[Chunks]
|
v
[Embedding Model] <-- OpenAI / HuggingFace
(each chunk → vector)
|
v
[Vector Store (Index)] <-- Pinecone / Chroma / FAISS
============ Online Phase ============
[User Question]
|
v
[Same Embedding Model]
|
v
[Query Vector]
|
v
[Similarity Search top-k] <-- cosine similarity
|
v
[Relevant Chunks (context)]
|
v
[Prompt Template] <-- question + context
|
v
[LLM: GPT-4o / Claude]
|
v
[Final Answer + Sources]
כל שלב בארכיטקטורה הזאת הוא נקודה שאפשר לשפר ולאופטימיזציה. החלטות על גודל ה-chunk, מודל ה-Embedding שנבחר, ומספר ה-chunks שמוחזרים (top-k) — כולן משפיעות ישירות על איכות התוצאות הסופיות.
Document Loaders — טעינת מסמכים
LangChain מגיע עם עשרות Document Loaders שתומכים בפורמטים שונים. הנה הנפוצים ביותר:
pip install langchain langchain-community langchain-openai langchain-pinecone pypdf chromadb sentence-transformers
# התקן: pip install langchain-community pypdf
from langchain_community.document_loaders import (
PyPDFLoader,
DirectoryLoader,
WebBaseLoader,
CSVLoader,
JSONLoader
)
# PDF יחיד
loader = PyPDFLoader("company_policy.pdf")
docs = loader.load()
print(f"Loaded {len(docs)} pages")
# תיקייה מלאה של PDFs
loader = DirectoryLoader(
"./docs/",
glob="**/*.pdf",
loader_cls=PyPDFLoader
)
docs = loader.load()
print(f"Loaded {len(docs)} pages")
# טעינה מ-URL
loader = WebBaseLoader("https://docs.example.com/api")
docs = loader.load()
# Google Drive (מצריך הגדרת OAuth)
from langchain_google_community import GoogleDriveLoader
loader = GoogleDriveLoader(
folder_id="your_folder_id",
file_types=["document", "pdf"]
)
docs = loader.load()
Text Splitting — חיתוך חכם
אחרי שטוענים את המסמכים, צריך לחתוך אותם ל-chunks קטנים יותר. זה קריטי — chunk גדול מדי מכיל יותר מדי מידע לא רלוונטי ומגדיל עלויות. chunk קטן מדי לא נותן context מספיק. הערכים המומלצים לרוב המקרים: chunk_size=1000, chunk_overlap=200.
from langchain.text_splitter import (
RecursiveCharacterTextSplitter,
MarkdownHeaderTextSplitter,
TokenTextSplitter
)
# Recursive — הכי מומלץ לרוב המקרים
splitter = RecursiveCharacterTextSplitter(
chunk_size=1000, # תווים לכל chunk
chunk_overlap=200, # חפיפה בין chunks — שומרת context
separators=["\n\n", "\n", ".", " "] # סדר עדיפות לחיתוך
)
chunks = splitter.split_documents(docs)
print(f"Created {len(chunks)} chunks")
# לפי Headers (Markdown) — מושלם לתיעוד טכני
headers = [("#", "H1"), ("##", "H2"), ("###", "H3")]
md_splitter = MarkdownHeaderTextSplitter(
headers_to_split_on=headers
)
md_docs = md_splitter.split_text(markdown_text)
# לפי Tokens — מדויק יותר לחישוב עלויות API
token_splitter = TokenTextSplitter(
chunk_size=250,
chunk_overlap=30
)
token_chunks = token_splitter.split_documents(docs)
טיפ: chunk_overlap חשוב
ה-overlap מוודא שמידע שנמצא על גבול שני chunks לא יאבד. לטקסטים עם משפטים ארוכים, תגדיל ל-300-400. לקוד, תוכל להוריד ל-50.
Embeddings — הלב של RAG
Embedding הוא המרת טקסט לוקטור מספרי (רשימה של מאות מספרים). הרעיון הגאוני הוא שטקסטים עם משמעות דומה יקבלו וקטורים קרובים זה לזה במרחב. כך, כשמחפשים "מה שעות הפעילות?", המערכת מוצאת chunk עם "שעות פתיחה של השירות" — גם בלי התאמת מילים מדויקת.
יש שתי אפשרויות עיקריות: מודלי OpenAI שמצריכים תשלום לפי שימוש אבל מצוינים באיכות, ומודלי HuggingFace שרצים לוקאלית ובחינם. לשפה העברית, מומלץ במיוחד מודל multilingual.
from langchain_openai import OpenAIEmbeddings
from langchain_community.embeddings import HuggingFaceEmbeddings
import numpy as np
# OpenAI (מומלץ לאיכות ולדיוק)
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
# 1536 dimensions, ~$0.02 per 1M tokens
# text-embedding-3-large — איכות גבוהה יותר, פי 2 יקר
embeddings_large = OpenAIEmbeddings(model="text-embedding-3-large")
# 3072 dimensions
# HuggingFace (חינמי, רץ לוקאלית, תומך עברית!)
embeddings_hf = HuggingFaceEmbeddings(
model_name="sentence-transformers/paraphrase-multilingual-mpnet-base-v2"
)
# 768 dimensions, תומך ב-50+ שפות כולל עברית
# בדיקת Cosine Similarity
v1 = embeddings.embed_query("AI Agent")
v2 = embeddings.embed_query("Autonomous AI System")
similarity = np.dot(v1, v2) / (np.linalg.norm(v1) * np.linalg.norm(v2))
print(f"Cosine similarity: {similarity:.3f}") # ~0.85
# Embedding של batch
texts = ["מה ימי הפעילות?", "how to reset password", "price list"]
vectors = embeddings.embed_documents(texts)
print(f"Shape: {len(vectors)} x {len(vectors[0])}")
Vector Stores ו-Retrieval
ה-Vector Store הוא מסד הנתונים שמאחסן את הוקטורים ומאפשר חיפוש מהיר של הוקטורים הדומים ביותר. לפיתוח מקומי ממליצים על Chroma, לפרודקשיין — Pinecone הוא הבחירה הנפוצה ביותר בשוק.
from langchain_pinecone import PineconeVectorStore
from langchain_community.vectorstores import Chroma
import os
# === Pinecone (Production) ===
os.environ["PINECONE_API_KEY"] = "your-api-key"
vectorstore = PineconeVectorStore.from_documents(
documents=chunks,
embedding=embeddings,
index_name="my-rag-index",
namespace="v1"
)
# חיבור ל-index קיים
vectorstore = PineconeVectorStore(
index_name="my-rag-index",
embedding=embeddings,
namespace="v1"
)
# === Chroma (Development / Local) ===
vectorstore = Chroma.from_documents(
documents=chunks,
embedding=embeddings,
persist_directory="./chroma_db"
)
# טעינה מ-disk
vectorstore = Chroma(
persist_directory="./chroma_db",
embedding_function=embeddings
)
# === Retriever ===
retriever = vectorstore.as_retriever(
search_type="similarity", # similarity / mmr / similarity_score_threshold
search_kwargs={"k": 4} # מספר chunks להחזיר
)
# MMR — גיוון תוצאות (מניע כפילויות)
retriever_mmr = vectorstore.as_retriever(
search_type="mmr",
search_kwargs={"k": 6, "fetch_k": 20, "lambda_mult": 0.5}
)
# חיפוש ידני
results = vectorstore.similarity_search("מה מדיניות ההחזרות?", k=3)
for doc in results:
print(doc.page_content[:200])
print(f"Source: {doc.metadata.get('source', 'N/A')}")
MMR vs Similarity Search
Similarity Search מחזיר את ה-k הדומים ביותר — אבל לעיתים הם כמעט זהים. MMR (Maximal Marginal Relevance) מאזן בין רלוונטיות לגיוון, כך שהתוצאות מכסות יותר זוויות של הנושא.
RAG Chain מלא — הכל ביחד
עכשיו מחברים את כל הרכיבים. ה-Prompt Template הוא אחד הדברים הכי חשובים — הנחיות ברורות ל-LLM על מה לעשות עם ה-context שסיפקנו, ובמיוחד ההוראה לומר "אני לא יודע" כשהמידע לא קיים.
from langchain_openai import ChatOpenAI
from langchain.chains import RetrievalQA
from langchain_core.prompts import PromptTemplate
# Prompt Template — ה-Prompt שמגדיר איך המודל יתנהג
TEMPLATE = """השתמש בחלקי ה-Context הבאים כדי לענות על השאלה בסוף.
אם אינך יודע את התשובה, אמור "אני לא יודע" — אל תמציא תשובות.
ענה תמיד בעברית, בצורה ברורה ומסודרת.
Context:
{context}
שאלה: {question}
תשובה:"""
prompt = PromptTemplate(
template=TEMPLATE,
input_variables=["context", "question"]
)
# LLM
llm = ChatOpenAI(
model="gpt-4o",
temperature=0 # 0 = עקבי ודטרמיניסטי
)
# RAG Chain
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff", # stuff = מכניס הכל ל-prompt
retriever=retriever,
return_source_documents=True, # מחזיר מקורות
chain_type_kwargs={"prompt": prompt}
)
# שאלה!
result = qa_chain.invoke({"query": "מה מדיניות ההחזרות שלנו?"})
print(result["result"])
print("\nמקורות:")
for doc in result["source_documents"]:
print(f" - {doc.metadata.get('source', 'unknown')}")
גרסה עם LCEL (LangChain Expression Language)
ה-LCEL הוא הגישה המודרנית ב-LangChain — הרכבה של רכיבים עם pipe operator. גמיש ומומלץ לפרויקטים חדשים.
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser
def format_docs(docs):
return "\n\n".join(doc.page_content for doc in docs)
# LCEL chain — קריא ונקי
rag_chain = (
{"context": retriever | format_docs, "question": RunnablePassthrough()}
| prompt
| llm
| StrOutputParser()
)
# שאלה
answer = rag_chain.invoke("מה כתוב בחוזה לגבי ביטול?")
print(answer)
# Streaming
for chunk in rag_chain.stream("תסביר את תנאי השירות"):
print(chunk, end="", flush=True)
Advanced RAG — שיפור ביצועים
ה-Basic RAG מגיע לאיכות טובה, אבל יש כמה טכניקות מתקדמות שיכולות לשפר את הדיוק משמעותית, במיוחד כשהמאגר גדול או השאלות מורכבות.
Reranking — מיון חוזר לפי רלוונטיות
Similarity Search מוצא chunks קרובים מבחינה וקטורית, אבל לא תמיד הכי רלוונטיים לשאלה הספציפית. Reranker הוא מודל נפרד שמדרג מחדש את התוצאות לפי רלוונטיות אמיתית — ומשפר recall ב-20-40%.
from langchain.retrievers import ContextualCompressionRetriever
from langchain.retrievers.document_compressors import CrossEncoderReranker
from langchain_community.cross_encoders import HuggingFaceCrossEncoder
# Cross-Encoder Reranker — מדרג results לפי רלוונטיות
model = HuggingFaceCrossEncoder(
model_name="cross-encoder/ms-marco-MiniLM-L-6-v2"
)
compressor = CrossEncoderReranker(model=model, top_n=3)
# Retriever עם Reranking
compression_retriever = ContextualCompressionRetriever(
base_compressor=compressor,
base_retriever=retriever
)
# שימוש
docs = compression_retriever.invoke("מה עלויות המנוי?")
print(f"Got {len(docs)} reranked docs")
HyDE — Hypothetical Document Embeddings
רעיון חכם: במקום לחפש את הוקטור של השאלה, ה-LLM קודם מייצר תשובה היפותטית, ואז מחפשים לפי הוקטור של התשובה הזו. שאלה ומסמך לרוב לא דומים וקטורית, אבל שתי תשובות לאותו נושא — כן. זה משפר משמעותית את ה-recall.
from langchain.chains import HypotheticalDocumentEmbedder
from langchain_core.prompts import PromptTemplate
# Prompt ליצירת מסמך היפותטי
hyde_prompt = PromptTemplate.from_template(
"Write a detailed paragraph that would answer: {question}"
)
# HyDE Retriever
hyde_embeddings = HypotheticalDocumentEmbedder.from_llm(
llm=llm,
base_embeddings=embeddings,
custom_instructions=hyde_prompt
)
# שילוב ב-vectorstore
hyde_retriever = vectorstore.as_retriever(
search_kwargs={"k": 4}
)
# לחיפוש משתמשים ב-hyde_embeddings.embed_query
Multi-Query Retriever — גיוון השאלות
ה-LLM מייצר מספר וריאציות של השאלה המקורית, מבצע חיפוש לכל אחת מהן, ומאחד את התוצאות. מגדיל recall בצורה משמעותית לשאלות מורכבות.
from langchain.retrievers.multi_query import MultiQueryRetriever
import logging
logging.basicConfig()
logging.getLogger("langchain.retrievers.multi_query").setLevel(logging.INFO)
multi_retriever = MultiQueryRetriever.from_llm(
retriever=vectorstore.as_retriever(),
llm=llm
)
# יכניס 3 וריאציות של השאלה ויאחד תוצאות
unique_docs = multi_retriever.invoke("מה הזכויות שלי כלקוח?")
Production Checklist
לפני שמעבירים מערכת RAG לפרודקשיין, יש נושאים קריטיים שחייבים לטפל בהם. מניסיון — רוב הבעיות בפרודקשיין נובעות מארבעה תחומים: הערכת איכות, ניטור, עלויות ואבטחה.
Evaluation — הערכת איכות
- •RAGAS — מסגרת הערכה אוטומטית: Faithfulness, Answer Relevancy, Context Recall
- •LangSmith — ניטור שיחות, tracing, A/B testing של prompts
- •בנה מאגר שאלות בדיקה לפני go-live
Monitoring — ניטור שוטף
- •מדוד Query latency (p50, p95, p99)
- •עקוב אחר Retrieval quality — האם המקורות נכונים?
- •Feedback loop — אפשר למשתמשים לדרג תשובות
Cost Optimization
- •GPTCache — Cache לשאלות חוזרות (חוסך 60-80%)
- •חשב tokens per query × מחיר × שאלות/יום
- •שקול gpt-4o-mini למקרים פשוטים
Security — אבטחה
- •לא לאחסן PII (מידע אישי) ב-Vector DB בלי הצפנה
- •הגנה מפני Prompt Injection — validate user input
- •namespace לפי permissions — כל משתמש רואה רק מה שמותר לו
טיפים מניסיון
Metadata Filtering: הוסף metadata לכל chunk (source, date, department) ואפשר סינון לפיהם. למשל: "ראה רק מסמכים מ-2024" או "רק מחלקת HR".
Incremental Indexing: אל תבנה מחדש את כל ה-index בכל עדכון. עקוב אחרי last_modified ועדכן רק chunks ששונו.
Parent-Child Chunking: שמור chunks קטנים ל-Embedding, אבל כשמוצאים match — החזר את ה-parent chunk הגדול יותר ל-LLM לצורך context מלא.
Chain of Thought בתשובות: הוסף להנחיה "חשוב שלב אחר שלב לפני שאתה עונה". מגדיל דיוק ב-15-25% לשאלות מורכבות הדורשות הסקה.
סיכום — מה הלאה?
בנית מערכת RAG מלאה — מטעינת מסמכים, דרך chunking ו-embedding, עד ל-retrieval ו-generation. זה הבסיס שעליו בנויות מאות מוצרי AI כיום. השלב הבא הוא לחבר את ה-RAG ל-Agent שיכול לנקוט פעולות על בסיס מה שמצא — ולכן מומלץ להמשיך למדריך AI Agents.
סיכום מהיר — החלטות ארכיטקטורה
| נושא | פיתוח | פרודקשיין |
|---|---|---|
| Vector Store | Chroma (local) | Pinecone / pgvector |
| Embedding | multilingual-mpnet | text-embedding-3-small |
| LLM | gpt-4o-mini | gpt-4o / Claude |
| Chunk size | 1000 / overlap 200 | מותאם לתוכן |
| Retrieval | similarity k=4 | MMR + Reranking |
קישורים שימושיים
Framework לאפליקציות LLM — Chains, Agents, LCEL ו-LangSmith
Vector Database לפרודקשיין — Serverless, Namespaces, Metadata Filtering
ReAct Loop, CrewAI, LangGraph — RAG כחלק ממערכת Agentic
תיעוד רשמי ומשאבים
מוכנים לבנות מערכת RAG אמיתית?
הצטרפו למסלול AI Agents Development — קורס מלא שמלמד LangChain, RAG, CrewAI ו-LangGraph עם 5 פרויקטים עסקיים אמיתיים.