יצירת Agentic RAG עבור יישומי Text-to-SQL

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

החזון מאחורי RAG אגנטי עבור טקסט ל-SQL

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

  1. מחלקים את המשימות לכלים קטנים יותר וניהוליים בתוך סוכן
  2. משפרים את הדיוק על ידי הקצאת משימות לכלים מיוחדים
  3. מגבירים את השקיפות על ידי מעקב אחר ההיגיון וזרימת העבודה של כל כלי
  4. מפשטים את הרחבת היקף העבודה וניפוי השגיאות באמצעות עיצוב מודולרי

בואו נדבר על איך הכלי הזה עובד ועל התפקיד של כל רכיב בהפיכת שאלות המשתמש לשאילתות SQL מדויקות.

סקירה כללית של הארכיטקטורה

המבנה כולל סוכן המנצל כלים בתוך זרימת העבודה של טקסט ל-SQL. התהליך ניתן לסיכום כך:

שאלת משתמש → כלי שינוי שאלות → כלי הפניית דוגמאות מועטות → כלי חיפוש היברידי → כלי דירוג מחדש → כלי שליפת טבלאות → כלי בניית פקודות → כלי ביצוע LLM → כלי ביצוע SQL → פלט סופי

1. כלי שינוי שאלות של משתמש

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

שיפורים

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

דוגמה

קלט: "הראה מכירות לאחרונה MTD."

שאלת שינית: "שלוף נתוני מכירות עבור 7 הימים האחרונים (מהחודש עד כה)."

Python

 

from datetime import date, timedelta

def transform_query(user_query):
    # טיפול בהתייחסויות זמניות פתוחות
    today = date.today()
    transformations = {
        "as of today": f"up to {today}",
        "till now": f"up to {today}",
        "recent": "last 7 days",
        "last week": f"from {today - timedelta(days=7)} to {today}",
    }
    
    for key, value in transformations.items():
        user_query = user_query.replace(key, value)

    # מיפוי קיצורים נפוצים
    abbreviations = {
        "MTD": "Month to Date",
        "YTD": "Year to Date",
    }
    for abbr, full_form in abbreviations.items():
        user_query = user_query.replace(abbr, full_form)

    return user_query

query_transform_tool = Tool(
    name="Query Transformer",
    func=transform_query,
    description="Refines user queries for clarity and specificity, handles abbreviations and open-ended terms."
)

2. כלי הפניית דוגמאות מועטות

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

זרימת עבודה לדוגמה

1. שאלה קלט: "הראה לי את מכירות הכוללות לפי מוצר עבור 7 הימים."

2. תבניות מוגדרות מראש:

  • "הראה מכירות מחולקות לפי אזור." → דוגמת SQL; SELECT region, SUM(sales) …
  • "הצג מכירות כוללות לפי מוצר." → דוגמה SQL; SELECT product_name, SUM(sales) …

3. שאלה הכי דומה: "הצג מכירות כוללות לפי מוצר."

4. דוגמה לפלט SQL: SELECT product_name, SUM(sales) FROM …

Python

 

from langchain.chat_models import ChatOpenAI

llm = ChatOpenAI(model="gpt-4")

predefined_examples = {
    "Show sales grouped by region": "SELECT region, SUM(sales) FROM sales_data GROUP BY region;",
    "Show total sales by product": "SELECT product_name, SUM(sales) FROM sales_data GROUP BY product_name;",
}

def find_similar_question(user_query):
    prompt = "Find the most similar question type for the following user query:\n"
    prompt += f"User Query: {user_query}\n\nOptions:\n"
    for example in predefined_examples.keys():
        prompt += f"- {example}\n"
    prompt += "\nRespond with the closest match."

    response = llm.call_as_function(prompt)
    most_similar = response['content']
    return predefined_examples.get(most_similar, "")

few_shot_tool = Tool(
    name="Few-Shot Prompting",
    func=find_similar_question,
    description="Finds the most similar question type using an additional LLM call and retrieves the corresponding example SQL."
)

