Query Planning לסוכני מידע — פירוק שאלה למספר מקורות וכלים

מאת צוות מדיה דיל · 12.08.2026 · AI Retrieval · 5 דק׳

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

מנהל מוצר שאל סוכן AI פנימי: "כמה זמן לוקח בממוצע לפתור באג P1, ואיך זה השתנה מאז ששינינו את תהליך ה-triage בחודש שעבר?". שאלה אחת, אבל בפועל היא מכילה לפחות שלוש תת-שאלות נפרדות: שליפת נתוני זמן-פתרון ממערכת ה-ticketing (Jira, SQL); זיהוי התאריך המדויק שבו שונה תהליך ה-triage (מסמך פנימי, retrieval טקסטואלי); וחישוב השוואתי בין שתי תקופות (לוגיקה, לא retrieval כלל). סוכן שמנסה לענות על זה בקריאת retrieval יחידה — "לוקח embedding של השאלה כולה ומחפש" — פשוט לא ימצא תשובה טובה, כי אף מקור בודד לא מכיל את כל המידע הדרוש. זה בדיוק התפקיד של Query Planning: לפרק שאלה מורכבת לתת-שאלות, לשייך כל אחת למקור/כלי המתאים, ולבנות תוכנית ביצוע ברורה לפני שמריצים משהו.

למה פירוק לפני ביצוע, לא תוך כדי

יש הבדל ארכיטקטוני משמעותי בין Query Planning לבין Agentic Search שמתואר במדריך נפרד: Agentic Search מבצע לולאה איטרטיבית שבה כל שלב תלוי בתוצאת השלב הקודם ומתגלה תוך כדי ריצה; Query Planning בונה תוכנית מלאה מראש, כגרף תלויות (DAG) של תת-שאלות, ורק אז מתחיל לבצע. הגישה השנייה עובדת טוב כשמבנה הבעיה ניתן לחיזוי — כמו בדוגמה למעלה, ששלושת מרכיביה ברורים מניתוח השאלה עצמה בלי צורך "לגלות" אותם תוך כדי חיפוש. היא גם הרבה יותר צפויה וזולה לבדיקה: אפשר לכתוב unit test על שלב הפירוק בנפרד משלב הביצוע, ולוודא שהמערכת מפרקת נכון עשרות ניסוחי שאלה שונים לפני שמריצים dollar אחד על retrieval אמיתי.

המחיר הוא גמישות: אם ביצוע תת-שאלה אחת חושף שהתוכנית המקורית שגויה (למשל, מתברר שתהליך ה-triage השתנה פעמיים ולא פעם אחת), Query Planning טהור לא מתאים את עצמו — צריך שכבת fallback שמזהה סטייה ומפעילה תכנון מחדש חלקי. בפרודקשן, רוב המערכות הטובות משלבות את שתי הגישות: תכנון ראשוני שמכסה את מבנה השאלה הצפוי, עם נקודת בדיקה אחרי כל תת-שאלה שמחליטה אם להמשיך לפי התוכנית או לסטות ממנה.

מבנה תוכנית: DAG של תת-שאלות

שכבת התכנון בפועל היא קריאת LLM ממוקדת שמקבלת את השאלה המקורית ורשימת הכלים/מקורות הזמינים (עם תיאור קצר של מה כל אחד מכסה), ומחזירה מבנה JSON: רשימת תת-שאלות, לכל אחת מקור מתאים, ותלויות ביניהן (תת-שאלה ג' תלויה בתוצאת תת-שאלה א'). לדוגמה עבור השאלה שפתחנו איתה:

  • תת-שאלה 1: "זמן ממוצע לפתרון P1 בחודש הנוכחי" → מקור: SQL על מסד ה-ticketing, ללא תלויות
  • תת-שאלה 2: "מתי שונה תהליך ה-triage" → מקור: חיפוש היברידי במסמכי תהליכים פנימיים, ללא תלויות
  • תת-שאלה 3: "זמן ממוצע לפתרון P1 לפני תאריך X" → מקור: אותה שאילתת SQL כמו 1, אבל עם פילטר תאריך שתלוי בתוצאת תת-שאלה 2

