יישום דפוסי שליפה לצינורות RAG

הושלמה

יצירת שליפה-מוגברת (RAG) משפרת את תגובות מודלי השפה על ידי מתן הקשר רלוונטי מהנתונים שלך. רכיב ה-retriever מחפש במאגר הידע שלך ומחזיר מסמכים שה-LLM משתמש בהם כדי לייצר תשובות מדויקות ומבוססות על הקרקע. Azure Database ל-PostgreSQL עם pgvector משמש כשליף יעיל, שומר את ההטמעות, ומבצע שאילתות דמיון שמזינות את שלב היצירה.

יחידה זו כוללת תכנון סכמות לעומסי עבודה ב-RAG, יישום שאילתות שליפה של קטעים, בניית צינורות קליטת מסמכים, החזרת מטא-דאטה של ציטוטים והערכת איכות השחזור.

הבנת ארכיטקטורת RAG ותפקיד הרטריבר

מערכות RAG פועלות לפי דפוס של שלושה שלבים:

  1. הטמעת שאילתה: המרו את שאלת המשתמש לווקטור באמצעות מודל הטמעה
  2. שחזור: חפש בחנות הווקטורים מסמכים דומים להטמעת השאילתות
  3. דור: העבר את המסמכים שנשלפו כהקשר ל-LLM, שמייצר תגובה

PostgreSQL פועל כ-retriever בארכיטקטורה זו. איכות השליפה משפיעה ישירות על איכות היצירה. אם הרטריבר מחזיר מסמכים לא רלוונטיים, ל-LLM חסר את ההקשר הנדרש למענה נכון ועלול לייצר מידע שגוי. אם הרטריבר מפספס מסמכים רלוונטיים, התשובה עלולה להיות לא שלמה.

עבור עוזר מחקר משפטי, שליפה אפקטיבית פירושה למצוא תקדימים, חוקים וסעיפי חוזה המטפלים בשאלה של עורך הדין. ה-LLM מסנתז את המקורות הללו לתשובה קוהרנטית עם ציטוטים.

סכמות עיצוב לעומסי עבודה ב-RAG

יישומי RAG בדרך כלל עובדים עם מקטעי מסמכים ולא עם מסמכים שלמים. מסמכים ארוכים חורגים מגבולות ההקשר של LLM ועלולים להכיל קטעים שאינם רלוונטיים לשאילתה מסוימת. חתיכה מחלקת מסמכים לקטעים קטנים וממוקדים שניתן לשלוף באופן עצמאי.

מסמכים וחלקים נפרדים

השתמש בשתי טבלאות: אחת למסמכי מקור ואחת למקטעים עם הטמעות:

