LLMOps — LLM בייצור
לבנות prototype זה קל. להריץ מוצר LLM אמין, זול ובטוח מול אלפי משתמשים — זו הנדסה אמיתית. כך עושים את זה נכון.
שלוש תכונות ששוברות את ההרגלים
LLMOps נשמע כמו עוד ראשי תיבות שמוסיפים ל-DevOps, וזו טעות שימושית להסיר מוקדם. יש שלושה הבדלים מבניים בין מוצר שמבוסס על מודל שפה לבין תוכנה רגילה, וכל פרקטיקה בתחום נגזרת מהם.
- אותו קלט לא מחזיר אותו פלט. בדיקה שעברה אתמול יכולה להיכשל היום בלי ששום דבר השתנה. כל שיטת בדיקה שמניחה תוצאה קבועה פשוט לא חלה כאן.
- המערכת יכולה להידרדר בלי שינוי קוד. הספק עדכן את המודל, המסמכים באינדקס השתנו, המשתמשים התחילו לשאול אחרת. לא פרסת כלום, וההתנהגות אחרת. זה מצב הכשל שהכי קשה לתפוס, כי אין אירוע להיתלות בו.
- איכות אינה בינארית. תשובה יכולה להיות נכונה, נכונה-אבל-ארוכה, נכונה-בטון-לא-מתאים, או חצי נכונה. "עבר / נכשל" לא מתאר את זה, ולכן צריך מדידה מסוג אחר.
המסקנה שכל השאר נבנה עליה: אי אפשר לנהל את זה בלי מדידה רציפה. בתוכנה רגילה, קוד שלא נגעת בו ממשיך לעבוד. כאן — לא בהכרח, וההנחה הזאת היא מקור רוב ההפתעות.
בתוכנה רגילה אתה שואל "מה נשבר". כאן אתה צריך לדעת קודם כל שמשהו נשבר — וזה לא מגיע אליך מעצמו.
מחזור החיים, ומה משתנה בכל שלב
מוצר מבוסס מודל עובר ארבעה שלבים, ובכל אחד מהם "נכון" מוגדר אחרת. רוב הכאב נגרם מהחלת הכללים של שלב אחד על שלב אחר.
- הוכחת היתכנות. השאלה היחידה היא אם המשימה בכלל פתירה. מותר לעבוד בממשק, מותר לקבע מספרים, מותר לא למדוד. מה שכן שווה כבר עכשיו: לשמור את הפרומפטים שעבדו ואת הדוגמאות שנכשלו — משם תיבנה ערכת ההערכה.
- פיילוט. משתמשים אמיתיים, מעטים. כאן חייב להופיע הלוג עם חמשת השדות, כי זה השלב שבו מתגלה מה באמת קורה. וכאן נבנית הערכה מתוך הכישלונות האמיתיים, לא מדמיון.
- ייצור. נוסף כל השאר: ולידציה על הפלט, מודל חלופי, ניטור, התראה אחת לפחות, ופריסה מדורגת לשינויים.
- תחזוקה. השלב הארוך ביותר והכי פחות מדובר. כאן העבודה היא לא לבנות אלא לשים לב — לדרדור שקט, לעליית עלות, למסמכים שהתיישנו.
הטעות הנפוצה היא לדלג מהראשון לשלישי. צוות שעובר מפרוטוטייפ שעבד יפה ישר לייצור מגיע בלי ערכת הערכה ובלי לוג — ואז השינוי הראשון שיצטרך לעשות יהיה עיוור.
והטעות ההפוכה, פחות נפוצה ולא פחות יקרה: לבנות תשתית מדידה מלאה לפני שיש משתמש אחד. בלי קלט אמיתי אין מה למדוד, והערכה שהומצאה מהראש בודקת את מה שדמיינת ולא את מה שקורה.
הפרומפט הוא קוד
הנוהג הנפוץ הוא לערוך פרומפטים בממשק של הספק או בקובץ שמישהו מעדכן ישירות. זה נוח, וזה מייצר בדיוק את המצב שבו אף אחד לא יודע למה המערכת התנהגה אחרת ביום שלישי.
פרומפט עומד בכל הקריטריונים של קוד: הוא משנה התנהגות, הוא נשבר, וצריך לדעת מי שינה אותו ומתי. לכן:
- בריפוזיטורי, לא בממשק. קובץ, בגרסאות, עם היסטוריה.
- בסקירה. שינוי פרומפט הוא שינוי התנהגות. הוא צריך עין שנייה בדיוק כמו שינוי בלוגיקה.
- עם מזהה גרסה שנשמר בלוג. אחרת אי אפשר לקשר תקלה לשינוי, וזה הפרט שהכי חסר בתחקירים.
- בפריסה מדורגת. פרומפט חדש לאחוז מהתנועה, ואז יותר — לא להחלפה גורפת.
ומה שכדאי להוציא מהפרומפט החוצה: מזהי מודלים, ספים, ומספרים. הם משתנים בקצב אחר מהטקסט, ועדיף שיהיו בהגדרה.
מזהי מודלים: הבאג שתמיד חוזר
דוגמה מהאתר הזה, כי היא מדגימה את זה טוב יותר מכל הסבר.
שניים מהמדריכים כאן הכילו מזהי מודלים ומספרי גרסאות בתוך דוגמאות קוד. הם נכתבו בזמן מסוים, נשארו בקובץ, ואיש לא חזר אליהם. בקוד אמיתי, אותו דפוס הוא לא מבוכה אלא השבתה: ביום שהספק מוציא גרסה משימוש, הקריאה מחזירה שגיאה, והמערכת נופלת בלי ששינית שורה.
מה שמונע את זה:
# ✗ מפוזר בקוד
resp = client.create(model="some-model-v3", ...)
# ✓ במקום אחד, מהגדרה
MODEL = os.environ["LLM_MODEL"]
FALLBACK_MODEL = os.environ.get("LLM_FALLBACK_MODEL")
resp = client.create(model=MODEL, ...)
ושני הרגלים שמשלימים את זה: לתעד את המזהה שבאמת שירת כל בקשה — כי ספקים מפנים לעיתים גרסה כללית לגרסה ספציפית, ואתה רוצה לדעת לאיזו; ולעקוב אחרי הודעות הוצאה משימוש של הספק שאתה משתמש בו. זו הדקה היחידה שמפרידה בין מעבר מתוכנן לתקלה בלילה.
הערכות הן ה-CI של זה
אם יש רכיב אחד שבלעדיו אי אפשר לעבוד, זה הוא. ערכת הערכה היא מה שמאפשר לשנות משהו ולדעת אם עשית טוב או רע — וזה בדיוק מה שאין בלעדיה.
מה שצריך, וזה פחות ממה שנדמה:
- שלושים עד מאה מקרים אמיתיים מהמוצר שלך, עם התוצאה הנכונה לצידם.
- כולל מקרים שאין להם תשובה — לפחות רבע. אלה בודקים אם המערכת יודעת לסרב, וזה המדד שהכי משפיע על אמון.
- כולל את המקרים המכוערים — קלט מנוסח רע, מעורב עברית-אנגלית, חסר.
- הרצה אוטומטית לפני כל שינוי בפרומפט, במודל או בשליפה.
והכלל שהופך את זה לנכס: כל תקלה בייצור מוסיפה מקרה לערכה. אחרי חצי שנה יש לך אוסף של בדיוק מה שמפיל אותך, וכל שינוי עתידי נבדק מולו. הרחבה: Evals.
ולגבי שיפוט אוטומטי — מודל שמדרג פלט של מודל אחר עובד סביר להשוואה יחסית ("איזו משתי הגרסאות טובה יותר") ופחות טוב לציון מוחלט. ובעברית שווה לבדוק את המשפט עצמו לפני שסומכים עליו, כי שופט שאומן לאנגלית מדרג טקסט עברי גרוע יותר מכפי שהוא.
ניטור: מה למדוד כשאין "נכשל"
המדדים הרגילים — חביון, שגיאות, תפוקה — עדיין נחוצים ולא מספיקים, כי הכשל האופייני כאן מחזיר 200 ונראה תקין.
ארבעה מדדים שתופסים דרדור שקט:
- שיעור כישלון סכימה. כמה פעמים הפלט לא עבר ולידציה. המדד הרגיש ביותר — הוא זז ראשון כשמשהו משתנה אצל הספק.
- התפלגות אורך הפלט. תשובות שהתקצרו או התארכו פתאום הן אות כמעט ודאי לשינוי בהתנהגות המודל.
- שיעור סירוב. כמה פעמים המערכת אמרה "לא מצאתי". עלייה פירושה שהשליפה נשברה; ירידה חדה פירושה שהמודל התחיל לנחש.
- ציוני השליפה. ממוצע הציון של הקטע הטוב ביותר. ירידה כאן מקדימה תלונות משתמשים בשבועות.
ושלושתם נמדדים כהתפלגות ולא כממוצע. ממוצע מסתיר בדיוק את מה שמעניין: המקרים החריגים. אחוזון 95 של החביון אומר יותר מהחציון, ואותו דבר נכון לעלות.
הרחבה על השכבה הזאת: Observability.
חמישה דברים לתעד, אחרת אין תחקיר
זה החלק שהכי משתלם לעשות מוקדם והכי כואב להוסיף אחרי הראשונה. תלונה של משתמש הופכת לחקירה של חמש דקות או לניחוש, בהתאם לאלה:
- הקלט המדויק — אחרי כל שכתוב ועיבוד שעשית לו.
- ההקשר שנשלף — הקטעים עצמם והציונים, לא רק המזהים.
- מזהה המודל שבאמת שירת — לא זה שביקשת.
- גרסת הפרומפט.
- הפלט הגולמי — לפני פירסור, אחרי הכל.
בלי החמישה, האבחון נעצר ב"המודל טעה". איתם, הוא כמעט תמיד מצביע על אחד משלושה: לא נשלף, נשלף והפלט סתר, או שהמקור עצמו שגוי.
ופרט שקל לפספס: מה שמתעדים הוא לרוב מידע אישי. שאלות משתמשים מכילות שמות, כתובות ופרטי חשבון. תחום שמירה, גישה מוגבלת, ודרך למחוק לפי משתמש — הם חלק מהתכנון ולא נספח.
האיכות ירדה ואין הסבר
התרחיש הכי מתסכל: הכל היה בסדר, לא פרסתם כלום, והמשתמשים מתלוננים. סדר בדיקה שמצמצם במהירות:
- להריץ את ערכת ההערכה עכשיו. אם היא נפלה לעומת ההרצה הקודמת — משהו במערכת השתנה. אם היא עברה — המערכת בסדר והקלט השתנה, וזה כיוון חקירה אחר לגמרי.
- להשוות מזהה מודל. מה שירת לפני שבוע מול מה ששירת אתמול. אם זה לא אותו דבר, מצאת.
- לבדוק את ציוני השליפה. ירידה בממוצע אומרת שהאינדקס או המסמכים השתנו — לא המודל.
- להסתכל על אורך הפלט. קפיצה או צניחה בהתפלגות היא אות כמעט ודאי לשינוי בהתנהגות המודל.
- לקרוא עשרים תלונות ברצף. השלב שהכי קל לדלג עליו והכי מהר מאבחן. אם כולן על אותו נושא, הבעיה במסמך ולא במערכת.
ושלוש סיבות שכדאי לשלול מוקדם כי הן נפוצות ולא אינטואיטיביות: מסמך שעודכן ושינה את מה שנשלף; שינוי בהתנהגות המשתמשים — קמפיין שהביא קהל ששואל אחרת; וגדילה של האינדקס, שמורידה את הדיוק של השליפה בלי שאף אחד נגע בקוד.
הכלל: אל תשנה את הפרומפט לפני שאתה יודע מה השתנה. זו התגובה האינסטינקטיבית, והיא מוסיפה משתנה בדיוק כשאתה מנסה לבודד אחד.
ניתוב ונפילה חכמה
ספקים נופלים, מגבילים קצב, ומאטים. מערכת שתלויה בספק אחד תלויה גם בזמינות שלו.
הפתרון המקובל הוא מודל חלופי — ויש בו מלכודת שכמעט תמיד מגלים בזמן התקלה: הפרומפט שלך מכויל למודל אחד. מודל אחר יחזיר פורמט קצת אחר, יסרב על דברים אחרים, ויתנהג אחרת על אותה הנחיה.
def ask(messages, schema):
for model in (PRIMARY, FALLBACK):
try:
out = call(model, messages, schema)
if validate(out, schema): # חובה, בשני המסלולים
return out, model
log("schema_fail", model=model)
except (RateLimit, Unavailable) as e:
log("provider_error", model=model, err=e)
raise NoUsableResponse()
שתי נקודות בקוד הזה. הראשונה: הוולידציה רצה גם על החלופי — אחרת נפילה שקטה מחליפה נפילה רועשת. השנייה: מחזירים איזה מודל שירת, כי בלי זה לא תדע שרבע מהתנועה שלך רץ שבוע על החלופי.
וההכרח שקודם לקוד: להריץ את ערכת ההערכה על שני המודלים. חלופי שלא נבדק הוא לא חלופי — הוא הימור שנשמר ליום הגרוע.
חביון: מה שהמשתמש באמת מרגיש
מודל שפה איטי בסדר גודל מקריאה רגילה למסד נתונים, וזה משנה החלטות תכנון שבתוכנה רגילה לא היו עולות.
מאיפה הזמן מגיע, בסדר יורד:
- אורך הפלט. הגורם הדומיננטי — הטוקנים נוצרים אחד-אחד. תשובה קצרה יותר היא מהירה יותר באופן ליניארי כמעט, וזו הדרך הפשוטה ביותר לקצר.
- שלבים בשרשרת. שליפה, ואז דירוג מחדש, ואז מודל — שלוש המתנות שמצטברות. כל שלב שאפשר להריץ במקביל שווה יותר מכל אופטימיזציה בתוך שלב.
- קריאות כלים. בסוכן, כל סבב הוא קריאה נוספת. שרשרת של חמישה צעדים היא חמש המתנות.
- עומס אצל הספק. משתנה לאורך היום ולא בשליטתך, ולכן שווה למדוד באחוזון ולא בממוצע.
ומה שעובד בממשק יותר מכל אופטימיזציה: הזרמה. תשובה שמתחילה להופיע אחרי חצי שנייה נתפסת כמהירה גם כשהיא נמשכת עשר שניות, ואותה תשובה שמגיעה בבת אחת בסוף נתפסת כתקועה. זה שינוי בחוויה בלי שינוי בזמן.
ולסוכנים — להראות מה קורה. "מחפש במסמכים", "בודק מלאי". משתמש שרואה התקדמות ממתין; משתמש שרואה מסך ריק מרענן, וכל רענון הוא קריאה נוספת שאתה משלם עליה.
עלות: לאן הכסף באמת הולך
חשבון של מוצר LLM מתנפח לרוב לא מהמקום שמצפים לו.
- הקשר, לא תשובות. ברוב המערכות רוב הטוקנים הם קלט — היסטוריה, קטעים שנשלפו, הנחיות מערכת. התשובה עצמה זניחה. מי שמנסה לחסוך בקיצור תשובות מתעסק בסעיף הקטן.
- מטמון פרומפט. אם החלק הקבוע של הבקשה גדול — הנחיות, סכימה, דוגמאות — שמירתו במטמון אצל הספק מוזילה משמעותית. פירוט בCaching.
- ניסיונות חוזרים. כל נפילה שגוררת ניסיון נוסף היא עלות כפולה. שיעור כישלון סכימה של אחוזים בודדים מתורגם ישירות לחשבון.
- עברית. אותו טקסט צורך יותר טוקנים מאשר באנגלית, כי הטוקנייזרים מפרקים מילה עברית לרסיסים. מי שמתכנן תקציב לפי מדידה באנגלית יטעה, ולא במעט.
ומה שכדאי למדוד: עלות לבקשה, בהתפלגות. הממוצע יסתיר את העשירון שצורך פי עשרה — ובדרך כלל שם נמצאת גם באג: לולאה שלא נעצרת, היסטוריה שלא נחתכת, או משתמש אחד שמריץ משהו אוטומטי. שווה להגדיר תקרה לבקשה בודדת ולהתריע על חריגה, כי הסעיף הזה הוא היחיד שיכול להכפיל את החשבון ביום. הרחבה: עלויות.
לפרוס שינוי בלי לשבור
שינוי פרומפט מרגיש קטן ומשפיע על כל התנועה בבת אחת. הסדר שעובד:
- ערכת ההערכה. אם היא נופלת, נעצרים כאן.
- מצב צל. הגרסה החדשה רצה במקביל, הפלט שלה נשמר ולא מוצג. משווים לא-מקוון, בלי סיכון.
- קנרי. אחוזים בודדים מהתנועה, עם מעקב על ארבעת המדדים מסעיף הניטור.
- הרחבה הדרגתית, עם דרך חזרה בלחיצה.
ונקודה שמפתיעה צוותים שהורגלו למוצר רגיל: בדיקת A/B על פלט של מודל דורשת יותר תנועה ממה שנדמה. השונות בין תשובות גבוהה, ולכן הפרש קטן נבלע ברעש. הרבה "ניצחונות" שמדווחים אחרי יומיים הם רעש — ובמוצר עם מעט משתמשים, הערכה לא-מקוונת על ערכה קבועה אמינה יותר מניסוי חי.
וכשהספק הוא זה שהשתנה
יקרה, ולא תקבל על זה התראה מראש. תוכנית פעולה קצרה:
- לזהות שזה קרה. ארבעת מדדי הניטור. אם אף אחד מהם לא נאסף, תגלה מלקוח.
- להשוות מול הערכה. להריץ את הערכה עכשיו ולהשוות להרצה האחרונה. זה מפריד בין "המודל השתנה" ל"המשתמשים השתנו".
- להצמיד גרסה אם אפשר. חלק מהספקים מאפשרים לנעול גרסה ספציפית — זה קונה לך זמן להסתגל במקום להגיב.
- לתקן פרומפט, לא לרדוף. ברוב המקרים ההתאמה היא שינוי קטן בהנחיה, לא מעבר ספק.
ושווה לקבוע מראש מי מקבל התראה כששיעור כישלון הסכימה חורג. מערכת שמדרדרת בשקט בשלוש בלילה היא בדיוק התרחיש שכל הסעיף הזה נועד למנוע.
ופרט אחרון שחוסך הפתעות: תקרא את דף השינויים של הספק פעם בחודש. זה עשר דקות, והוא מכיל בדרך כלל את ההודעות על הוצאה משימוש כמה שבועות לפני שהן נכנסות לתוקף. הפער בין לדעת מראש לבין לגלות מהמערכת הוא ההבדל בין מעבר מתוכנן לתקלה.
אבטחה שנכנסת לתמונה
שתי נקודות שספציפיות למוצר מבוסס מודל, ושנוטות ליפול בין הכיסאות:
- קלט שמגיע מבחוץ הוא לא נתון, הוא טקסט שמשפיע. מסמך שמשתמש העלה, דף שנשלף, מייל שנכנס — כולם מגיעים לתוך ההקשר ויכולים להכיל הנחיות. הפירוט בPrompt Injection, והעיקרון: הרשאות נקבעות בקוד ולא בפרומפט.
- מפתחות ותשתית. מפתח API שיושב בקוד צד-לקוח הוא חשבון פתוח לכל מי שיפתח את הדפדפן. הקריאות עוברות דרך שרת שלך, תמיד, גם בפרוטוטייפ.
ועוד אחת מעשית: הגבלת קצב לפי משתמש. בלעדיה, משתמש אחד עם לולאה יכול לשרוף את התקציב החודשי ביום.
ומי אחראי על מה
בצוות קטן זה נשמע מיותר, וזה בדיוק המקום שבו דברים נופלים — כי פרומפט הוא ארטיפקט שגם מהנדס וגם מי שמבין את התחום נוגעים בו.
החלוקה שעובדת:
- מי שמבין את התחום מחזיק את ערכת ההערכה. הוא זה שיודע מה תשובה נכונה. זה התפקיד החשוב ביותר בכל הרשימה, והוא לרוב לא מוגדר לאף אחד.
- מהנדס מחזיק את הצינור. שליפה, ולידציה, ניתוב, ניטור.
- הפרומפט משותף — ולכן דווקא הוא חייב סקירה. מי שמבין תחום כותב את התוכן, מהנדס בודק שהוא לא שובר את הסכימה.
- מי מקבל את ההתראה — שם אחד, לא "הצוות". התראה שמגיעה לכולם לא מגיעה לאף אחד.
ופרקטיקה אחת שמחזירה יותר מכל השאר: שמישהו יקרא עשרים שיחות אמיתיות בשבוע. לא מדדים — שיחות. זה תופס דברים שאף מדד לא מגדיר, וזה גם מייצר את רוב המקרים שנכנסים לערכת ההערכה.
צוות קטן: המינימום שבאמת נדרש
הכלים בתחום הזה נמכרים כאילו צריך פלטפורמה. לצוות של אחד עד שלושה, זה מה שבאמת חייב להתקיים:
- פרומפטים בריפוזיטורי. קובץ. לא ממשק.
- שלושים מקרי בדיקה וסקריפט שמריץ אותם. אפשר בשעה.
- לוג עם חמשת השדות. טבלה מספיקה.
- מזהה מודל בהגדרה, לא בקוד.
- התראה אחת — על שיעור כישלון סכימה.
חמשת אלה מכסים את רוב מה שמפיל מערכות בשלב הזה, ואף אחד מהם לא דורש כלי. כל השאר יכול לחכות — ולרוב צריך לחכות, כי פלטפורמה שנבחרה לפני שידעת מה אתה מודד היא פלטפורמה שתחליף.
ומתי כן שווה כלי ייעודי: כשהערכה גדלה מספיק שהרצה שלה ידנית מעצבנת, כשיש יותר מאדם אחד שנוגע בפרומפטים, או כשצריך להשוות כמה גרסאות בו-זמנית. שלושת הסימנים האלה מגיעים מעצמם, ועד שהם מגיעים — טבלה וסקריפט עושים את העבודה בלי תלות שתצטרך להחליף.
מתי זה מיותר
- בפרוטוטייפ. לפני שיש משתמשים אמיתיים, תשתית מדידה היא הסחת דעת. מה שכן שווה כבר עכשיו: פרומפטים בגרסאות ומזהה מודל בהגדרה — שניהם חינם.
- כשהמודל אינו בנתיב הקריטי. פיצ׳ר עזר שאפשר לכבות לא מצדיק את כל השרשרת.
- בשימוש פנימי בלבד. כלי שצוות של חמישה משתמש בו — אדם ישים לב שמשהו מוזר. בכלי שפונה ללקוחות, אף אחד לא ישים לב.
טעויות שחוזרות
- לקבע מזהה מודל בקוד. נופל ביום שהגרסה יוצאת משימוש.
- לערוך פרומפטים בממשק. בלי היסטוריה, בלי סקירה, בלי דרך לקשר תקלה לשינוי.
- להחליף פרומפט בלי הערכה. שיפור באזור אחד ששובר אזור אחר בשקט.
- מודל חלופי שלא נבדק. הימור שנשמר ליום הגרוע.
- למדוד ממוצעים. מסתיר בדיוק את מה שמעניין.
- בלי לתעד את הקשר שנשלף. ואז כל תלונה היא ניחוש.
- לתכנן תקציב לפי אנגלית. עברית צורכת יותר.
- להסיק מ-A/B קצר. השונות גבוהה; רוב ה"ניצחונות" האלה הם רעש.