לסוכני LangGraph יש סוף סוף דרך אמינה לשמור על המצב (state) שלהם לאחר שבועות של אובדן נתונים שקט. לאחר שלושה ניסיונות כושלים לשימוש ב-checkpointing — SQLite, אחסון אובייקטים גולמי (raw object storage), וגרסה פגומה של כל אחד מהם — המחבר הגיע לתבנית של עדכון אטומי (atomic-update pattern) שמונעת מסוכנים להתחיל מחדש בכל פעם שמגיעה בקשה.

למה checkpointing חשוב עבור LangGraph

LangGraph מאפשרת למפתחים לחבר קריאות LLM לכדי "סוכנים" (agents) ניתנים לשימוש חוזר שיכולים לזכור מה קרה קודם לכן בשיחה. סוכנים אלו מפרקים בקשת משתמש לתתי-משימות, שומרים את התוצאות הביניים, וממשיכים מנקודת העצירה שלהם בקריאה הבאה. אם המצב השמור נעלם, הסוכן מחשב הכל מחדש, מה שגורם לבזבוז משאבי מחשוב, עלייה בשיהוי (latency) וחוויית משתמש גרועה. בבוט ייצור (production) שמטפל בהודעות Telegram, האובדן מחק שבועות של היסטוריית שיחות.

הפתרון הראשון: SQLite saver

ה-SqliteSaver המובנה עובד מצוין כאשר מופע (instance) יחיד מריץ את הסוכן. הוא כותב כל checkpoint כבלוק JSON בקובץ SQLite מקומי. הבעיות החלו כאשר המפתח הוסיף שדה חדש לטיפוס AgentState וביצע פריסה מחדש (redeployed). ה-checkpoints הקיימים, שנוצרו לפני שינוי הסכימה (schema), היו חסרים את השדה החדש. מכיוון ש-SqliteSaver אינו מריץ מיגרציה, LangGraph טענה את ה-JSON הלא שלם, השמיטה את הנתונים החסרים, והסוכן התחיל מחדש מההתחלה.

נקודה מרכזית: אחסון SQLite הוא כלי להדגמה (demo), ולא פתרון מוכן לייצור (production) כאשר נדרשת אבולוציה של סכימה.

הפתרון השני: Object storage

כדי להשיג שליטה על פורמט הסריאליזציה (serialization), המחבר כתב saver מותאם אישית שהעלה את ה-JSON checkpoint ל-Oracle Cloud Object Storage. המהלך העניק גמישות לניהול גרסאות של הסכימה באופן ידני, אך הוא הציג מצב כשל חדש. כאשר שתי בקשות הגיעו לאותו תהליך שיחה בו-זמנית, שתיהן ניסו לדרוס את אותו אובייקט. שירותי Object storage מותאמים לתבניות של כתיבה פעם אחת וקריאה פעמים רבות; הם אינם מספקים סמנטיקה של דריסה אטומית (atomic overwrite). מצב המרוץ (race condition) יצר קבצי JSON פגומים או קטועים, והסוכן שוב איבד את ההקשר שלו.

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

הפתרון השלישי: עדכונים אטומיים עם ניהול גרסאות

העיצוב הסופי והיציב משלב שני רעיונות: מספרי גרסה מפורשים וכתיבות מותנות (conditional writes) המבוססות על ה-ETag של האובייקט (מזהה ה-checksum של שירות האחסון).

  1. קרא (Read) את ה-checkpoint הנוכחי ולכד את ה-ETag שלו.
  2. הגדל (Increment) שדה גרסה בתוך מעטפת ה-checkpoint.
  3. כתוב (Write) את ה-checkpoint המעודכן באמצעות בקשה מותנית שתצליח רק אם ה-ETag תואם לזה שנקרא קודם לכן.
  4. נסה שוב (Retry) את כל לולאת הקריאה-הגדלה-כתיבה אם הכתיבה המותנית נכשלת מכיוון שתהליך אחר שינה את האובייקט.

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

התבנית עובדת עם object storage התומך בכתיבות מותנות מבוססות ETag.

לקחים למהנדסי AI

  • השתמשו ב-SQLite רק עבור אבות-טיפוס (prototypes). סוכני ייצור זקוקים לאחסון שיכול להתמודד עם שינויי סכימה וכתיבות מקביליות.
  • תכננו מיגרציות סכימה בעצמכם. מילונים מוקצבים (Typed dictionaries) מתארים מבנים לצורך ניתוח סטטי, אך אינם אוכפים את המבנה בזמן ריצה (runtime).
  • התייחסו למצב (state) כמשאב משותף. באגים של מקביליות (concurrency) מופיעים כאובדן נתונים שקט; קשה יותר לדבג אותם מאשר חריגות (exceptions) מפורשות.
  • השתמשו בפרימיטיבים של ענן (cloud primitives). כתיבות מותנות מבוססות ETag מספקות נעילה אופטימית (optimistic locking) זולה ללא שירות נעילה נפרד.
  • תעדו (Log) כל שלב. כשלים שקטים — כמו שדה חסר ש-LangGraph מתעלם ממנו — הם הקשים ביותר לאיתור.

מה הלאה עבור checkpointing ב-LangGraph?

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

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