CREATE TABLE source_documents (
    id SERIAL PRIMARY KEY,
    title TEXT NOT NULL,
    source_url TEXT,
    document_type TEXT,
    ingested_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE TABLE document_chunks (
    id SERIAL PRIMARY KEY,
    document_id INTEGER REFERENCES source_documents(id) ON DELETE CASCADE,
    chunk_index INTEGER NOT NULL,
    content TEXT NOT NULL,
    embedding vector(1536),
    token_count INTEGER,
    start_char INTEGER,
    end_char INTEGER,
    UNIQUE (document_id, chunk_index)
);

עיצוב זה מספק מספר יתרונות:

  • שליפה גמישה: אתה יכול לשלוף חלקים בודדים או לשחזר קטעי מסמך
  • מעקב מקור: כל קטע מקשר חזרה למקור שלו לציטוטים
  • אחסון יעיל: מטא-דאטה של המסמך נשמר פעם אחת, לא משוכפל לכל חלק
  • עדכונים קלים: החלפת מסמך פירושה למחוק את החלקים שלו ולבלע מחדש

הוספת מטא-דאטה לסינון והקשר

כלול מטא-דאטה שמסייע בסינון ובשחזור הקשר:

CREATE TABLE document_chunks (
    id SERIAL PRIMARY KEY,
    document_id INTEGER REFERENCES source_documents(id) ON DELETE CASCADE,
    chunk_index INTEGER NOT NULL,
    content TEXT NOT NULL,
    embedding vector(1536),
    token_count INTEGER,
    section_title TEXT,
    page_number INTEGER,
    created_at TIMESTAMPTZ DEFAULT NOW()
);

כותרות מדורים ומספרי עמודים עוזרים למשתמשים לאמת מידע שנשלף ועוזרים למודל להבין את ההקשר של כל חלק.

צור אינדקסים לתבניות שאילתות RAG

מדדי דמיון וקטוריים (HNSW או IVFFlat) מאפשרים חיפושי הטמעה מהירים, כפי שסובר קודם במודול זה. עומסי עבודה ב-RAG נהנים גם מאינדקסים של B-tree שמאיצים את החיבורים והמסננים הייחודיים לשחזור מקטעים. כשאתה שולף מקטעים, לעיתים קרובות אתה מחבר חזרה למסמכי המקור למטא-דאטה של ציטוטים ומסנן או מסדר לפי מיקום החלק בתוך המסמך. ללא האינדקסים הללו, PostgreSQL סורק את כל טבלת החלקים לכל שאילתת שחזור.

-- B-tree index for document lookups (speeds up JOINs to source_documents)
CREATE INDEX document_chunks_document_id_idx
ON document_chunks (document_id);

-- Composite index for chunk ordering (supports context window queries)
CREATE INDEX document_chunks_doc_chunk_idx
ON document_chunks (document_id, chunk_index);

האינדקס המורכב על (document_id, chunk_index) חשוב במיוחד לשליפת חלון הקשר, שבו אתה מביא קטעים סמוכים כדי לספק הקשר מסביב. בלעדיו, PostgreSQL חייב לסרוק את כל המקטעים כדי למצוא שכנים.

יישום שליפת מקטעים לבניית הקשר

שאילתות RAG מושכות קטעים שהופכים להקשר של ה-LLM. תבנית השליפה שתבחרו משפיעה גם על איכות התשובה וגם על יעילות הטוקנים. צריך לאזן בין שלושה נושאים מתחרים: שליפת מספיק תוכן רלוונטי, שמירה על גבולות הטוקנים, ומתן הקשר מספק למודל השפה הגדול כדי להבין כל חלק.

הגישה הפשוטה ביותר מחזירה ישירות את החלקים הדומים ביותר של k העליון. זה עובד טוב כשהצ'אנקים שלך עצמאיים והשאלות שלך תואמות את החלקים הבודדים בצורה ברורה. עם זאת, מסמכים משפטיים, מפרטים טכניים ותוכן נרטיבי לעיתים קרובות מפיצים מושגים על פני מספר פסקאות. קטע עשוי להתייחס ל"התנאים הנ"ל" או "כפי שתואר לעיל" מבלי להכיל את הטקסט המוזכר.

במקרים אלו, שליפת חלון הקשר מביאה מקטעים סמוכים יחד עם כל התאמה. אם חתיכה 47 דומה ביותר לשאילתה, אתה גם שולף את החלקים 46 ו-48. זה מגדיל את השימוש בטוקנים אך מפחית את הסיכון לתשובות לא שלמות. הפשרה תלויה במבנה התוכן שלך: מסמכים מובנים מאוד עם גבולות חלקים ברורים זקוקים לפחות הקשר מסביב מאשר טקסט נרטיבי זורם.

מגבלות אסימונים מוסיפות מגבלה נוספת. למודלים גדולים גדולים יש חלונות הקשר קבועים, ואתה צריך מקום להנחיית המערכת, לשאלת המשתמש ולתגובה שנוצרת. אם תשלוף 10 מקטעים בממוצע 500 אסימונים כל אחד, כבר צרכת 5,000 אסימונים לפני שה-LLM כותב מילה אחת. עקוב אחרי ספירת האסימונים המצטברת במהלך השליפה כדי להישאר במסגרת התקציב.

השאלה הבאה משלבת את הדפוסים הללו. הוא שולף את החלקים הדומים ביותר עם השכנים שלהם, עוקב אחרי טוקנים מצטברים, וכולל מטא-דאטה של ציטוטים:

WITH matched_chunks AS (
    -- Find the most similar chunks
    SELECT id, document_id, chunk_index, embedding <=> $1 AS distance
    FROM document_chunks
    ORDER BY embedding <=> $1
    LIMIT 3
),
context_window AS (
    -- Expand to include adjacent chunks for context
    SELECT DISTINCT dc.id, dc.document_id, dc.chunk_index, dc.content,
           dc.token_count, mc.distance
    FROM matched_chunks mc
    JOIN document_chunks dc ON dc.document_id = mc.document_id
        AND dc.chunk_index BETWEEN mc.chunk_index - 1 AND mc.chunk_index + 1
),
token_limited AS (
    -- Track cumulative tokens to respect LLM context limits
    SELECT cw.*, sd.title AS source_title, sd.source_url,
           SUM(cw.token_count) OVER (ORDER BY cw.distance, cw.chunk_index) AS cumulative_tokens
    FROM context_window cw
    JOIN source_documents sd ON cw.document_id = sd.id
)
SELECT id, content, source_title, source_url, distance
FROM token_limited
WHERE cumulative_tokens <= 3000
ORDER BY distance, chunk_index;

התאימו את הפרמטרים לפי מקרה השימוש שלכם. עבור עוזר מחקר משפטי שעונה על שאלות ספציפיות של סעיפים, תוכל למצוא חמישה התאמות ללא חלון הקשר. עבור בוט שירות לקוחות שעונה על שאלות פתוחות לגבי תיעוד מוצר, ייתכן שתמצא שלוש התאמות עם שני חלקים של הקשר מסביב כל אחד.

טיפול בצינורות קליטת מסמכים

מסמכים חדשים חייבים לעבור עיבוד לפני שהם ניתנים לחיפוש. צינור הקליטה מחלק מסמכים לחתיכות, יוצר הטמעות ומאחסן הכל ב-PostgreSQL.

האופן שבו אתה מחלק מסמכים משפיע ישירות על איכות השחזור. אסטרטגיית החלוקה הנכונה תלויה במבנה התוכן ובדפוסי השאילתות שלך:

  • חתיכות בגודל קבוע: חלק כל N תווים או טוקנים. גישה זו פשוטה ליישום ומניבה ספירות טוקנים צפויות, אך היא עלולה לקצר משפטים או פסקאות באמצע מחשבה. השתמש בקטעים בגודל קבוע כאשר לתוכן שלך אין גבולות מבניים ברורים או כשאתה צריך גדלים אחידים לתכנון תקציב טוקנים.

  • קטעים סמנטיים: מפצל בגבולות טבעיים כמו פסקאות, קטעים או משפטים. זה שומר על המשמעות בתוך כל חתיכה אך יוצר גדלים משתנים. השתמש בקטעים סמנטיים למסמכים מובנים שבהם הסעיפים מייצגים מחשבות שלמות, כמו סעיפים משפטיים, תיעוד API או ערכי שאלות נפוצות.

  • חפיפות חופפות: כלול טקסט מקטעים סמוכים כדי לשמור על הקשר בגבולות. לדוגמה, חפיפה של 200 תווים בין מקטעים של 1,000 תווים מבטיחה שמושגים החוצים גבולות מקטעים יופיעו לפחות במקטע אחד שלם. השתמש בחפיפה כאשר השאילתות שלך עשויות להתאים לתוכן קרוב לגבולות מקטעים.

בתרחיש עוזר מחקר משפטי, חלוקה סמנטית בגבולות פסקאות או סעיפים עובדת היטב כי הטקסט המשפטי מאורגן לסעיפים וטיעונים נפרדים. כל חתיכה מייצג מושג משפטי שלם שיכול לעמוד בפני עצמו בהקשר של ה-LLM.

לאחר שחילקת את המסמכים ויצרת הטמעות, הכנס אותם ביעילות באמצעות פעולות אצווה. עיצוב שתי טבלאות דורש הכנסת מסמך המקור תחילה כדי לקבל את מזהה שלו, ולאחר מכן הכנסת כל החלקים לשורה מרובת שורות INSERTאחת . גישה זו ממזערת נסיעות הלוך ושוב למסד הנתונים ושומרת על מערכת היחסים בין המסמך למקטע. רוב ממשקי ה-API להטמעה גם מקבלים מספר טקסטים לכל בקשה, כך שניתן ליצור הטמעות עבור חלקים שלמים של מסמך בקריאת API אחת לפני ההכנסה.

עדכוני מסמכים דורשים החלטה: האם אתה מעתיק מסמכים או מחליף אותם? ברוב יישומי RAG, החלפה פשוטה יותר. מחק את החלקים הקיימים (המגבלה ON DELETE CASCADE מטפלת בזה אוטומטית כשאתה מוחק את מסמך המקור), ואז מחזיר את התוכן המעודכן. גישה זו מבטיחה שתוצאות השליפה שלך תמיד משקפות את מצב המסמך הנוכחי. אם אתה צריך היסטוריית גרסאות, תוסיף עמודה version ותשמור source_documents חלקים ישנים לצד חדשים, תוך סינון לפי גרסה בזמן השאילה.

מדדי HNSW מטפלים במחיקות בצורה אלגנטית מבלי לבנות מחדש. אינדקסי IVFFlat עשויים להצטבר פיצועים לאחר שינויים משמעותיים, מה שידרוש בנייה מחדש תקופתית לשמירה על ביצועי השאילתה.

יישום שליפה עם ציטוטים

ציטוטים הופכים את RAG מקופסה שחורה לכלי מחקר שקוף. כאשר עוזר המחקר המשפטי מצטט סעיף חוזה, על עורך הדין לאמת את הצעת המחיר מול המסמך המקורי. ללא מטא-דאטה של ציטוטים, המשתמשים חייבים לסמוך על תוצרי ה-LLM באופן עיוור — סיכון משמעותי בהקשרים משפטיים, רפואיים או פיננסיים שבהם הדיוק הוא קריטי.

ציטוטים יעילים דורשים יותר מאשר רק תוכן קטעים. שאילתות השליפה שלך צריכות להחזיר את כותרת מסמך המקור, כתובת URL או מזהה מסמך, כותרת הסעיף ומספר העמוד כאשר זמינים. מטא-דאטה זה מאפשר ליישום שלך לציין ציטוטים שמשתמשים יכולים לעקוב אחריהם חזרה למקור. ציון המרחק גם עוזר: ייתכן שתציג ציטוטים בעלי ביטחון גבוה בצורה בולטת תוך סימון התאמות עם ביטחון נמוך לאימות משתמשים.

אתגר נפוץ מתעורר כאשר מספר מקטעים מאותו מסמך תואמים לשאילתה. אם החלקים 12, 15 ו-18 מתוך "תבנית הסכם עבודה" מופיעים כולם בתוצאות המובילות שלך, רישום אותם כשלושה ציטוטים נפרדים מטשטש את התגובה. במקום זאת, קבץ קטעים לפי מסמך מקור ומציג אותם כציטוט יחיד עם מספר קטעים רלוונטיים. גישה זו מפיקה פלט נקי יותר ועוזרת למשתמשים לראות את ההקשר המלא מכל מקור.

השאילתה הבאה מדגימה שליפת מסמכים מקובצת. הוא מדרג חלקים בתוך כל מסמך, מגביל לשלושה חלקים בכל מסמך כדי לא להעמיס על ההקשר, ומאגד את התוכן לעיצוב ציטוטים נקי יותר:

WITH ranked_chunks AS (
    SELECT
        dc.*,
        sd.title AS document_title,
        sd.source_url,
        dc.embedding <=> $1 AS distance,
        ROW_NUMBER() OVER (PARTITION BY dc.document_id ORDER BY dc.embedding <=> $1) AS rank_in_doc
    FROM document_chunks dc
    JOIN source_documents sd ON dc.document_id = sd.id
    WHERE dc.embedding <=> $1 < 0.5
)
SELECT
    document_id,
    document_title,
    source_url,
    array_agg(content ORDER BY chunk_index) AS chunks,
    MIN(distance) AS best_distance
FROM ranked_chunks
WHERE rank_in_doc <= 3
GROUP BY document_id, document_title, source_url
ORDER BY best_distance
LIMIT 5;

האפליקציה שלך יכולה לעצב זאת לציטוטים ידידותיים למשתמש:

"המעסיק רשאי לסיים הסכם זה בהודעה מוקדמת של 30 יום." — תבנית הסכם עבודה, סעיף 4.2, עמוד 3

הערכה ושיפור איכות השליפה

איכות השליפה קובעת את יעילות RAG. שליפה לקויה מובילה לייצור גרוע, בלי קשר ליכולת ה-LLM שלך. אם הרטריבר מחזיר קטעים לא רלוונטיים, ה-LLM מבזבז קיבולת חלון הקשר על טקסט חסר תועלת. אם הרטריבר מפספס חלקים רלוונטיים, למודל השפה הגדול חסר את המידע הדרוש כדי לענות נכון. מדידת איכות השליפה בנפרד מאיכות הייצור עוזרת לך לאבחן היכן צינור ה-RAG שלך זקוק לשיפור.

שלושה מדדים לוכדים היבטים שונים של ביצועי השחזור:

  • דיוק: החלק של החלקים שנאספו באמת רלוונטיים. דיוק נמוך פירושו שהמודל מקבל הקשר לא רלוונטי שעלול לבלבל אותו או לגרום לו לייצר מידע שגוי. לשפר את הדיוק על ידי הידוק ספי המרחק או הפחתת מספר החלקים שנשלפו.

  • זיכרון: החלק של החלקים הרלוונטיים שנשלפים. זיכרון נמוך פירושו שהמודל מפספס מידע חשוב, מה שמוביל לתשובות לא שלמות או שגויות. שיפור השליפה על ידי שחרור ספי מרחק, הגדלת מספר הקטעים שנשלפו, או התאמת פרמטרי אינדקס כמו ef_search.

  • דירוג ממוצע הדדי (MRR): כמה גבוה מופיעה התוצאה הרלוונטית הראשונה ברשימת הדירוגים. MRR חשוב כי מודלים גדולים יותר שוקלים הקשר מוקדם יותר, ומשתמשים שסורקים ציטוטים מבחינים בתוצאות המובילות ראשונים. שפרו את ה-MRR על ידי שיפור מודל ההטמעה או עיבוד מקדים לשאילתות.

כדי למדוד מדדים אלו, צריך מאגר נתונים להערכה: סט של שאילתות מייצגות בשילוב עם שיפוטים אנושיים לגבי אילו חלקים רלוונטיים. בניית מאגר הנתונים הזה דורשת מאמץ מראש — מישהו חייב להריץ שאילתות דגימות ולתייג את התוצאות — אבל זה משתלם בכך שאתה מאפשר לך לבצע שיפורים מונחי נתונים במקום לנחש. תתחיל עם 20-50 שאילתות שמייצגות את סוגי השאלות שהמשתמשים שלך שואלים בפועל. לכל שאילתה, שלפו את 10-20 החלקים הראשונים ותנו למומחה דומיין לדרג את הרלוונטיות שלהם בקנה מידה פשוט (לא רלוונטי, רלוונטי במידה מסוימת, רלוונטי מאוד).

עם ההערכה הזו, תוכל למדוד דיוק ושחזור בנקודות חיתוך שונות (precision@5, recall@10) ולעקוב כיצד שינויים בצינור שלך משפיעים על מדדים אלו. הריץ את שאילתות השליפה שלך מול קבוצת ההערכות, השווה את התוצאות לשיפוטים האנושיים, וחשב את המדדים. רוב הצוותים מאוטומטיים את זה לסקריפט ניקוד שפועל בכל פעם שהם משנים אסטרטגיות חתיכה, מודלים מוטמעים או פרמטרי אינדקס.

כאשר המדדים יורדים מתחת ליעד, יש לנסות באופן שיטתי פרמטרים שמשפיעים על הפשרה בין דיוק לזיכרון:

  • גודל חתיכה: קטעים קטנים משפרים את הדיוק על ידי החזרת תוכן ממוקד יותר, אך עלולים לפגוע בזיכרון על ידי פיצול מידע רלוונטי על פני מספר קטעים. חלקים גדולים יותר משפרים את הזיכרון אך מדללים את הרלוונטיות.

  • חפיפה בין חלקים: חפיפה נוספת שומרת על הקשר מעבר לגבולות, ומסייעת לשאילתות שמתאימות לתוכן קרוב לקצוות הקטעים. עם זאת, חפיפה מגדילה את דרישות האחסון ועלולה להחזיר תוכן מיותר.

  • מודל הטמעה: מודלים שונים לוכדים קשרים סמנטיים שונים. מודל המבוסס על טקסט משפטי עשוי להצליח יותר ממודל כללי למחקר משפטי. שקול מודלים ספציפיים לתחום או למודלים מכוונים אם מודלים כלליים אינם מתפקדים טוב.

  • פרמטרי אינדקס: Higher ef_search (HNSW) או probes (IVFFlat) משפרים את השליפה על ידי חיפוש מועמדים נוספים, במחיר של השהיית השאילתה. תתחיל עם פרמטרים ברירת מחדל ותגדיל רק אם הקריאה אינה מספקת.

  • ספי מרחק: ספים צמודים יותר משפרים את הדיוק על ידי הוצאת התאמות שוליות. ספים רופפים משפרים את הקריאה על ידי הכללת יותר מועמדים. השתמש במאגר הנתונים של ההערכה שלך כדי למצוא את הסף שמאזן את שני המדדים עבור מקרה השימוש שלך.