פוסט
Guardrails: בדיקות סביב המודל
Guardrails בשכבות לאפליקציות LLM: סינון קלט עם מודל קטן, בדיקה והשחרה של הפלט, בדיקת פעולות, מגבלות, ומה להציג למשתמש כשבדיקה נכשלת.
Guardrails הם הסט הרחב של בדיקות ששומרות את הקלט והפלט של מערכת בגבולות, גם מפני מתקפות וגם מפני טעויות רגילות: מספר מומצא, מספר טלפון שדלף, בקשה שלא קשורה לנושא, תשובה בפורמט הלא נכון. המודל עצמו הוא שכבה אחת. השאר הוא קוד שאתם כותבים סביבו.
דוגמת ההשחרה הורצה עם Python 3.9 והפלט שלה אמיתי. קוד הסינון מבוסס על המדריך הרשמי להגנה מפני jailbreaks ו-prompt injection ועל התיעוד של structured outputs, ולא הורץ.
שכבות, לא חומה אחת
אף בדיקה לא תופסת הכול, וכל אחת טועה לפעמים. שימו כמה בדיקות זולות ופשוטות בשורה, כשכל אחת יכולה לחסום, לתקן או ליפול לחלופה בטוחה:
בדיקות קלט
לפני שבקשה מגיעה למודל הראשי:
- ולידציה פשוטה: מגבלות אורך, סוגי קבצים מותרים, מגבלת קצב לכל משתמש. זול ואמין.
- סינון עם מודל קטן: המדריך הרשמי מציע מודל קל עם פלט מובנה, שעונה על שאלה צרה אחת, כמו “האם זה מזיק?” או “האם זה קשור למוצר שלנו?”:
import anthropic
from pydantic import BaseModel
client = anthropic.Anthropic()
class Screen(BaseModel):
on_topic: bool
harmful: bool
def screen(user_text):
response = client.messages.parse(
model="claude-haiku-5-5",
max_tokens=200,
messages=[{"role": "user", "content":
"A user sent this message to a customer support bot for a software product:\n"
f"<message>{user_text}</message>\n"
"Is it on-topic for product support? Is it harmful, illegal or explicit?"}],
output_format=Screen,
)
if response.stop_reason == "refusal":
return Screen(on_topic=False, harmful=True) # לפי התיעוד: להתייחס לסירוב כמזיק
return response.parsed_output
המדריך מציין שמסווגי הבטיחות של המודל הקטן עצמו יכולים לסרב לבקשת הסינון, וממליץ להתייחס ל-stop_reason: "refusal" כאל קביעה שהתוכן מזיק, כמו שהקוד עושה.
בדיקות פלט
אחרי שהמודל עונה, לפני שהמשתמש רואה:
- פורמט: עם פלט מובנה הסכמה מובטחת, אבל עדיין בדקו את הערכים החשובים (תאריך בטווח, מחיר שקיים בקטלוג שלכם).
- עיגון: בתשובות RAG, בדקו שהטענות נתמכות בטקסט שנשלף, עם השופט מהפוסט על evals, על מדגם או על תשובות בסיכון גבוה.
- מידע רגיש: אל תציגו מידע אישי שהמשתמש לא אמור לראות. שכבה ראשונה ופשוטה היא השחרה לפי תבניות:
import re
PATTERNS = {
"email": re.compile(r"[\w.+-]+@[\w-]+\.[\w.-]+"),
"card": re.compile(r"\b(?:\d[ -]?){13,16}\b"),
"phone": re.compile(r"\b0\d{1,2}-?\d{7}\b"),
}
def redact(text):
for name, pattern in PATTERNS.items():
text = pattern.sub(f"[{name.upper()}]", text)
return text
answer = "Contact dana@example.com or 054-1234567. Card on file: 4111 1111 1111 1111."
print(redact(answer))
Contact [EMAIL] or [PHONE]. Card on file: [CARD].
ביטויים רגולריים מפספסים דברים ולפעמים תופסים יותר מדי (תבנית הטלפון שלמעלה מתאימה לפורמט מקומי אחד), אז התייחסו אליהם כשכבה אחת ולא כהבטחה. כלים ייעודיים לזיהוי מידע אישי עושים את זה טוב יותר, וההגנה הטובה ביותר היא לא לשלוף מלכתחילה מידע שהמשתמש לא רשאי לראות.
בדיקות פעולה
בסוכנים, ה-guardrail החשוב ביותר יושב בין ההחלטה של המודל לבין העולם האמיתי:
- רשימות מותרות: אילו כלים, אילו תיקיות, אילו דומיינים, אילו נמענים.
- בדיקת ארגומנטים בקוד: כלי החזר כספי דוחה סכומים מעל מגבלה, לא משנה מה המודל ביקש.
- אישור אנושי לכל דבר בלתי הפיך או שיוצא החוצה: הראו בדיוק מה יקרה (“לשלוח את המייל הזה ל-3 האנשים האלה?”) וחכו ל"כן".
- תקציבים: מקסימום צעדים, טוקנים וכסף לכל משימה ולכל משתמש.
כשבדיקה נכשלת
הערך של guardrail תלוי במה שקורה אחרי שהוא נדלק:
- תגידו למשתמש משהו מועיל: “אני יכול לעזור רק בשאלות על המוצר” עדיף על שגיאה כללית.
- תיפלו לחלופה בטוחה: עונים בלי החלק המסוכן, מעבירים לנציג אנושי, או שואלים שאלת הבהרה.
- תרשמו את זה בלוג עם ה-trace, ועקבו אחרי השיעורים. עלייה פתאומית בחסימות פירושה מתקפה, או בדיקה קשוחה מדי.
- תבדקו גם את ה-guardrails: הוסיפו לסט ה-eval דוגמאות שצריכות להיחסם ודוגמאות שצריכות לעבור, כדי לדעת גם שהם עוצרים את המקרים הרעים וגם שהם לא עוצרים משתמשים טובים.
הפוסט הבא מחבר הכול: מפת החלטות לבחירה בין prompting, RAG, כלים, סוכנים ו-fine-tuning.