שאלו מודל שפה כמה אותיות יש במילה “strawberry”. רוב הסיכויים שהוא יטעה. הוא עשוי לומר עשר. הוא עשוי לנחש אחת-עשרה. הוא יישמע בטוח בעצמו לחלוטין, ובכל זאת הוא יהיה טועה. בקשו מאותו מודל לחשב ריבית דריבית על הלוואה, או לחבר שני מספרים גדולים, או לספור ימי עסקים בין שני תאריכים, ולעיתים קרובות תקבלו תשובה שנראית סבירה, אך עם ספרות שגויות במעט, באופן מסוכן.
זה קורה מכיוון שמודלי שפה גדולים אינם מפעילים היגיון לגבי מספרים כפי שבני אדם עושים. הם חוזים טוקנים (tokens). טוקן יכול להיות מילה שלמה, חלק ממילה, או ספרה בודדת. כשמודל רואה את המילה “strawberry”, הוא לא רואה שמונה אותיות בודדות המסודרות בשורה. הוא רואה קבוצה של מקטעים. מעולם לא לימדו אותו לספור תווים, אלא רק לחזות איזה מקטע טקסט יבוא לאחר מכן. מגבלה דומה חלה גם על אריתמטיקה. למודל אין מחשבון פנימי. חסרה לו לוגיקת נשיאה (carry logic). אין לו הבנה אמיתית של ערך ספרתי. כשהוא מכפיל את 148 ב-279, הוא לא מבצע פעולת כפל. הוא מבצע התאמת תבניות (pattern-matching) מול ביטויים דומים שראה במהלך האימון, ומנחש איזו רצף של ספרות צריך לבוא לאחר מכן. עבור סכומים קטנים מאוד, התבנית חזקה מספיק כדי לעבוד. עבור כל דבר הדורש דיוק אמיתי, הניחוש בסופו של דבר נכשל.
שתי משימות, בוט אחד
שיטות פרומפטינג (prompting) סטנדרטיות מבקשות ממערכת אחת לבצע שתי משימות שונות מאוד בו-זמנית. ראשית, להבין את הלוגיקה של הבעיה. שנית, לבצע את המתמטיקה המדויקת. המודל מרשים באמת במשימה הראשונה. הוא יכול לקרוא בעיה מילולית, לחלץ משתנים, למפות קשרים ולתכנן נתיב לפתרון. אך לאחר מכן הוא חייב לשמש כמחשבון של עצמו. שם השרשרת מתפרקת. ספרה אחת שמשתבשת בשלב השלישי מדבקת כל שלב שאחריה. הלוגיקה עצמה עשויה להיות מושלמת, אך התשובה הסופית היא זבל כי המודל טעה בחיבור.
מודלי שפה מבוססי תוכנית, או PAL, פותרים זאת על ידי חלוקת העבודה. במקום לבקש מהמודל תשובה, אתם מבקשים ממנו תוכנית.
כך הזרימה עובדת בפועל: אתם מציגים את הבעיה. המודל מבין את הלוגיקה, מגדיר את המשתנים ומבנה את האלגוריתם. לאחר מכן, במקום לחשב את התוצאה בעצמו, הוא כותב סקריפט קצר, בדרך כלל ב-Python. הסקריפט הזה מועבר למפרש קוד (code interpreter) אמיתי. המפרש מריץ את הלוגיקה ומחזיר את התוצאה המדויקת והדטרמיניסטית. המודל מתאר את המתמטיקה. Python מבצעת את המתמטיקה.
היגיון ניתן להרצה בפועל
חשבו על PAL כעל היגיון ניתן להרצה. אם סקריפט יכול לפתור בעיה, תנו למודל לכתוב את הסקריפט.
שקלו דוגמה קונקרטית. עליכם לחשב את סכום הפירעון של פיקדון קבוע בסך ₹50,000 בריבית שנתית של 8.5 אחוזים, בריבית דריבית רבעונית, למשך שבע שנים. אם תשאלו מודל שפה ישירות, הוא עשוי לכתוב נוסחה, להציב ערכים ולחשב את התוצאה בשרשרת מחשבה (chain of thought). עם זאת, אם תסתכלו מקרוב, אתם עשויים לגלות שהוא טעה בחישוב הריבית הרבעונית על ידי חלוקה לא נכונה של הריבית, או שהוא עיגל שלב ביניים והעביר את השגיאה הלאה. התשובה נראית סבירה אך שגויה במאות רופי.
עם PAL, האינטראקציה משתנה. אתם מנחים את המודל ליצור קוד Python שמגדיר principal = 50000, rate = 0.085, time = 7, ו-n = 4, ואז מחשב את amount = principal * (1 + rate/n) ** (n * time). המודל מפיק את הקוד. סביבת הרצה של Python מריצה אותו. אתם מקבלים את הנתון המדויק, עד לעיגול האחרון, בכל פעם מחדש. אין ניחושים בכפל, אין שארית שהומתה, ואין שגיאת עיגול בטוחה מדי.
אותו דפוס תקף גם למתמטיקה של תאריכים. שאלו מודל איזה תאריך חל בדיוק 120 ימי עסקים מהיום, לא כולל סופי שבוע. מודל מבוסס טקסט בלבד עשוי לספור קדימה ולהחליק ביום שבת. גישת PAL גורמת למודל לכתוב סקריפט המשתמש בלוגיקה של datetime ו-calendar, ואז מאפשרת למפרש לבצע איטרציות בדיוק רב. מניפולציית נתונים עובדת באותו אופן. אם עליכם לנתח קובץ CSV מבולגן, לסנן JSON מקונן או להריץ טרנספורמציה סטטיסטית מהירה, המודל צריך לנסח את הלוגיקה בעוד המפרש מטפל באיטרציות.
למה זה באמת חשוב
המעבר מתשובות בטקסט חופשי לקוד ניתן להרצה מספק שלושה יתרונות מעשיים.
דטרמיניזם. מודל שפה ששואלים את אותה שאלה פעמיים עשוי לשנות את הניסוח שלו או לשנות ספרה. מפרש (interpreter) מחזיר את אותו פלט עבור אותו קלט בכל פעם. היציבות הזו חשובה ביותר בחשבונאות, לוגיסטיקה, תזמון וכל חישוב הנדסי שבו עקביות אינה אופציונלית.
ניתנות לאימות. כשמודל מגיש לך שלושה פסקאות של הסבר, עליך לקרוא כל משפט כדי לצוד את המספר השגוי היחיד. כשהוא מגיש לך סקריפט בן עשר שורות, אתה יכול לעבור על הקוד. אתה יכול לוודא שנוסחת הריבית דריבית נכונה עוד לפני שהמפרש בכלל רץ. אתה יכול לבדוק שמות של משתנים, לזהות שגיאות off-by-one ואפילו לנהל גרסאות (version-control) לפתרון. שטח הפנים לטעויות נסתרות מצטמצם באופן דרמטי.
אמינות. המודל נשאר במסלול שלו. הוא עושה את מה שנבנה לעשות: להסיק מסקנות לגבי מבנה, סמנטיקה ופירוק בעיות. המכונה עושה את מה שנבנה לעשות: לחשב במדויק. הפרדת תחומי אחריות (separation of concerns) זו היא בדיוק הדרך שבה תוכנה אמינה מתוכננת ארכיטקטונית. הרכבה (Composition) מנצחת עיצוב מונוליטי.
הרץ זאת כקוד לא מהימן
נדרשת מילת אזהרה. יש להתייחס לקוד שנוצר כאל קלט לא מהימן. המודל עלול לכתוב סקריפט עם לולאה אינסופית, בקשת רשת מיותרת, או פעולת מערכת קבצים שלא ביקשת. תמיד הרץ את התוכניות הללו בתוך ארגז חול (sandbox) מבודד. השתמש ב-containers עם הרשאות מוגבלות, בפונקציות serverless ללא גישה לרשת, או בסביבות מבוקרות היטב עם זמן CPU מוגבל וללא אחסון קבוע (persistent storage). אבטחה אינה הערת שוליים כאן. היא חלק מתכנון המערכת.
היכן PAL מצטיין, והיכן הוא נעצר
PAL עובד בצורה נפלאה עבור מתמטיקה, תאריכים ומניפולציה של נתונים מובנים. הוא מסיר את השגיאות המכניות המטרידות הסקה מבוססת טקסט בלבד.
עם זאת, הוא אינו מתקן לוגיקה שגויה. אם המודל בוחר בנוסחה הלא נכונה,
