איך מחברים LLM או סוכן AI למידע חיצוני בזמן אמת, בלי אימון מחדש ובלי הזיות. מדריך מלא — מהרעיון ועד קוד מעשי שעובד.
הבעיה שה-RAG פותר — ולמה זה גאוני.
מודל שפה יודע רק מה שאומן עליו. הוא לא מכיר מסמכים פנימיים, מידע חדש, DB ארגוני, או כל תוכן שנוצר אחרי ה-cutoff date שלו.
שולחים לו בזמן אמת את הקטעים הרלוונטיים ביותר מהמאגר שלך, ומבקשים ממנו לענות רק על בסיסם. מידע מדויק, ניתן לאימות.
אימון מחדש של מודל עולה הרבה כסף, זמן ומשאבים. בכל פעם שהמידע מתעדכן — צריך לאמן שוב.
מעדכנים את Vector DB — המודל מיד עובד עם המידע החדש. אין צורך לגעת במודל עצמו כלל.
שני שלבים עיקריים: אינדוקס (מבצעים פעם אחת) ושאילתה (כל פעם שלקוח שואל).
מבצעים פעם אחת / בעת עדכון המסמכים
PDF, Word, דפי אינטרנט, שורות מ-DB, Notion, Confluence — כל מקור מידע שרלוונטי לתחום.
Data Sourcesמחלקים כל מסמך לקטעים קטנים (512–1024 טוקן בד"כ). גודל chunk משפיע על איכות האחזור — קטן מדי = חסר הקשר, גדול מדי = רעש.
Text Splitterמודל Embedding ממיר כל קטע טקסט למספרים (וקטור). טקסטים עם משמעות דומה מקבלים וקטורים קרובים במרחב. דוגמאות: text-embedding-3-small, bge-m3, nomic-emb.
Embedding Modelכל וקטור נשמר יחד עם הטקסט המקורי ו-metadata (שם קובץ, תאריך, כותרת...). DB פופולריים: Chroma, Pinecone, Weaviate, FAISS, pgvector.
Vector DBמתבצע בכל פעם שמישהו שואל שאלה
המשתמש כותב שאלה. זה יכול להיות שאלה ישירה, הוראה לסוכן, או חלק מ-multi-turn conversation.
User Queryחובה להשתמש באותו מודל שהטמיע את המסמכים! אחרת המרחבים לא תואמים ואין התאמה.
Embed Queryמחפשים את K הקטעים הקרובים ביותר לווקטור השאלה (בד"כ K=3-10). שיטת חיפוש: cosine similarity, dot product, Euclidean distance.
Top-K Retrievalמחברים: System Prompt + קטעים שנמצאו (Context) + שאלת המשתמש. ה-LLM מקבל הוראה לענות רק על בסיס הקטעים שסופקו.
Prompt Builderהמודל קורא את הקטעים, מסנתז תשובה מדויקת, ויכול לציין מאיזה מסמך לקח את המידע (Citations). Claude, GPT-4, Llama, Mistral — הכל עובד.
LLM Responseשלוש גישות מרכזיות לבניית RAG ב-Python. בחר את המתאים לך.
# התקנה: pip install langchain langchain-openai chromadb from langchain.document_loaders import DirectoryLoader from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain_openai import OpenAIEmbeddings, ChatOpenAI from langchain.vectorstores import Chroma from langchain.chains import RetrievalQA # 1. טעינת מסמכים loader = DirectoryLoader("./docs", glob="**/*.pdf") docs = loader.load() # 2. חלוקה לקטעים splitter = RecursiveCharacterTextSplitter( chunk_size=1000, chunk_overlap=200 ) chunks = splitter.split_documents(docs) # 3. יצירת Vector DB embeddings = OpenAIEmbeddings(model="text-embedding-3-small") vectordb = Chroma.from_documents(chunks, embeddings) # 4. חיבור ל-LLM + שאילתה llm = ChatOpenAI(model="gpt-4o") qa = RetrievalQA.from_chain_type( llm=llm, retriever=vectordb.as_retriever(search_kwargs={"k": 4}) ) # 5. שאלה! result = qa.invoke("מה מדיניות החזרות של החברה?") print(result["result"])
# התקנה: pip install llama-index from llama_index.core import VectorStoreIndex, SimpleDirectoryReader from llama_index.core import Settings from llama_index.llms.openai import OpenAI from llama_index.embeddings.openai import OpenAIEmbedding # 1. הגדרות גלובליות Settings.llm = OpenAI(model="gpt-4o") Settings.embed_model = OpenAIEmbedding(model="text-embedding-3-small") Settings.chunk_size = 1024 # 2. טעינה ויצירת index (הכל באוטומטי) documents = SimpleDirectoryReader("./docs").load_data() index = VectorStoreIndex.from_documents(documents) # 3. שאילתה query_engine = index.as_query_engine(similarity_top_k=4) response = query_engine.query("מה מדיניות החזרות של החברה?") print(response) print("\nמקורות:") for node in response.source_nodes: print(f" - {node.metadata.get('file_name')}")
# התקנה: pip install anthropic chromadb sentence-transformers import anthropic import chromadb from sentence_transformers import SentenceTransformer # 1. הקמת Vector DB client = chromadb.Client() collection = client.create_collection("docs") embed_model = SentenceTransformer("all-MiniLM-L6-v2") # 2. הכנסת מסמכים (דוגמה) docs = ["מדיניות החזרות: 30 ימים", "משלוח חינם מעל 200 ש״ח"] embeddings = embed_model.encode(docs).tolist() collection.add( documents=docs, embeddings=embeddings, ids=["doc1", "doc2"] ) # 3. פונקציית RAG עם Claude def rag_query(question): # חפש קטעים רלוונטיים q_embedding = embed_model.encode(question).tolist() results = collection.query( query_embeddings=[q_embedding], n_results=3 ) context = "\n".join(results["documents"][0]) # קריאה ל-Claude עם ההקשר claude = anthropic.Anthropic() response = claude.messages.create( model="claude-sonnet-4", max_tokens=1024, messages=[{ "role": "user", "content": f"""ענה על השאלה רק על בסיס הקטעים הבאים: {context} שאלה: {question}""" }] ) return response.content[0].text # 4. שימוש answer = rag_query("מה מדיניות ההחזרות?") print(answer)
הטכנולוגיות המובילות — לכל שלב של הצינור.
ההבדל בין RAG בינוני למצוין הוא הפרטים.
Chunk קטן מדי (<300 טוקנים) מאבד הקשר. גדול מדי (>2000) מביא רעש. טווח זהב: 500-1200 טוקנים, עם chunk_overlap של 10-20%.
חובה! אם הטמעת את המסמכים עם text-embedding-3-small — גם השאילתה חייבת להיטמע בו. מרחבים שונים = אפס התאמות.
לפני שמתעסקים עם ה-LLM — וודאו שה-retrieval מחזיר את הקטעים הנכונים. אם הוא לא — הכל נידון לכישלון. בדקו top-K ידנית עם 20 שאלות לדוגמה.
שמרו עם כל chunk: שם קובץ, תאריך, מחבר, כותרת. אפשר לסנן לפי כך שהחיפוש יהיה מהיר ומדויק יותר. "תביא רק מסמכים מ-2025" — פתיר רק עם metadata טובים.
אחרי שקיבלתם top-10 מה-Vector DB — הריצו דרך Reranker (כמו Cohere Rerank). זה משפר דיוק ב-30-50%. עולה קצת יותר, אבל שווה.
בבקשה ל-LLM — תבקשו לציין מאיזה קטע הוא שואב את המידע. משתמשים אוהבים לראות מקורות, וזה מקל על אימות ודיבוג.
כן: שאלות-תשובות על מסמכים פנימיים, ערכים עדכניים מתמיד (מחירון, מלאי, מדיניות), מידע גדול מאוד (מיליוני מסמכים), תשובות שדורשות מקורות.
לא: משימות יצירתיות (כתיבת שירים), חישובים מתמטיים, משימות שדורשות היגיון חזק בלי הסתמכות על מידע, מידע שצריך להשתנות דרמטית (אז עדיף fine-tuning).