3. כלי חיפוש היברידי

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

כיצד הכל מתמזג יחד?

מיפוי טבלת מילות מפתח

גישה זו מבצעת מיפוי של הטבלאות למילות המפתח שנמצאות בשאילתה. לדוגמה:

  • המופע של "מכירות" מפיק ברשימה קצרה של טבלת המכירות.
  • המופע של "מוצר" מפיק ברשימה קצרה של טבלת המוצרים. 

מיפוי חפיפת מילות מפתח (BM25)

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

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

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

נתון:

  • sales_data: מכיל מונחים כמו "מכירות," "תאריך," "מוצר."
  • products: מכיל מונחים כמו "מוצר," "קטגוריה."
  • orders: מכיל מונחים כמו "הזמנה," "תאריך," "לקוח."
  • financials: מכיל מונחים כמו "רווח," "רווחה," "הוצאה."

שאילתת משתמש: "הצג סכום מכירות על פי מוצר."

  • זהה מונחים בשאילתת המשתמש: ["מכירות," "מוצר"].
  • מיין כל מסמך (בהתבסס על תדירות וקשריות של המונחים הללו) בטבלת מסד הנתונים.

קשריות של מסמכים:

  • sales: קשריות גבוהה עקב "מכירות" ו"מוצר"
  • products: קשריות גבוהה עקב "מוצר." 
  • orders: קשריות נמוכה עקב הופעה של "מכירות" בלבד.
  • financials: לא רלוונטי.

פלט:

רשימה ממוינת: [products, sales_data, orders, financials]

חיפוש סמנטי

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

פיזור דירוג ריצפי

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

מיזוג דירוגים הפוך (RRF) משלב בין BM25 לחיפוש סמנטי:

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

נוסחת RRF:

RRF(d) = Σ(r ∈ R) 1 / (k + r(d))

איפה:

  • d הוא מסמך
  • R הוא קבוצת הדירוגים (שיטות חיפוש)
  • k הוא קבוע (בדרך כלל 60)
  • r(d) הוא הדירוג של המסמך d בשיטת החיפוש r

דוגמה שלב-אחר-שלב

נתוני קלט.

1. תוצאות דירוג BM25:

  • מוצרים (דירוג 1)
  • נתוני מכירות (דירוג 2)
  • הזמנות (דירוג 3)

2. תוצאות דירוג חיפוש סמנטי:

  • נתוני מכירות (דירוג 1)
  • כספים (דירוג 2)
  • מוצרים (דירוג 3)

מיזוג שלב-אחר-שלב

לכל טבלה, חשב את הניקוד:

1. נתוני מכירות

  • דירוג BM25 = 2, דירוג סמנטי = 1
  • ניקוד RRF = (1/60+2 ) + (1/60+1) = 0.03252

2. מוצרים

  • דירוג BM25 = 1, דירוג סמנטי = 3
  • ניקוד RRF = (1/60+1) + (1/60+3)= .03226

3. הזמנות

  • BM25 דירוג = 3, דירוג סמנטי = לא דורג
  • ניקוד RRF = (1/60+3)= 0.01587

4. פיננסים

  • דירוג BM25 = לא דורג, דירוג סמנטי = 2
  • ניקוד RRF = (1/60+2)=0.01613

5. מיין לפי ניקוד RRF

  • sales_data (הניקוד הגבוה ביותר עקב דירוג גבוה בחיפוש סמנטי).
  • products (ניקוד גבוה מBM25).
  • orders (רלוונטיות נמוכה בכלל).
  • financials (התנגדות מוגבלת).

פלט סופי: ['sales_data', 'products,' 'financials,' 'orders']

טבלאות המופיעות באמצעות המיפוי של טבלת מילות מפתח תמיד יכללו.

Python

 

from rank_bm25 import BM25Okapi

