Guardrails ל-AI
— לשלוט במה שהמערכת עושה
מודל שפה הוא לא-דטרמיניסטי: אותה מערכת יכולה לענות מצוין ורגע אחר כך לחשוף PII, להזות עובדה, או להיגרר להוראה זדונית. Guardrails הם שכבות ההגנה שמקיפות את המודל — סינון קלט ופלט, זיהוי PII, מניעת הזיות ו-human-in-the-loop — כדי שתוכל להעלות AI לפרודקשן ולישון בלילה.
הדף הזה נועד למי שבונה מערכות AI וצריך להגן עליהן. הוא מסביר כיצד מערכות AI נכשלות בהיעדר בקרות — מפני שאי אפשר להתגונן מפני מנגנון שלא מבינים — ורוב התוכן שלו עוסק באמצעי ההגנה: מה לבדוק, מה לחסום, ומה לתעד. אין כאן כלים לתקיפה ואין רשימת ניסוחים לשימוש נגד מערכת של מישהו אחר. תקיפה של מערכת שאינה שלכם, או שלא קיבלתם עליה אישור מפורש בכתב, אסורה על פי חוק המחשבים, התשנ"ה-1995.
נכתב על ידי בבוש — אנליסט סייבר ומומחה לוחמת סייבר, העוסק באבטחת מידע בארגון פיננסי. עוד על הכותב
מה זו בקרה, ומה היא לא
בקרה (guardrail) היא בדיקה שרצה מסביב למודל — על מה שנכנס אליו, על מה שיצא ממנו, או על מה שהוא מבקש לעשות — ומחליטה אם להעביר הלאה.
מה שחשוב לומר מיד הוא מה היא לא: היא אינה חלק מהמודל ואינה הנחיה בפרומפט. "אל תיתן ייעוץ רפואי" בהנחיית המערכת היא בקשה — היא מורידה את ההסתברות ואינה מונעת. בקרה היא קוד שרץ אחרי, רואה את התוצאה, ויכולה לעצור אותה.
ההבחנה הזו אינה סמנטית. כל מה שמנוסח כבקשה למודל ניתן לעקיפה — בקלט מנוסח אחרת, בטקסט שנשלף ממסמך, או פשוט כי המודל טעה. מה שמנוסח כתנאי בקוד לא.
אם ההגנה שלכם מנוסחת כמשפט שהמודל אמור לציית לו — אין לכם בקרה. יש לכם תקווה.
השאלה אינה מה לזהות — היא מה לעשות
רוב הדיון בתחום עוסק בזיהוי: איך לתפוס תוכן פוגעני, איך לזהות שהמודל המציא. אבל הזיהוי הוא החלק הקל, וההחלטה מה קורה כשהבדיקה נדלקת היא זו שקובעת אם המערכת שמישה.
לכל בקרה צריכה להיות תשובה מראש לשאלה הזו, ויש ארבע אפשרויות בלבד:
- לחסום ולומר. המשתמש מקבל הודעה מנוסחת במקום התשובה. מתאים כשהתוכן באמת אסור.
- לנסות שוב. אותה בקשה עם תיקון — רק כשהכשל מבני (פורמט, סכמה), לא כשהוא תוכני.
- להעביר לאדם. המקרה נכנס לתור בדיקה. זו התשובה הנכונה כשהעלות של טעות גבוהה והנפח נמוך.
- להעביר עם סימון. התשובה יוצאת, ומסומנת לביקורת בדיעבד. מתאים כשהמערכת חייבת לענות ותיקון בדיעבד אפשרי.
בקרה שאין לה תשובה לשאלה הזו אינה בקרה — היא לוג. וזה בסדר גמור, אם זו הייתה הכוונה: לתעד בלי לחסום הוא שלב ראשון לגיטימי, ובלבד שקוראים לו בשמו.
שלוש נקודות שבהן בקרה יושבת
הבלבול הנפוץ הוא לדבר על "בקרות" כקטגוריה אחת. הן שלוש נקודות שונות עם אופי שונה לגמרי:
- על הקלט — לפני שהמודל רואה. זולה, מהירה, ומוגבלת: היא לא יודעת מה ייצא.
- על הפלט — אחרי שהמודל ענה, לפני שהמשתמש רואה. כאן תופסים תוכן בעייתי, פורמט שגוי ופרטים שהומצאו.
- על הפעולה — לפני שמשהו קורה בעולם. זו החשובה ביותר בהרבה, וזו שהכי הרבה מערכות מדלגות עליה.
הסדר הזה גם סדר החשיבות, והוא הפוך מסדר תשומת הלב שהתחום נותן. תשובה גרועה על המסך היא מבוכה; חיוב שגוי או מייל שיצא ללקוח הלא נכון הוא נזק.
בקרת פעולה: היחידה שחייבת
אם עושים דבר אחד מהדף הזה, זה הדבר. לפני כל פעולה שמשנה מצב — לחייב, למחוק, לשלוח, לאשר — רץ קוד שמחליט אם היא מותרת, לפי זהות המשתמש ולפי הכללים העסקיים.
מה שהופך את זה לבקרה ולא להנחיה:
- ההרשאה נבדקת במימוש הכלי, לא ברשימת הכלים שנשלחה למודל. אם פעולה מסוכנת זמינה למערכת, היא ניתנת להפעלה — השאלה היחידה היא אם הקוד יאשר אותה.
- היקף מוגבל. כלי שמחפש לקוחות מחפש רק בלקוחות של המשתמש הנוכחי, והמזהה מגיע מההקשר של הבקשה ולא מהפרמטר שהמודל שלח.
- תקרות. סכום מרבי לפעולה, מספר מרבי של הודעות לשעה. מערכת בלי תקרה היא מערכת שבה באג אחד שולח מאה מיילים.
- אישור אנושי על מה שאינו הפיך, ובאישור מוצג התוכן — למי, מה כתוב, כמה זה עולה. "לאשר הפעלת send_email?" הוא כפתור שלוחצים עליו בלי לקרוא אחרי הפעם החמישית.
איך זה נראה בקוד
שכבת הבקרה כולה, בצורתה המינימלית — והדבר שבולט בה הוא כמה מעט ממנה דורש מודל:
def guarded_answer(question, user):
# 1. קלט: גודל בלבד. לא רשימת מילים אסורות.
if len(question) > MAX_INPUT:
return blocked("השאלה ארוכה מדי")
out = model_answer(question, user)
# 2. מבנה — מכני
if not matches_schema(out):
out = retry_once(question, user, error=schema_error(out))
if not matches_schema(out):
return review("פלט לא תקין", out)
# 3. דליפה — מכני
if PII.search(out["answer"]):
return blocked("התשובה הכילה פרטים אישיים")
# 4. עיגון — מכני, ותופס את מה שסכמה לא תופסת
if out["quote"] and out["quote"] not in out["sources_text"]:
out["needs_review"] = True
# 5. שיפוט — רק מה שנשאר, ורק אם צריך
if out.get("needs_review") or is_sensitive(question):
verdict = judge(question, out) # קריטריון אחד, temperature=0
if not verdict["ok"]:
return review(verdict["reason"], out)
log(question, out) # גם מה שעבר, לדגימה
return out
שלושה דברים בקוד הזה חשובים יותר מהמבנה. ארבע מתוך חמש הבדיקות אינן דורשות מודל — הן מיידיות, חינמיות ודטרמיניסטיות. הן מסודרות מהזולה ליקרה, כך שהשיפוט רץ רק על מה שלא נפסל כבר. ולכל בדיקה יש תוצאה מוגדרת — חסימה, ניסיון חוזר או העברה לבדיקה — ולא רק רישום לוג.
בקרת פלט: מה כדאי לבדוק
אחרי שהמודל ענה ולפני שהתשובה יוצאת, ארבע בדיקות מכסות את רוב המקרים — ושלוש מהן אינן דורשות מודל:
- מבנה. האם הפלט תואם את הסכמה. בדיקה מכנית, מיידית וחינם.
- דליפה. האם התשובה מכילה משהו שאסור שיצא — תעודת זהות, מספר כרטיס, מפתח, או תוכן של לקוח אחר. ביטוי רגולרי תופס את רוב זה.
- עיגון במקור. במערכת שעונה מתוך מסמכים: לבקש שהתשובה תצרף את הציטוט שעליו היא נשענת, ואז לבדוק בקוד שהציטוט אכן מופיע בקטע שנשלף. שורה אחת של קוד, ותופסת בדיוק את סוג הטעות שסכמה לא תופסת.
- שיפוט תוכן. רק מה שנשאר — טון, נאותות, האם התשובה בכלל עונה על השאלה. כאן נכנס מודל, ורק כאן.
הסדר הזה אינו עניין של טעם: כל בדיקה שאפשר לעשות בקוד ונעשית במודל היא בדיקה שהוספתם לה עלות, השהיה ואי-ודאות בלי סיבה.
בקרת קלט: מוגבלת יותר משנדמה
הבדיקה על הקלט מפתה כי היא זולה ומוקדמת, וכדאי לדעת את גבולותיה. היא טובה לסינון גס ולא להגנה אמיתית — מה שהיא לא תופסת היא כל ניסוח שלא צפיתם, וזה רוב הניסוחים.
מה שכן שווה לעשות עליה: לחסום קלט חורג בגודל — מסמך של מאה עמודים שנדבק לתיבת שאלה הוא בדרך כלל טעות ותמיד עלות; לזהות תוכן מחוץ לתחום ולנתב אותו, שזו שאלת מוצר ולא אבטחה; ולהסיר מידע אישי לפני שהוא נשלח לספק, כשהמדיניות מחייבת.
ומה שלא שווה: לנסות לתפוס ניסיונות הזרקת הוראות ברשימת מילים. אין דרך לנסח רשימה כזו שתעבוד, וההסתמכות עליה מסוכנת יותר מהיעדרה, כי היא יוצרת תחושת ביטחון. ההגנה האמיתית מפני הזרקת הוראות אינה בקלט אלא בהגבלת מה שהמערכת רשאית לעשות עם הפלט.
הכישלון הנפוץ הוא דווקא הפוך
כשחושבים על בקרה שנכשלה, מדמיינים שמשהו רע עבר. בפועל, הכישלון שמפיל מערכות הוא ההפך: בקרה שחוסמת יותר מדי.
מערכת שמסרבת לענות על שאלה לגיטימית אחת מכל עשר נתפסת מהר מאוד כשבורה, והתוצאה צפויה — מישהו מרפה את הסף, או מכבה את הבקרה לגמרי "עד שנסתדר". וכך מגיעים למצב גרוע מזה שהתחילו ממנו, כי עכשיו אין בקרה בכלל ויש הנחה שיש.
שלוש דרכים להימנע:
- להתחיל בתיעוד בלבד. להפעיל את הבדיקה בלי שתחסום, ולראות שבוע מה היא הייתה חוסמת. כמעט תמיד מתברר שהיא תופסת דברים לגיטימיים.
- למדוד את שני הכיוונים. כמה נחסם שהיה צריך להיחסם, וכמה נחסם שלא. המספר השני כמעט אף פעם לא נמדד, והוא זה שקובע אם המערכת שמישה.
- לחסום צר, לסמן רחב. רק מה שברור לגמרי נחסם; כל השאר יוצא עם סימון לבדיקה. כך שומרים על שימושיות ולא מאבדים ראייה.
אדם בתוך הלולאה, כשזה באמת נחוץ
"אישור אנושי" מופיע בכל מסמך על בטיחות AI, ורוב המימושים שלו לא שווים כלום. אישור הוא בקרה רק אם האדם באמת יכול לשפוט, ורוב המסכים לא מאפשרים את זה.
ארבעה תנאים שהופכים אישור לאמיתי:
- מוצג התוכן, לא הפעולה. לא "לאשר שליחת מייל?" אלא הנמען, הטקסט המלא, והעלות אם יש. אישור שאפשר לשפוט בשתי שניות הוא אישור שבאמת נשפט.
- יש מספיק זמן. תור של מאה אישורים ביום מקבל את אותה תשומת לב שמקבלת הסכמה לתנאי שימוש. אם הנפח כזה — הבעיה אינה המסך, היא שהמשימה לא מתאימה לאישור פרטני, וצריך לדגום או להגדיר כללים אוטומטיים.
- הסירוב עובד. לחיצה על "לא" חייבת לעצור, ולהיכנס למערכת כתוצאה — לא כשגיאה טכנית שגורמת לניסיון חוזר עם ניסוח מעט אחר, שיעבור הפעם כי המאשר התעייף.
- למי שמאשר יש הקשר. אדם שרואה בקשה מנותקת אינו יכול לדעת אם היא סבירה. צרפו את השאלה שהובילה אליה.
ומתי לוותר על אישור פרטני: כשהנפח גבוה והעלות של טעות נמוכה. שם עדיף לדגום — לבדוק אחוז אחד ברצינות — מאשר לאשר הכול באופן אוטומטי ולקרוא לזה פיקוח.
מה שאפשר בקוד — בקוד
הפיתוי הוא להשתמש במודל לכל בדיקה, כי זה קל לכתוב. זו טעות שמשלמים עליה בשלושה מטבעות: עלות, השהיה, ואי-ודאות — שהאחרון הוא החמור, כי בקרה שלפעמים מחליטה אחרת על אותו קלט היא בקרה שאי אפשר לסמוך עליה.
ההבחנה פשוטה. בקוד: סכמה, טווחים, תבניות, אורך, הרשאות, תקרות, האם הערך קיים במסד הנתונים, האם הציטוט מופיע במקור. במודל: האם הטון מתאים, האם התשובה עונה על השאלה, האם התוכן פוגעני בהקשר.
ואפילו כשמשתמשים במודל — קריטריון אחד לכל בדיקה, נימוק לפני הכרעה, וטמפרטורה אפס. בקרה אמורה להיות משעממת וחזרתית; כל יצירתיות בה היא רעש.
עברית: איפה הבקרות נחלשות
רוב הכלים המוכנים בתחום אומנו ונמדדו על אנגלית, וזה מתבטא בארבע דרכים:
- מסנני תוכן מוכנים חלשים יותר בעברית. הם קיימים ועובדים, והדיוק שלהם נמוך מזה שבאנגלית — לשני הכיוונים. אל תניחו שסף שכוונן על אנגלית מתאים.
- רשימות מילים כמעט חסרות ערך. אותיות השימוש הופכות כל מילה לכמה צורות, וניסוח עברי גמיש מספיק כדי לעקוף כל רשימה. זה מחזק את הכלל הקודם: העיגון הוא בהרשאות, לא בסינון טקסט.
- ביטויים רגולריים צריכים התאמה. תעודת זהות ישראלית, מספר טלפון מקומי, מספר חשבון — התבניות שונות מהאמריקאיות שמגיעות מוכנות בכל ספרייה.
- ובדיקת עיגון במקור רגישה לכתיב. השוואת מחרוזות פשוטה תיכשל על "ההזמנה" מול "הזמנה". נרמלו לפני ההשוואה, או השוו באופן סמנטי.
ומעל הכול — ההודעה שהמשתמש מקבל כשבקרה נדלקת צריכה להיות בעברית ולהיות מועילה. "הבקשה נחסמה" מייצר פנייה לתמיכה; "אני לא יכול לתת ייעוץ רפואי, אבל אפשר לעזור עם X" מייצר המשך שיחה.
באיזה סדר לבנות
הטעות הנפוצה בתכנון היא לבנות את כל שכבות הבקרה לפני שיש שימוש. התוצאה היא מערכת שמגינה מפני מה שדמיינתם, וזה כמעט אף פעם לא מה שמגיע בפועל.
סדר שעובד, וכל שלב בו נובע ממה שגיליתם בקודם:
- בקרת פעולה, לפני ההשקה. הרשאות, היקף ותקרות. זו היחידה שאסור לדחות, כי היא מונעת נזק ולא מבוכה.
- מבנה ודליפה, גם הן לפני ההשקה. מכניות, זולות, ואין סיבה לא.
- תיעוד של הכול — כולל מה שעבר. שבוע של קלט אמיתי מלמד יותר מחודש של תכנון.
- ואז לקרוא. מאה תשובות אמיתיות, בעין. כאן מתגלה מה באמת צריך בקרה, וזה תמיד שונה מהרשימה שהכנתם.
- ורק אז שיפוט תוכן, על הקטגוריות שהקריאה חשפה — ובמצב תיעוד קודם, לפני שהוא חוסם.
הסדר הזה חוסך את שתי הטעויות היקרות: מערכת בייצור בלי בקרת פעולה, ושכבת סינון מסובכת שחוסמת דברים לגיטימיים וששום נתון לא הצדיק מראש.
איך יודעים שהבקרות עובדות
בקרה שאיש לא מדד היא הנחה. שלושה מדדים, וכל אחד עונה על שאלה אחרת:
- שיעור ההפעלה. באיזו תדירות כל בדיקה נדלקת. בקרה שלא נדלקה מעולם היא או מיותרת או שבורה — ושתי האפשרויות שוות בדיקה.
- חסימות שגויות. מתוך מה שנחסם, כמה היה לגיטימי. זה המדד שקובע אם המערכת שמישה, והוא דורש לקרוא דגימה ידנית — אין לזה קיצור דרך.
- מה עבר. דגימה של תשובות שהבקרות אישרו, שנקראת בעין אנושית. זו הדרך היחידה לגלות מה הבדיקות לא תופסות.
והדרך לבנות את זה: מערך קבוע של מקרים — חלקם צריכים להיחסם וחלקם בהחלט לא — שרץ אחרי כל שינוי בסף או בהנחיה. בלעדיו כל כוונון הוא ניחוש, וזו בדיוק הנקודה שבה מערכות מתדרדרות: מישהו הרפה סף, איש לא מדד, והמערכת נעשתה מתירנית בשקט.
מה זה עולה
לבקרות יש מחיר ושווה לתכנן אותו במקום לגלות אותו. בדיקה בקוד היא כמעט חינם; בדיקה במודל היא קריאה נוספת — עלות והשהיה על כל בקשה.
שלוש דרכים לשמור על זה סביר: למצות קודם את הבדיקות שאינן דורשות מודל, כי הן תופסות חלק ניכר; להריץ שיפוט רק על מה שלא נפסל כבר, כלומר לסדר את הבדיקות מהזולה ליקרה; ומודל קטן יותר לשיפוט — בדיקה לפי קריטריון אחד ברור היא משימה קלה בהרבה מהמשימה שנבדקת, ולעתים קרובות מודל קטן מספיק.
ובצד ההשהיה: בדיקת פלט שרצה אחרי התשובה מוסיפה זמן שהמשתמש מרגיש. במערכות שמזרימות תשובה זה מחייב החלטה — לבדוק על המלא לפני שמציגים, או להציג ולעצור אם צריך. אין תשובה אחת, ויש חובה להחליט במפורש במקום לגלות אחרי שמשתמש ראה חצי תשובה שנמחקה.
איפה זה נשבר לאורך זמן
בקרות לא נשברות ביום שבו הן נכתבות — הן נשחקות. שלושה דפוסים שחוזרים בכל מערכת שרצה מספיק זמן:
הסף שהורפה ולא הוחזר. מישהו התלונן על חסימה, מישהו הרפה, אף אחד לא מדד. חודש אחרי, הבקרה כמעט לא נדלקת ואיש לא יודע שהיא כבר לא מגינה. שינוי סף הוא שינוי קוד — הוא צריך להיות מתועד ולהריץ את מערך המקרים.
הקלט שזז. הבקרות כוונו על השאלות שמשתמשים שאלו בהשקה. חצי שנה אחרי הם שואלים דברים אחרים, והכיסוי נעשה חלקי בלי ששום דבר נשבר גלויה. זו הסיבה לדגום גם את מה שעובר, ולא רק את מה שנחסם.
והספק שעדכן. המודל מתנהג מעט אחרת, והפלט שעבר את הבדיקות אתמול עובר אותן אחרת היום. מעבר גרסת מודל הוא שינוי שמצדיק הרצה מלאה של מערך הבקרות — ולא רק בדיקה שהתשובות עדיין נשמעות סבירות.
המשותף לשלושתם: הם שקטים. בקרה שנשחקה לא מדווחת על עצמה, ולכן ביקורת תקופתית קצרה — לקרוא חמישים מקרים פעם ברבעון — שווה יותר מכל התרעה שתגדירו.
מתי לא צריך
- כשהפלט הולך רק אליכם. כלי פנימי שאתם מפעילים על עצמכם לא צריך שכבת סינון — אתם הבקרה.
- לפני שיש שימוש. בקרות שנבנו לפני שראיתם קלט אמיתי מגינות ממה שדמיינתם, וזה כמעט אף פעם לא מה שמגיע.
- כשהמודל אינו פועל בעולם. מערכת שרק מסכמת טקסט לא צריכה בקרת פעולה, כי אין פעולה.
- כשהתשובה הנכונה היא לא לבנות את זה. אם משימה דורשת כל כך הרבה בקרות שהיא כבר לא חוסכת עבודה — זו התשובה, ועדיף לשמוע אותה מוקדם.
שלוש שאלות לפני שמעלים לייצור
לא רשימת תיוג ארוכה — שלוש שאלות שאם יש להן תשובה, רוב השאר מסתדר:
- מה הדבר הגרוע ביותר שהמערכת הזו יכולה לעשות? לא התשובה הכי מביכה — הפעולה הכי יקרה. אם התשובה היא "לחייב לקוח" או "לשלוח מאה מיילים", שם צריכה להיות התקרה והאישור, וכל השאר משני.
- אם מישהו ינסה לגרום לה לחרוג מתפקידה, מה יעצור אותו? אם התשובה מתחילה ב"כתוב בהנחיה ש..." — אין תשובה. ההגבלה חייבת להיות במימוש.
- איך תדעו שבקרה הפסיקה לעבוד? אם אין תשובה, היא תפסיק ואיש לא ידע. זו השאלה שהכי קל לדלג עליה והיא זו שקובעת מה יקרה בעוד חצי שנה.
טעויות שחוזרות
- בקרה שהיא משפט בפרומפט. בקשה, לא הגבלה — וניתנת לעקיפה.
- בלי החלטה מה קורה כשהיא נדלקת. אז זה לוג, ואפשר לקרוא לזה בשמו.
- לדלג על בקרת פעולה. השכבה היחידה שמונעת נזק אמיתי.
- לבדוק הרשאה לפי פרמטר שהמודל שלח. הזהות מגיעה מהבקשה, לא מהפלט.
- מודל לכל בדיקה. עלות, השהיה, ואי-ודאות במקום תנאי פשוט.
- למדוד רק מה שנחסם. חסימות שגויות הן מה שמפיל מערכות.
- רשימת מילים אסורות. לא עובדת בעברית, ומייצרת ביטחון שווא.
- הודעת חסימה כללית. מייצרת פניות לתמיכה במקום המשך שיחה.
- סף שכוונן פעם אחת. הקלט זז, והמערכת נעשית מתירנית בשקט.
- בלי תקרות. באג אחד בלולאה שולח מאה מיילים.