תת-שאלות 1 ו-2 יכולות לרוץ במקביל (fan-out), ותת-שאלה 3 ממתינה לתוצאת תת-שאלה 2. זו בדיוק הסיבה שמבנה DAG עדיף על רשימה סדרתית פשוטה — הוא מאפשר להריץ כמה שיותר תת-שאלות במקביל, מה שמקצר משמעותית את זמן התגובה הכולל בהשוואה לביצוע טורי נאיבי.

ניתוב לכלי הנכון: לא תמיד retrieval

שכבת התכנון צריכה להכיר לא רק "איפה" לחפש אלא גם מתי בכלל לא לחפש — תת-שאלות מסוימות (כמו החישוב ההשוואתי בדוגמה) הן לוגיקה טהורה שרצה על תוצאות שכבר נשלפו, לא retrieval נוסף. פירוק תת-שאלות שגוי שמנסה "לחפש" תשובה לחישוב מתמטי הוא טעות נפוצה: המודל עלול לנסות למצוא מסמך שמכיל את התשובה המספרית במקום לחשב אותה בעצמו מהנתונים שכבר נאספו. תכנון טוב מבחין במפורש בין tool_type: retrieval, computation, aggregation, ומנתב כל תת-שאלה בהתאם. שילוב הגישה הזו עם המקורות ההטרוגניים (SQL, גרף, לקסיקלי, סמנטי) שמתוארים במדריך Hybrid Retrieval 2.0 נותן לשכבת התכנון את מלוא הארסנל שממנו לבחור.

כשתת-שאלה נכשלת: טיפול בכשלים בתוך התוכנית

תוכנית שנבנתה מראש חייבת גם להתמודד עם המציאות שבה תת-שאלה בודדת נכשלת — API חיצוני מחזיר שגיאה, שאילתת SQL חורגת מ-timeout, או חיפוש טקסטואלי לא מוצא שום תוצאה רלוונטית. הגישה הנאיבית היא לתת לכשלון בודד להפיל את כל התוכנית, אבל זה מיותר: אם תת-שאלה 3 תלויה בתוצאת תת-שאלה 2 שנכשלה, רק הענף הזה נפגע — תת-שאלה 1 שרצה במקביל ולא תלויה בכלום עדיין מספקת ערך. ארכיטקטורת DAG תומכת בטבעיות בטיפול חלקי כזה: כל צומת בתוכנית מסומן כ-succeeded, failed או skipped (כשהוא תלוי בצומת שנכשל), ושלב המיזוג הסופי מקבל את כל הסטטוסים האלה ובונה תשובה שמשקפת בכנות אילו חלקים הצליחו.

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

מקרה קצה חשוב נוסף הוא תת-שאלה שמחזירה תוצאה ריקה ולא שגיאה — למשל, שאילתת SQL שרצה בהצלחה אך לא מצאה שורות תואמות. זה לא כשל טכני, אבל דורש טיפול שונה מתוצאה מלאה: המערכת צריכה להבחין בין "לא נמצא מידע כי הוא לא קיים" (תשובה לגיטימית ומלאה — למשל "אין באגים פתוחים מסוג P1 החודש") לבין "לא נמצא מידע כי הפילטר או השאילתה שגויים" (תקלה שדורשת ניסוח מחדש). הבחנה כזו קשה לאוטומציה מלאה, ולכן מערכות בשלות בדרך כלל מוסיפות שכבת sanity check פשוטה — לדוגמה, אם תוצאה ריקה חוזרת בעקביות עבור סוג שאילתה מסוים על פני מאות ריצות, זה סימן חזק לבעיה בניסוח ולא במציאות עצמה, ושווה לחקור את זה בנפרד מהניטור השוטף.

מיזוג תוצאות לתשובה אחת

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

תגיות: Query Planning · Query Decomposition · DAG · AI Agents · Multi-Tool Retrieval · Orchestration

← חזרה לבלוג · צור קשר