בנית כלי פנימי המאפשר לצוות להריץ 28 בדיקות יחידה על פיצ'ר מבוסס LLM מבלי לקרוא אפילו פעם אחת ל-API של המודל. עשית זאת על ידי עטיפת המודל בממשק שניתן לזיוף (fakeable interface) והוספת שלוש שכבות של הערכה: דטרמיניסטית, היוריסטית ומבוססת LLM.
אסירונים (assertions) סטנדרטיים נשברים ברגע ש-LLM מייצר טקסט חופשי. אותו פרומפט יכול להניב משפט שונה בכל הרצה, ולכן assertEqual(output, expected) יסמן ככישלון גם כאשר המודל התנהג בצורה נכונה. רוב קבוצות ההנדסה או שמשחררות את הפיצ'ר ללא אימות, או מנסות לבדוק את המודל עצמו, תוך התייחסות למטרה משתנה ללא הרף כאילו הייתה ספרייה סטטית.
למה הבעיה הזו חשובה
כיום, מודלי LLM נמצאים בתוך תהליכי עבודה הפונים ללקוחות – פנייה באימייל, מענה לתמיכה, יצירת תוכן. עובדה אחת שהומצאה (hallucinated fact) או מזהה שדלף עלולים לפגוע במוניטין המותג, לחשוף נתונים פרטיים או להוביל להפרות רגולציה. ללא אסטרטגיית בדיקה אמינה, צוותים מבזבזים זמן על רדיפה אחרי כשלים לא עקביים (flaky failures) או משחררים באגים שצפים רק בסביבת הייצור.
הגישה: צמצום האחריות של המודל
הצעד הראשון היה להגביל את מה שה-LLM עושה בפועל. במערכת של המחבר, המודל רק מנסח הודעות פנייה. כל לוגיקת הניתוב, ניהול המצב (state management) ובדיקות הבטיחות נשארים בקוד רגיל. על ידי הגבלת המודל לפלט יחיד ומוגדר היטב, המערכת הסובבת נשארת דטרמיניסטית וניתנת לבדיקה.
כדי לאפשר זאת, ה-LLM יושב מאחורי ממשק ספק (provider interface), המאפשר להשתמש בגרסה מזויפת (fake) בבדיקות. בייצור, המימוש קורא ל-API החיצוני; בערכת הבדיקות, "fake" קל משקל מחזיר תגובה מוכנה מראש (canned response). מכיוון ששאר הקוד מתקשר רק עם הממשק, ניתן להריץ את כל תהליך העבודה באמצעות בדיקות יחידה שמעולם לא נוגעות ברשת. התוצאה היא ליבה צפויה ש-28 הבדיקות מאמתות.
מערך הערכה (evaluation harness) כנה
גם עם היקף מצומצם, הפלט של המודל נותר לא דטרמיניסטי. לכן, המחבר בנה מערך הערכה בעל שלוש שכבות, כאשר כל שכבה מטפלת בסוג אחר של סיכון.
שכבה 1 – בדיקות דטרמיניסטיות כללי regular-expression פשוטים תופסים שגיאות קונקרטיות כמו מזהה בניין שגוי או טוקנים אסורים. בדיקות אלו מהירות ומספקות תוצאת pass/fail בינארית.
שכבה 2 – בדיקות היוריסטיות סקריפטים המחפשים מספרים או תאריכים שהומצאו, ומסמנים טענות עובדתיות מובהקות שקרית. הם מפספסים טענות שקריות שאינן כוללות רמזים מספריים, והמחבר מודה בגלוי במגבלה זו.
שכבה 3 – שופט LLM מודל משני המדרג טון ומקצועיות. מכיוון ששלב זה מסתמך על מערכת הסתברותית נוספת, הוא משמש רק להיבטים סובייקטיביים שבהם כללים דטרמיניסטיים יהיו בלתי אפשריים.
המפתח למערך הוא מערך הנתונים המשמש להערכה. המחבר קידד דפוסי כישלון ידועים – מלכודות ספציפיות וידע בתחום – כך שהמערך בודק בדיוק את הטעויות שהופיעו בפועל. זהו אינו "רשת לכל דבר" קסומה, אלא רשת ביטחון ממוקדת.
מה זה אומר עבור צוותים
- הקטינו את תפקיד ה-LLM. פחות אחריות הופכת את הבידוד והבדיקה לקלים יותר.
- העבירו ניתוב, מצב ובטיחות לקוד. לוגיקה מסורתית נשארת דטרמיניסטית וניתנת לבדיקה מלאה.
- חשפו את המודל דרך ממשק שניתן לזיוף (fakeable interface). בדיקות יחידה רצות ללא קריאות חיצוניות, מה ששומר על ערכת הבדיקות מהירה ואמינה.
- בנו הערכה בשכבות. התחילו בכללים דטרמיניסטיים, הוסיפו היוריסטיקות להזיות (hallucinations) ידועות, ושמרו את שופטי ה-LLM לבדיקות איכות סובייקטיביות.
- ציינו את המגבלות. אף שכבה אינה מבטיחה שלמות; המערך תופס רק את מה שתכנתם במפורש לזהות.
נקודת מבט נגדית: עדיין אי אפשר לבצע בדיקת יחידה למודל עצמו
המחבר מודה שמודל הוא מטרה נעה. אפילו שכבת שופט ה-LLM יורשת את חוסר הדטרמיניזם שהיא מנסה להעריך. כתוצאה מכך, המערכת לעולם לא תוכל להבטיח שכל הזיה או הפרת מדיניות ייתפסו לפני השחרור. הגישה מפחיתה סיכון, אך לא מבטלת אותו, והיא מסתמכת על יכולת הצוות לעדכן את נתוני ההערכה ככל שמופיעים מצבי כישלון חדשים.
שורה תחתונה
אי אפשר לכתוב בדיקת יחידה קלאסית שמאשרת את הפלט המדויק של LLM, אבל אפשר לבנות מערכת שבה השפעת המודל מוגבלת, הממשק שלו ניתן להחלפה, והפלט שלו עובר סינון באמצעות בדיקות שכבות ושקופות. השילוב הזה הופך רכיב שעלול להיות לא עקבי לחלק צפוי באפליקציה גדולה יותר הניתנת לבדיקה.