def hybrid_search(query):
    # מיפוי מבוסס מילות מפתח
    keyword_to_table = {
        "sales": "sales_data",
        "product": "products",
    }
    keyword_results = [table for keyword, table in keyword_to_table.items() if keyword in query.lower()]

    # חיפוש BM25
    bm25 = BM25Okapi(["sales_data", "products", "orders", "financials"])
    bm25_results = bm25.get_top_n(query.split(), bm25.corpus, n=5)

    # חיפוש סמנטי
    semantic_results = vector_store.similarity_search(query, k=5)

    # רקורסיה של דירוגים
    def reciprocal_rank_fusion(results):
        rank_map = {}
        for rank, table in enumerate(results):
            rank_map[table] = rank_map.get(table, 0) + 1 / (1 + rank)
        return sorted(rank_map, key=rank_map.get, reverse=True)

    combined_results = reciprocal_rank_fusion(bm25_results + semantic_results)

    return list(set(keyword_results + combined_results))

hybrid_search_tool = Tool(
    name="Hybrid Search",
    func=hybrid_search,
    description="Combines keyword mapping, BM25, and semantic search with RRF for table retrieval."
)

4. כלי ריסורינג

כלי זה מבטיח שהטבלאות הרלוונטיות ביותר יועדפו.

דוגמה

  • טבלאות קלט: ["sales_data," "products," "financials"]
  • לוגיקת ריסורינג
    • לכל טבלה, חישוב ניקוד רלוונטי על ידי חיבור בין השאילתה ותיאור הטבלה.
    • מיון לפי ניקוד רלוונטי.
  • פלט: ["sales_data," "products"]

עוד קצת על הלוגיקת ה-Re-ranking:

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

  • קלט זוגי. השאילתת וכל תיאור של טבלה מוצמדים ומועברים כקלט ל־cross-encoder.
  • קידוד משותף. בניגוד לקידודים נפרדים (לדוגמה, bi-encoders), ה־cross-encoder מקודד את הזוג ביחד, מאפשר לו לכולל טוב יותר את ההקשר והתלות בין השאילתת לתיאור הטבלה.
  • דירוג. המודל מוציא ציון רלוונטיות עבור כל זוג, מציין כמה הטבלה מתאימה לשאילתת.
Python

 

from transformers import pipeline

reranker = pipeline("text-classification", model="cross-encoder/ms-marco-TinyBERT-L-2")

def re_rank_context(query, results):
    scores = [(doc, reranker(query + " " + doc)[0]['score']) for doc in results]
    return [doc for doc, score in sorted(scores, key=lambda x: x[1], reverse=True)]

re_rank_tool = Tool(
    name="Re-Ranker",
    func=re_rank_context,
    description="Re-ranks the retrieved tables based on relevance to the query."
)

5. כלי בניית העבר

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

נניח שאתה מי שמיומן ביצירת שאילתות SQL. יצור שאילתת SQL ל: אחזור מכירות סה"כ מקובצות לפי מוצר לשבעת הימים האחרונים.

טבלאות רלוונטיות:

  1. sales_data: מכילה עמודות [מכירות, תאריך, product_id].
  2. products: מכילה עמודות [product_id, שם_מוצר].

דוגמה ל-SQL:

Plain Text

 

SELECT product_name, SUM(sales) FROM sales_data JOIN products ON sales_data.product_id = products.product_id GROUP BY product_name;

היבטים עתידיים

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

  1. סוכנים מיוחדים לאחזור הקשר. סוכנים נפרדים לחיפושים סמנטיים ולחיפושים לפי מילות מפתח.
  2. סוכנים ספציפיים למשימות. סוכנים מתמחים באימות SQL או באופטימיזציה.
  3. שיתוף פעולה בין סוכנים. שימוש בסוכן תיאום כדי לנהל העברת משימות.

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

מסקנה

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

Source:
https://dzone.com/articles/creating-an-agentic-rag-for-text-to-sql-applications