רוב צוותי ההנדסה נתקלים באותו מחסום עם RAG (Retrieval-Augmented Generation). הם פועלים לפי ספר הכללים של המדריכים: חלוקת מסמכים למקטעים (chunks) קבועים של חמש מאות שנים עשרה או אלף עשרים וארבעה טוקנים, העברתם דרך מודל embedding יחיד, וביצוע top-k lookup פשוט במסד נתונים וקטורי. במצגת זה נראה מצוין. בייצור (production), זה מתפרק.
מקטעים קבועים אינם מתחשבים בתוכן. הם יחלקו בשמחה חוזה משפטי באמצע משפט, וישאירו סעיפי אחריות תלויים בין שני קטעי טקסט לא קשורים. הם ישלכו תיאור של API endpoint שלמה לתוך מקטע אחד מנופח, כזה שגדול כל כך שהפרמטר הספציפי שהמשתמש שאל עליו טובע ברעש. וכאשר השליפה איטית, כל מילישנייה של latency משפיעה ישירות על חווית המשתמש. למדנו זאת בדרך הקשה. אז פירקנו את שכבת השליפה שלנו ובנינו אותה מחדש. ה-recall שלנו ב-ten קפץ משבעים ושמונה אחוזים לתשעים וחמישה אחוזים. ה-latency לא עלה. הוא קרס.
הבעיה עם RAG של "העתק-הדבק"
ערימת ה-RAG הסטנדרטית הפכה לסוג של הגדרת ברירת מחדל. מקטעים קטנים, מודל embedding אחד, חיפוש וקטורי, וזהו. הגישה הזו שורדת דמו, כי בדמו משתמשים בשאלות נקיות ובמסמכים מסודרים. נתוני ייצור לעולם אינם מסודרים.
למסמכים משפטיים יש מבנה היררכי. סעיפים מכילים תתי-סעיפים. תתי-סעיפים מכילים סעיפים קטנים. אם תחתכו אותם עם מונה טוקנים גס, תהרסו את אותם קשרים שהמודל צריך כדי להסיק מסקנות. גם לתיעוד API יש מבנה, אך הוא שונה. חתימת פונקציה, הפרמטרים שלה, ערך ההחזרה ודוגמת שימוש מהווים יחידה לוגית. אם תכריחו את זה להיכנס לחלון טוקנים קבוע, אתם או שתקטעו את הדוגמה או שתנפחו את המקטע בפונקציות לא קשורות. כרטיסי תמיכה הם מבולגנים, שיחתיים ומלאים בשינויי נושא פתאומיים. ויקי (Wikis) הם נרחבים וכוללים הפניות צולבות. אסטרטגיית chunking אחת לא יכולה לשרת את כולם, ובכל זאת צוותים פורסים באופן שגרתי בדיוק את זה. אנחנו הפסקנו להעמיד פנים שזה אפשרי.
Chunking אסטרטגי: התאמת השיטה לחומר
עברנו ל-content-aware chunking. עבור מסמכים משפטיים, אנו משתמשים ב-recursive chunking המכבד את ההיררכיה של המסמך. זה שומר על סעיפים שלמים ושומר על קשרי אב-בן בין הסעיפים. עבור תיעוד API, בנינו function-aware chunking המתייחס לכל פונקציה או endpoint כגבול. אם תיאור פרמטר ארוך מדי, המקטע מתרחב סביב הפונקציה הזו, ולא סביב מגבלת טוקנים. עבור כרטיסי תמיכה, אנו משתמשים ב-semantic chunking שמזהה גבולות נושא טבעיים. כשלקוח עובר בפתאומיות מתלונה על חיוב לבאג טכני, הפיצול קורה בדיוק בנקודת המעבר הזו. עבור ויקי ובסיסי ידע לא מובנים, אנו משתמשים ב-agentic chunking שבו LLM קל מעריך את הטקסט ומחליט היכן צריך להיות גבול משמעותי. זה לוקח יותר זמן להגדרה מאשר חיתוך לפי תווים, אך זה ההבדל בין שליפה שעובדת לשליפה שמנחשת.
שליפה היברידית: למה חיפוש וקטורי לבדו אינו מספיק
חיפוש וקטורי מבין משמעות, אך הוא עלול לפספס התאמות מדויקות. אם משתמש מדביק קוד שגיאה כמו ERR_CONNECTION_RESET_0x5F3, דמיון סמנטי עשוי לדרוג אותו נמוך יותר מפסקאות שרק דנות בשגיאות רשת באופן כללי. BM25, לעומת זאת, מוצא מחרוזות מדויקות אך מפספס קשרים קונספטואליים. אתם צריכים את שניהם.
אנו מריצים חיפוש וקטורי ו-BM25 במקביל. לאחר מכן אנו משלבים את התוצאות באמצעות Reciprocal Rank Fusion, או RRF, שמנרמל את הציונים משני מרחבי החיפוש השונים מבלי להכריח אותם להיכנס לאותו קנה מידה. לאחר השילוב (fusion), אנו שולחים את המועמדים המובילים דרך cross-encoder reranker. זה מוסיף כמות קטנה של latency, אך הרווח ב-precision הוא משמעותי. ה-reranker קורא את השאילתה ואת כל מועמד יחד ומקצה ציון רלוונטיות המדויק הרבה יותר מסימילריות הקוסינוס של ה-embedding הראשוני. בפועל, השילוב הזה תופס קודי שגיאה מדויקים שחיפוש וקטורי טהור מפספס, תוך שהוא עדיין מציף שלבי פתרון תקלות קשורים קונספטואלית שחיפוש מילות מפתח היה מתעלם מהם.
הרחבת שאילתות: תיקון קלט המשתמש לפני שהוא מגיע לאינדקס
משתמשים לא כותבים שאילתות חיפוש מושלמות. הם שואלים שאלות multi-hop כמו "למה הפריסה האחרונה שלי נכשלה ואיך אני מבצע rollback", מה שדורש מציאת שני גופי ידע נפרדים וחיבור ביניהם. או שהם שואלים שאלות מעורפלות שמתאימות בצורה גרועה לאינדקס.
אנחנו מבצעים טרנספורמציה לשאילתות לפני החיפוש. שאלה מרובת-שלבים (multi-hop) מתפרקת לתת-שאלות. כוונה מעורפלת מורחבת למספר שאילתות חיפוש ספציפיות. מצאנו שהרחבת שאילתת משתמש אחת לחמש שאילתות חיפוש נפרדות יכולה להעלות את ה-recall מ-78% ל-96%. זה לא עניין של לתת ל-LLM הנחיות (prompting) חזקות יותר. זה עניין של מתן יותר הזדמנויות למערכת השליפה למצוא את ההקשר הנכון. כל שאילתה מופקת לוכדת זווית או טרמינולוגיה שונה, והתוצאות המאוחדות מציירות תמונה מלאה.
אופטימיזציה בייסיאנית: הפסיקו לנחש
ברגע שיש לכם מספר אסטרטגיות chunking, שליפה היברידית (hybrid retrieval) והרחבת שאילתות, אתם ניצבים בפני בעיה חדשה. יש יותר מדי פרמטרים (knobs). גודל ה-chunk, אחוז החפיפה (overlap), משקל הוקטור מול משקל ה-BM25, ספי reranking וערכי top-k – כולם מקיימים אינטראקציה בצורה לא ליניארית. כוונון ידני הופך למשחק של ניחושים.
הפסקנו לנחש. אנחנו מתייחסים ל...
