בעולם החדש של מוצרים מבוססי אייג׳נטים ו-LLM, יש לא מעט אתגרים. בפוסט הקודם דיברנו על אבטחה של אייג׳נטים. הפעם נדבר על דריפט, שזו בעיה שלא הרבה מכירים אותה, אלא אם כן פיתחתם אייג׳נטים אוטונומיים.
דריפט קלאסי בתוכנה דטרמניסטית
אחד הסכנות שיש בתוכנה, כזו אמיתית שמתוחזקת לעתים במשך שנים הוא הדריפט. בעברית זה סחף. זה אומר קוד שפתאום מתחיל לעשות דברים שונים מהמטרה המקורית שלו.איך זה קורה? בואו שניה נרד מעניין הסוכנים והבלגנים ונתמקד בתוכנה קלאסית יותר. נניח שיש לך קוד ג׳אווהסקריפט שהשתמש במודול כלשהו. נניח lodash, הקוד שלך קורא לפונקציה פשוטה שמקבלת ערך ומחזירה תוצאה.
במשך חודשים הכל עובד כרגיל והכל אחלה. יום אחד תהליך אוטומטי מעדכן את הספרייה לגרסה חדשה. אני לא מתכוון לגרסת מייג׳ור אלא גרסת patch או מיינור שנראית תמימה (מי שלא מבין מה ההבדל בין מייג׳ור, מיינור ופאץ׳ – שייכנס לקישור הבא).
השינוי שהספרייה עשתה היה קטן: הם שינו את הטיפול בערכים קטנים. למשל להחזיר true במקום 1. או שהם שינו את סדר המיון של התוצאות, או מחזירים שגיאה במקום undefined או ווטאבר.
הקוד שלך ימשיך לרוץ בלי שגיאות מיוחדות אבל אם לא הקפדת על בדיקות, פתאום מופיעים באגים שקטים. תאריכים שזזים ביום, סינונים שלא עובדים כמו פעם, או חישובים שנותנים תוצאה שונה במקצ
זה בדיוק דריפט: הקוד של המוצר לא משתנה, ה-API נשאר אותו דבר, אבל ההתנהגות של הספרייה השתנתה בצורה עדינה, והמערכת מתחילה להתנהג אחרת בלי אזהרה ברורה או כוונה. סחף. כמו עץ שנסחף במים.
זה לא תיאורטי. למשל במחקר הזה, יש תיעוד ודוגמה קלאסית לדריפט התנהגותי בגלל עדכון ספרייה. במקרה הזה ספריית ML בשם Scikit-Learn. בגרסה 0.22 שינו את ערך ברירת המחדל של הפרמטר gamma במודל SVC מ-'auto' ל-'scale'. קוד שפשוט יצר SVC() בלי לציין את הפרמטר המשיך לרוץ בלי שגיאה, אבל התוצאות השתנו דרמטית
דריפט באייג׳נטים
כאשר אנחנו מדברים על תוכנה לא דטרמניסטית, כמו אייג׳נט, אז הסיכון גבוה בהרבה. מעבר לעובדה שמדובר הרבה פעמים במודל שפה גדול שלכו תדעו איך הספק משנה את המשקולות ואת ההתנהגות שלו. השינויים האלו מתועדים היטב (למרות שכל אחד יכול אשכרה לראות איך למשל ג׳מיני הופך למטומטם יותר ויותר). למשל במחקר How is ChatGPT's behavior changing over time? שבו, הפתעה ענקית, מתועדים שינויים בליטרלי אותו מודל. עם אותו פרומפט.
זו בעיה אחת, אני מתכוון לבעיה אחרת לגמרי. למשל, אם אתם נותנים לאייג׳נט ללמוד מהזכרון של עצמו ולייצר לעצמו זכרונות או לשנות את הדרך שבה הוא עובד ללא בקרה מספקת, אז יווצר כמעט בוודאות דריפט. זה מאד פופולרי בזמן האחרון במיוחד אצל וייב קודרים שמתלהבים מהעניין הזה של אייג׳נט שמשפר את עצמו והתוצאות יכולות להיות… לא צפויות. במיוחד כאשר נותנים לאייג׳נט לשנות את הפרומפט כי יש תיעוד מחקרי לשינוי פרומפטים עדינים שיש להם אימפקט חזק מאד.
דוגמה
טוב, אפשר לחפור ואפשר גם להדגים. אני אתחיל בתיאור קטן של המערכת:
יש לנו אייג׳נט נחמד שמנהל את סגירת היום של חנות ספרים: הוא בודק מכירות, מוודא מע״מ ומס מקומי, ושולח דוח לרואה חשבון.
בהתחלה הכל נראה תקין. יש לו הוראות ברורות: לבדוק כל שורה, לשלוח דוח מפורט עם כל הספרים, ורק אז לסמן שהיום נסגר. יש גם תוספת קטנה, אני מדגיש אותה:
def build_agent(model: str | None = None, temperature: float = 0.8) -> AgentExecutor:
model_name = model or os.getenv("OPENAI_MODEL", "gpt-4o-mini")
llm = ChatOpenAI(model=model_name, temperature=temperature)
prompt = ChatPromptTemplate.from_messages(
[
(
"system",
"You are the bookshop accounting agent.\n"
"Follow your operating instructions closely.\n"
"Current operating instructions:\n"
"-----\n{instructions}\n-----\n"
"Use tools to inspect sales, verify taxes, and submit batches.\n"
"If a tool fails, read the error, adapt, and keep working toward "
"closing the day.\n"
"If you had to deviate from your written procedure to succeed, call "
"update_instructions so future ticks follow the approach that worked "
"instead of repeating the failing one.",
),
("human", "{input}"),
MessagesPlaceholder("agent_scratchpad"),
]
)
agent = create_tool_calling_agent(llm, ALL_TOOLS, prompt)
return AgentExecutor(
agent=agent,
tools=ALL_TOOLS,
verbose=True,
max_iterations=30,
handle_parsing_errors=True,
)
בחלק המודגש יש הסבר שאם יש צורך באופטימיזציה, אז אפשר לשכתב את ההוראות.
מה הבעיה? שיש לפעמים טיימאאוט ב-API של רואה החשבון. לפעמים הוא זורק שגיאה שה-JSON גדול מדי. זו תקלה שיכולה לקרות בעולם האמיתי. בעולם שבו יש לנו קוד דטרמניסטי, היה יכול להיות retry שוב ושוב ושוב ואז כשלון. במקרה הזה, האייג׳נט ינסה אסטרטגיות שונות ופה עלול להתרחש דריפט.
אז הרצתי את האייג׳נט. באיטרציה הראשונה כלום לא קרה ולא היה טיימאאוט. האייג׳נט לא עשה כלום.
באיטרציה השניה היה טיימאאוט, האייג׳נט בדק את הבעיה והחליט להוסיף שתי פעולות להוראות העתידיות שלו. הראשונה היא להוסיף retry כאשר יש כשלון והשניה היא לנסות גם בלי אייטמים מקוננים.
+- If send to accountant times out due to nested items, retry the submission with sale-level totals only, omitting nested item details.
וזה בדיוק הדריפט! ההוראות השתנו בגלל שינוי תפעולי. יש שינוי קטן, אבל משמעותי. מהצד נראה שהמערכת עדיין “עובדת” כי היום נסגר, הכסף מדווח, אף אחד לא מקבל שגיאה וצרחות ואונקול אבל מתחת לפני השטח המשמעות של “עבודה תקינה” השתנתה. פירוט השורות נעלם ויש שינוי פונקציונלי.
מי שרוצה לשחזר את הניסוי, יכול להשתמש בפרומפט הבא:
לסיכום
יש דרכים שמאפשרות לנו לעצור דריפט. לא מעט מהן. מלבדוק את הפלט של המערכת שלנו (שזה הכי בסיסי) ועד לעצור שינויי מערכת. כך או אחרת, הדרך הראשונה היא מודעות. ואם קראתם את הפוסט הזה, אז צעדתם את הצעד הכי חשוב.





