חזרה לבלוג

ללמוד על אייג'נטים מתוך בניית אייג'נט עם אייג'נטים

האייג'נט שלי דילג על שלבים. שינויים בפרומפט לא עזרו — הפתרון היה להוסיף אכיפה דרך הקוד. ארבעה דפוסים לבניית אייג'נט שמשתמש בכלים — פרומפט מול toolConfig, ראוטרים, תהליך בשלבים, observability.

agents claude-code codex tool-use ai workflow

פרומפט זה מדיניות. קוד זה בקרת זרימה.

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

פרומפט זה משהו שהמודל מפרש. toolConfig — או המקבילות שלו, ה-tool_choice: "required" של OpenAI וה-tool_choice: {type: "any"} של Anthropic — זה משהו שהמודל חייב לציית לו. ההבחנה הזו הכי משמעותית כשיש לך התנהגות שחייבת לקרות.

השדה בג’מיני נראה ככה:

functionCallingConfig: {
  mode: "AUTO" | "ANY" | "NONE";
  allowedFunctionNames?: string[];
}

AUTO משאיר למודל את החופש להחליט אם לקרוא לכלי ובאיזה. ANY מאלץ אותו לקרוא לכלי, כשהבחירה מוגבלת לשמות שב-allowedFunctionNames — ה-API ידחה תשובה שאיננה קריאת פונקציה. NONE משבית את הכלים לקריאה הזאת. רוב הזמן הכל רץ ב-AUTO; ANY הוא כלי הדיוק.

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

אם פעם כתבת “אתה MUST לקרוא ל-X” באותיות גדולות וראית את המודל לא קורא ל-X — זו הסיבה. ההוראה היא משאלה. פרומפט זה המלצה; קוד זה אכיפה.

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

הראוטר: להחליט בקוד מתי לאכוף

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

classifyIntent(message) → {
  intent: "edit" | "question" | "unsupported" | ...
  target?: string
}

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

יש פיתוי להשתמש ב-regex בשביל זה. לא כדאי. regex שביר שמופעל על “הזז את הסעיף לסוף” יאלץ עריכה על בקשה שהמערכת לא באמת יכולה לקיים — והופך סירוב נכון לפעולה שגויה. כמה מאות מילי-שניות של קריאת מסווג ברמת Flash נותנות החלטה אמיתית על קטגוריה, כולל הקטגוריה “אני לא יכול לקבוע, אל תאכוף כלום”.

זה הגשר בין הפרומפט לקוד: הפרומפט כבר לא צריך ללמד את המודל “האם זו עריכה?” — הקוד כבר החליט. המודל כבר לא מחליט — הוא פועל בתוך מסגרת שהקוד הגדיר.

תחילה התנאים, אחר כך הפעולה

אם הפרומפט של האייג’נט לא מכיל את הדאטה שהפעולה אמורה לפעול עליה, אי אפשר לאכוף את הפעולה בבטחה — המודל ימציא את הדאטה כדי לעמוד באילוץ. עריכה כפויה על גוף מסמך שהמודל מעולם לא קרא מייצרת עריכה מומצאת.

הפתרון: להפעיל את ה-toolConfig בשלבים.

  1. שליפה כפויה. קודם אוכפים את כלי השליפה, כך שהמודל יטען את התוכן האמיתי.
  2. כתיבה כפויה. ברגע שהשליפה קרתה, מצמצמים את הכלים המותרים לכלי הכתיבה — המודל חייב לבצע את הפעולה.
  3. חזרה ל-AUTO. אחרי הכתיבה, חוזרים ל-AUTO כדי שהמודל יכתוב את האישור הטבעי שלו.

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

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

פעולה כפויה שגויה יקרה הרבה יותר מתשובה רכה שגויה, ולכן התשובה “אני לא בטוח” של המסווג צריכה תמיד לנתב ל-AUTO.

החלטות קשות דורשות observability

שתי אסימטריות הופכות את זה לחובה ברגע שמתחילים לאכוף כלים.

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

השנייה היא קריאוּת. כשהמודל פועל לפי פרומפט בלבד, הוא מתאר תוך כדי מה הוא חושב — “חשבתי שרצית עריכה, אז…” — ואת המסלול הזה אפשר לקרוא בדיעבד. תחת כלי כפוי הוא לא מתאר — הוא הולך ישר לקריאה.

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

הגרסה הזולה היא לוג קטן שנשמר בכל קריאה:

  • איזה intent זוהה
  • אילו phases נאכפו
  • אילו כלים היו מותרים
  • האם הפעולה באמת הוחלה

שיבוש נראה במבט אחד — “intent=edit על שאלה רגילה” — במקום הנדסה הפוכה של מערכי קריאות כלים מתוך לוגים.

עיקרי הדברים

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