Agent Retry Architecture — Retry, Backoff ו-Failure Recovery
מאת צוות מדיה דיל · 09.08.2026 · AI · 12 דק׳
לנסות שוב זה קל לומר וקשה לעשות נכון. מדריך טכני על עיצוב retry, exponential backoff, idempotency ו-circuit breakers בסוכני AI שקוראים לכלים חיצוניים ולמודלי שפה.
סוכן AI קורא ל-API של ספק תשלומים, מקבל timeout אחרי 30 שניות. מה קורה עכשיו? אם התשובה היא "ננסה שוב מיד" — יש סיכוי אמיתי שהחיוב בפועל כבר עבר בצד השרת, וניסיון חוזר יחייב את הלקוח פעמיים. אם התשובה היא "נוותר" — יש סיכוי שזה היה כשל רגעי ברשת, וויתור מיותר לגמרי. Retry architecture נכון בסוכני AI הוא לא "עטפו הכל ב-try/catch עם לולאה", אלא מערכת החלטות מדודה: מתי לנסות שוב, כמה זמן לחכות, איך לוודא שניסיון חוזר לא יוצר נזק כפול, ומתי להפסיק ולהעביר לטיפול אנושי.
למה סוכני AI נכשלים — Transient מול Permanent Failures
לא כל כשל שווה. כשל זמני (transient) — timeout ברשת, rate limit של 429, שגיאת 503 זמנית מהשרת — צפוי להיעלם אם מנסים שוב אחרי המתנה. כשל קבוע (permanent) — 400 Bad Request כי הקלט לא תקין, 401 כי ה-API key לא תקף, או תשובת מודל שמסרבת לבצע את המשימה מסיבה עקרונית — לא ייעלם בניסיון חוזר, ורק יבזבז זמן וכסף. עיצוב retry טוב מתחיל בסיווג נכון של סוגי הכשלים, ולא בטיפול גורף באותו אופן בכולם.
- 429 / Rate Limit — retry עם backoff, לרוב עם כיבוד header כמו
Retry-After. - 5xx / Timeout — retry עם exponential backoff.
- 4xx (למעט 429) — לרוב לא ניתן לתיקון ע"י retry; דורש תיקון קלט או escalation.
- תשובת מודל לא תקינה (JSON שבור, hallucination) — retry עם prompt מתוקן, לא חזרה גולמית זהה (ראו Structured Outputs).
Retry עם Backoff — איך עושים את זה נכון
Exponential backoff — הגדלת זמן ההמתנה בין ניסיונות באופן מעריכי (למשל 1s, 2s, 4s, 8s) — מונע מצב שבו סוכן שנתקל בכשל זמני מפציץ את השירות החיצוני בניסיונות חוזרים ומחמיר את המצב (שהוא בעצמו יכול לגרום ל-rate limiting נוסף). תוספת חשובה: jitter — רעש אקראי קטן שנוסף לזמן ההמתנה — כדי שכשהרבה instances של הסוכן נכשלים בו-זמנית (למשל אחרי הפסקת שירות רחבה), הם לא ינסו שוב כולם באותו רגע בדיוק ויציפו את השירות ברגע שהוא חוזר.
function backoffDelay(attempt, base = 1000, max = 30000) {
const exp = Math.min(max, base * 2 ** attempt);
const jitter = Math.random() * exp * 0.3;
return exp - jitter / 2 + jitter * Math.random();
}
// attempt 0 -> ~1s, attempt 3 -> ~8s, attempt 5+ -> נתקע סביב max
חשוב לא פחות: מספר ניסיונות מקסימלי. ללא תקרה, סוכן יכול להיתקע בלולאת retry שצוברת עלות טוקנים ו-latency בלי סוף. תקרה טיפוסית היא 3-5 ניסיונות, ולאחריה — escalation, לא ניסיון נוסף.
Idempotency — התנאי ההכרחי ל-Retry בטוח
retry בטוח רק אם הפעולה אידמפוטנטית — כלומר, ביצוע שלה פעמיים נותן את אותה תוצאה כמו ביצוע פעם אחת. פעולות קריאה (GET, שאילתת חיפוש) אידמפוטנטיות מטבען. פעולות כתיבה (יצירת הזמנה, חיוב, שליחת מייל) לא — אלא אם מוסיפים idempotency key: מזהה ייחודי שנשלח יחד עם הבקשה, שהשרת החיצוני (או שכבת הביניים שלכם) משתמש בו כדי לזהות "כבר עיבדתי את זה" ולהחזיר את אותה תוצאה בלי לבצע את הפעולה שוב.
כשקוראים ל-API חיצוני שלא תומך ב-idempotency keys באופן טבעי (לא כל ה-APIs תומכים), חייבים לבנות שכבת הגנה בצד שלכם: לפני ביצוע פעולה בלתי-הפיכה, בדקו אם כבר יש רשומה שמעידה שהיא בוצעה (למשל לפי task_id + step_id), ורק אם אין — בצעו ורשמו מיד. זה קשור הדוק לעיצוב ה-checkpoints וליישום Durable Execution.
Circuit Breakers ו-Dead Letter Queues
Circuit Breaker — כשאחוז הכשלים מול שירות חיצוני עובר סף מסוים בפרק זמן קצר, "פותחים מעגל" ומפסיקים לנסות זמנית, במקום להמשיך לספוג כשלים ולבזבז זמן וכסף. אחרי פרק זמן, מנסים "חצי-פתוח" — כמה בקשות בדיקה — ואם הן מצליחות, סוגרים חזרה למצב רגיל. זה מגן גם על המערכת שלכם וגם על השירות החיצוני מ-cascading failures.
Dead Letter Queue (DLQ) — כשמשימה נכשלת אחרי כל הניסיונות המותרים, היא לא נעלמת — היא עוברת לתור ייעודי לטיפול, שם היא ממתינה לבדיקה אנושית או ללוגיקת recovery מיוחדת. בלי DLQ, כשלים "נופלים על הרצפה" בשקט וקשה מאוד לגלות בדיעבד כמה משימות נכשלו ומדוע.
Retry ברמת הכלי מול ברמת המשימה
החלטה ארכיטקטונית חשובה: איפה למקם את לוגיקת ה-retry? retry ברמת הכלי (tool-level) — כל קריאת כלי בודדת עוטפת את עצמה ב-retry עצמאי, שקוף לשאר המערכת. פשוט לממש, אבל "עיוור" להקשר הרחב — אם הכלי נכשל 3 פעמים ואז הצליח, שאר הסוכן לא יודע שהיה שם קושי. retry ברמת המשימה (task-level) — הסוכן עצמו, כשצעד נכשל לגמרי, מחליט האם לנסות גישה אחרת (כלי אחר, פרומפט שונה) או לדווח כשל למעלה. זה גמיש יותר אך דורש שהסוכן "יידע" שהיה כשל ויוכל להחליט מה לעשות איתו — קשור ישירות ל-Agent Reflection.
בפרקטיקה, המערכות היציבות ביותר משלבות את שתיהן: retry אוטומטי קצר-טווח ברמת הכלי לכשלים זמניים ברורים (timeout, 503), והעברה למעלה לרמת המשימה כשהכשל חוזר על עצמו או שהוא מסוג שדורש שינוי אסטרטגיה.
דוגמה מהשטח
סוכן שמייצא דוחות ל-CRM חיצוני: כל בקשת יצוא נושאת idempotency key ייחודי (שילוב של task_id ומספר batch). קריאה ראשונה נכשלת ב-timeout אחרי שהשרת החיצוני כבר קיבל וטיפל בבקשה. retry עם אותו idempotency key מגיע לשרת, שמזהה שכבר טופל, ומחזיר את התוצאה השמורה — בלי ליצור רשומה כפולה ב-CRM. בלי idempotency key, ה-retry היה יוצר דוח כפול בכל פעם שה-timeout קורה אחרי שהעיבוד בפועל כבר הצליח.
טעויות נפוצות
- retry בלי backoff — מציף שירות שכבר במצוקה ומחמיר את הכשל במקום לפתור אותו.
- retry על כל שגיאה, כולל שגיאות permanent — מבזבז זמן וכסף על משהו שלא ישתנה.
- אין תקרת ניסיונות — לולאה שיכולה להימשך בלי סוף.
- retry על side effects לא-אידמפוטנטיים בלי idempotency key — הסכנה המסוכנת ביותר, כי היא יוצרת נזק עסקי אמיתי (חיוב כפול, מייל כפול) לא רק בזבוז משאבים.
תיעוד ומעקב אחר היסטוריית Retry
כל ניסיון retry שווה תיעוד — לא רק ההצלחה הסופית. מבלי לוג מפורט של כל ניסיון (מתי, למה נכשל, כמה זמן חיכה עד הניסיון הבא), קשה מאוד לאבחן דפוסי כשל שיטתיים: האם שירות חיצוני מסוים נכשל בעקביות בשעות מסוימות ביום? האם סוג בקשה מסוים מצריך יותר ניסיונות ממוצע מסוגים אחרים? נתונים כאלה, שנאספים באופן שוטף כחלק מלוגיקת ה-retry עצמה, מזינים החלטות תשתיתיות עתידיות — למשל, מתי כדאי להחליף ספק, להוסיף circuit breaker חדש, או לשנות SLA מול צד שלישי. בלי תיעוד שיטתי, כל השאלות האלה נשארות בגדר ניחוש.
Retry ו-Timeouts — ההגדרה שקל לפספס
לפני שמדברים על retry בכלל, יש שאלה בסיסית שנוטים להזניח: מה ה-timeout הנכון לקריאה שממתינים לה? timeout קצר מדי גורם ל-retry מיותר על קריאות שבסך הכל היו איטיות אבל היו מצליחות אם היו ממתינים עוד קצת — מה שמכפיל עומס בלי סיבה אמיתית. timeout ארוך מדי גורם למשאבים (worker threads, connections) להיתקע בהמתנה ארוכה במקום להתפנות לעבודה אחרת, ומעכב את זיהוי הכשל בפועל. הפתרון הנכון תלוי במאפייני הקריאה הספציפית — timeout לקריאת מודל שפה (שיכולה לקחת עשרות שניות באופן לגיטימי, בטח עם streaming) שונה מהותית מ-timeout לקריאת API פנימי מהיר. הגדרת timeouts שונים לפי סוג הקריאה, ולא ערך גלובלי אחיד לכל המערכת, היא צעד ראשון הכרחי לפני שבכלל מגיעים לעיצוב לוגיקת ה-retry עצמה.
Retry על שגיאות מודל שפה — מקרה מיוחד
retry על קריאה למודל שפה שונה מהותית מ-retry על API רגיל, כי "כשל" לא תמיד אומר שגיאת HTTP — לפעמים המודל מחזיר 200 OK עם תשובה שפשוט לא תקינה: JSON שלא נסגר, שדה שחסר, תשובה שסוטה לגמרי מהמבוקש. סוג הכשל הזה דורש retry אחר: לא סתם לשלוח את אותו prompt שוב (שעלול להחזיר את אותה תוצאה שגויה, כי אין שום דבר שהשתנה), אלא retry עם prompt מתוקן שכולל את הבעיה הספציפית שזוהתה — למשל "התשובה הקודמת לא הייתה JSON תקין, הנה השגיאה המדויקת: [...]". זה קשור ישירות ל-Structured Outputs ול-Agent Reflection — הלולאה שם מתוארת בעצם היא סוג של retry "חכם" שמשתמש בהקשר הכשל כדי לשפר את הניסיון הבא, לא רק לחזור על עצמו.
נקודה נוספת חשובה: rate limiting (429) ממודל שפה לרוב מגיע עם header שמציין בדיוק כמה זמן לחכות (retry-after) — כיבוד ה-header הזה עדיף על פני backoff גנרי, כי הוא משקף את המצב האמיתי בצד ה-provider, לא ניחוש.
עלות תפעולית של לוגיקת Retry לא-נכונה
שווה לכמת את זה: מערכת עם retry אגרסיבי מדי (בלי backoff, בלי תקרה) לא רק "מבזבזת קריאות" — היא יכולה לגרום ל-rate limiting נוסף שפוגע בכל שאר התעבורה למודל, ליצור עומס מיותר על שירותים חיצוניים שגורם להם להחזיר עוד יותר שגיאות (אפקט מדרדר), ולנפח חשבונית API בצורה לא-פרופורציונלית לערך שמתקבל. מצד שני, retry שמרני מדי (בלי retry כלל, או תקרה נמוכה מדי) הופך כשלים זמניים חולפים לכשלים תפעוליים אמיתיים שדורשים התערבות אנושית מיותרת. האיזון הנכון נמדד באמפיריה, לא בניחוש — מעקב אחר שיעור ההצלחה בכל ניסיון retry נותן תמונה ברורה איפה שווה להוסיף ניסיון נוסף ואיפה זה כבר לא עוזר.
בניית שכבת retry מוצקה היא אחד ההשקעות הכי משתלמות באמינות מערכת AI — ולעיתים קרובות הנושא הכי פחות מקבל תשומת לב עד שקורית תקרית. מדיה דיל בונה תשתיות אמינות כאלה ללקוחות — נשמח לדבר בוואטסאפ, או קראו על תשתית הפרודקשן שלנו.
שאלות נפוצות
כמה ניסיונות retry זה סביר?
3-5 ניסיונות עם exponential backoff הוא טווח נפוץ. מעבר לזה, לרוב עדיף להעביר ל-DLQ או escalation אנושית מאשר להמשיך לנסות.
מה ההבדל בין idempotency key ל-checkpoint?
idempotency key מונע ביצוע כפול של אותה פעולה. checkpoint מציין מאיפה להמשיך תהליך שנקטע. הם משלימים זה את זה, לא תחליף זה לזה.
האם צריך circuit breaker גם לקריאות למודל השפה עצמו?
כן — אם ה-provider חווה degradation, המשך שליחת בקשות רק מגדיל latency ועלות בלי תועלת. circuit breaker יכול לעבור זמנית ל-fallback model.
מה עושים עם משימות שנתקעו ב-DLQ?
צריך תהליך תפעולי מוגדר — התראה, בדיקה אנושית, ואפשרות re-drive ידני אחרי תיקון הגורם השורשי.
תגיות: Agent Retry · Exponential Backoff · Idempotency · Circuit Breaker · Dead Letter Queue · AI Agents Reliability