הגרסה הראשונה של פרומפט היא טיוטה. לעיתים קרובות היא נותנת משהו קרוב למה שאתם רוצים, ואת הפער בין קרוב לנכון סוגרים באיטרציות: משנים את הפרומפט בכוונה, מריצים אותו שוב על אותו קלט, ובודקים אם השינוי עזר. כשעושים את זה ברישול, איטרציה הופכת לניסוח מחדש אקראי עד שפלט אחד בר מזל נראה בסדר. כשעושים את זה כניסוי קטן, מקבלים פרומפט שעובד על מאה הקלטים הבאים, לא רק על זה שבדקתם.
לשיטה יש חמישה שלבים: להגדיר איך נראית תוצאה טובה, לשמור קלטי בדיקה קבועים, לשנות דבר אחד בכל פעם, להשוות פלטים ולדעת מתי לעצור.
להגדיר איך נראית תשובה טובה
לפני שעורכים משהו, כתבו מה הפלט חייב לעשות, כבדיקות שאפשר לענות עליהן בכן או לא. את "הודעת commit טובה" אי אפשר לבדוק. את אלה אפשר:
- שורת הנושא קצרה מ-50 תווים.
- היא עוקבת אחרי פורמט Conventional Commits (
fix:,feat:וכן הלאה). - הגוף מסביר למה השינוי היה נחוץ, לא מה שה-diff כבר מראה.
- היא מזכירה את מספר ה-issue כשיש כזה.
לקריטריונים יש שני תפקידים. הם אומרים לכם מה להוסיף לפרומפט, כי כל אחד מהם הוא לעיתים קרובות הוראה חסרה. והם מונעים מכם לשפוט פלטים לפי כמה נעים הם נשמעים, שזו הדרך הקלה ביותר לקבל תשובה בטוחה בעצמה אבל שגויה.
לשמור סט קטן של קלטי בדיקה
בחרו שלושה עד עשרה קלטים והשאירו אותם קבועים בזמן העבודה. כללו מקרים טיפוסיים, כמה מקרים מביכים (שדה ריק, קלט ארוך מאוד, שפה שהפרומפט לא ציפה לה), וכל קלט שנכשל בעבר. פרומפט שכוונן על דוגמה אחת נוטה להתאים לדוגמה הזו ולהיכשל בבאה.
כתבו את הפרומפט כתבנית עם מקום לקלט, כך שכל גרסה תרוץ על בדיוק אותו חומר.
לשנות דבר אחד בכל פעם
כשמשנים את התפקיד, את הפורמט ואת הדוגמאות ביחד והפלט משתפר, אי אפשר לדעת איזה שינוי עשה את זה, ואולי אתם שומרים שתי עריכות חסרות תועלת. שנו דבר אחד, הריצו, השוו. הלשוניות למטה הן שלוש גרסאות של אותו פרומפט, כל אחת שינוי יחיד מהקודמת.
- if len(password) > 8:
+ if len(password) >= 8:
return Trueעדכון בדיקת אורך הסיסמה
ה-commit הזה מעדכן את לוגיקת בדיקת הסיסמה על ידי שינוי אופרטור ההשוואה מ-> ל->=. כתוצאה מכך, סיסמאות שאורכן 8 תווים בדיוק ייחשבו מעכשיו תקינות. השינוי משפר את דיוק הבדיקה ומספק חוויה טובה יותר למשתמשים שיוצרים חשבונות.
גרסה 1 נכשלת בקריטריונים 2 ו-3: לנושא אין קידומת סוג, והגוף מספר מחדש את ה-diff במילים רבות יותר. גרסה 2 מוסיפה את דרישות הפורמט ועוברת את קריטריונים 1 ו-2, אבל הגוף שלה עדיין מתאר את ה-diff. העריכה המתבקשת הבאה היא הוראה נוספת ("הסבר למה"), אבל המודל לא יכול להסביר סיבה שמעולם לא ניתנה לו. גרסה 3 מוסיפה במקום זאת את ההקשר החסר, והגוף וההפניה ל-issue מגיעים בלי שביקשו אותם. זה השיעור הנפוץ ביותר של איטרציה: כשהוראה לא מתקנת פלט, לפרומפט בדרך כלל חסר מידע, לא הדגשה.
להשוות פלטים זה לצד זה
הריצו כל גרסה על כל קלט בדיקה, והריצו כל אחת יותר מפעם אחת, כי מודלי צ'אט דוגמים את המילים שלהם ושתי הרצות של אותו פרומפט שונות זו מזו. שימו את הפלטים זה ליד זה ובדקו אותם מול הקריטריונים שלכם, לא מול מה שאתם זוכרים מההרצה הקודמת.
לקריטריונים מדויקים, קריאה שנייה למודל יכולה לבצע סבב ראשון של מתן ציונים. הדביקו את הפלטים בפרומפט מדרג שבו הקריטריונים כתובים במפורש:
| קריטריון | פלט A | פלט B |
|---|---|---|
| 1. נושא קצר מ-50 תווים | עבר: הנושא באורך 37 תווים | עבר: אותו נושא, 37 תווים |
| 2. Conventional Commits | עבר: מתחיל ב-"fix:" | עבר: מתחיל ב-"fix:" |
| 3. הגוף מסביר למה | נכשל: "שינוי בדיקת האורך מ-> ל->=" חוזר על ה-diff | עבר: "משתמשים שעקבו אחרי ההוראות לא הצליחו להירשם" |
| 4. מזכיר את ה-issue | נכשל: אין מספר issue | עבר: "Fixes #412" |
התייחסו למודל מדרג כעוזר, לא כשופט. Zheng et al. 2023, "Judging LLM-as-a-Judge with MT-Bench and Chatbot Arena", תיעדו הטיות אצל מודלים שופטים, כולל העדפה לתשובות ארוכות יותר ולתשובה שנמצאת במיקום מסוים. שמרו על קריטריונים שאפשר לבדוק, בקשו ראיות מצוטטות, החליפו את הסדר של A ו-B כשאתם משווים שניים, וקראו בעצמכם מדגם של הפלטים. בדיקות כמו ספירת תווים אמינות יותר כשורת קוד מאשר כשיפוט של מודל.
לתקן את הפרומפט, לא את השיחה
בצ'אט מפתה לתקן את התשובה בהודעות המשך: "קצר יותר", "לא, תזכיר את ה-issue", "תשתמש בפורמט השני". כך מקבלים פלט טוב אחד ומשאירים את הפרומפט גרוע כמו שהיה. כשתיקון עובד, העבירו אותו לתוך הפרומפט והריצו את הפרומפט מההתחלה. בפעם הבאה שתצטרכו את התוצאה, תדביקו הודעה אחת במקום לחזור על חמש.
שמרו גרסאות ישנות עם הערה של שורה אחת על מה השתנה ומה זה תיקן. קובץ טקסט פשוט מספיק. כשעריכה מאוחרת יותר מחמירה את המצב, אפשר לחזור אחורה במקום לנסות להיזכר מה הפרומפט אמר פעם.
להריץ השוואה בקוד
ברגע שפרומפט רץ דרך API, סקריפט קצר יכול להפיק את התצוגה זה לצד זה לכל גרסה ולכל קלט בדיקה. הסקריפט הזה משתמש ב-OpenAI Python SDK וכותב קובץ markdown שאפשר לקרוא מההתחלה עד הסוף.
from openai import OpenAI
client = OpenAI()
MODEL = "your-model-id" # e.g. from your provider's model list
# each prompt file contains {input} where the test diff goes
PROMPTS = {
"v2": open("prompts/commit_v2.txt").read(),
"v3": open("prompts/commit_v3.txt").read(),
}
TESTS = [open(f"tests/diff_{i}.txt").read() for i in range(1, 6)]
with open("results.md", "w") as out:
for i, test in enumerate(TESTS, 1):
out.write(f"## Test {i}\n\n")
for name, template in PROMPTS.items():
for run in (1, 2):
response = client.chat.completions.create(
model=MODEL,
messages=[{"role": "user", "content": template.replace("{input}", test)}],
)
out.write(f"### {name}, run {run}\n\n{response.choices[0].message.content}\n\n")
מתי לעצור
עצרו כשכל קריטריון עובר על כל קלט בדיקה לאורך כמה הרצות. להוסיף עוד אחרי זה בעיקר מוסיף אורך, וכל הוראה נוספת היא עוד דבר שעלול להתנגש באחרים.
עצרו גם כשהשינויים מתחילים להחליף כשלים: עריכה אחת מתקנת את בדיקה 2 ושוברת את בדיקה 4, והבאה הופכת את זה. הדפוס הזה אומר שמבקשים מהפרומפט לעשות משהו שהוראה אחת לא יכולה לקבע. הדרכים המקובלות לצאת מזה הן להראות את הפורמט עם כמה דוגמאות (few-shot prompting), לפצל את העבודה לשלבים עם prompt chaining, או להעביר את החלקים הדטרמיניסטיים, כמו ספירת תווים או בדיקת פורמט, לקוד שבודק את הפלט. אם נגמרו לכם הרעיונות, meta prompting יכול לעזור: תנו למודל את הפרומפט, את הקלט ואת הפלט הגרוע, ושאלו איזה חלק בפרומפט הוא הגורם הסביר ביותר.
שאלות נפוצות
איך משפרים פרומפט שנותן תשובות גרועות?
הסתכלו על התשובה הגרועה ותנו שם למה שלא בסדר בה: פורמט שגוי, עובדה חסרה, קהל לא נכון, ארוכה מדי. אחר כך מצאו מה הפרומפט לא אמר שהיה מונע את זה, הוסיפו את הדבר הזה בלבד, והריצו את הגרסה החדשה על אותו קלט. תשובות גרועות נובעות לעיתים קרובות יותר מהקשר חסר או מהוראת פורמט חסרה מאשר מהניסוח.
כמה קלטי בדיקה צריך כדי לבדוק פרומפט?
לפרומפט שתשתמשו בו שוב, שלושה עד עשרה בדרך כלל מספיקים: כמה מקרים טיפוסיים, מקרה קצה אחד או שניים (קצר מאוד, ארוך מאוד, חריג) וקלט אחד שכבר השתבש בעבר. השאירו אותם קבועים בזמן האיטרציה, כדי ששינוי בפלט יגיע מהפרומפט ולא מקלט אחר.
למה אני מקבל תשובה אחרת כשאני מריץ שוב את אותו פרומפט?
מודלי צ'אט דוגמים כל מילה מתוך התפלגות הסתברויות, ולכן הפלטים משתנים בין הרצות. כשמשווים שתי גרסאות של פרומפט, הריצו כל אחת יותר מפעם אחת על אותם קלטים. הבדל שמופיע בכל הרצה הוא כנראה אמיתי; הבדל שמופיע בהרצה אחת עשוי להיות מקרי. דרך API אפשר גם להוריד את ה-temperature כדי לצמצם את השונות.
אפשר להשתמש ב-AI כדי לתת ציון לפלטים של הפרומפט שלי?
כן, לקריטריונים שאפשר לנסח במדויק, כמו "הגוף מסביר למה השינוי היה נחוץ" או "מזכיר את מספר ה-issue". תנו למודל המדרג את הקריטריונים המדויקים שלכם ובקשו עבר או נכשל לכל קריטריון, עם ציטוט כראיה. למודלים מדרגים יש הטיות ידועות, כולל העדפה של תשובות ארוכות יותר והעדפה של מיקום אחד על פני השני, אז קראו בעצמכם חלק מהפלטים והחליפו את הסדר כשאתם משווים שניים.
מתי להפסיק לשפר פרומפט?
עצרו כשכל קריטריון עובר על כל קלט בדיקה לאורך כמה הרצות, או כשכל שינוי חדש מתקן מקרה אחד ושובר אחר. בשלב הזה הבעיה בדרך כלל כבר לא בפרומפט: ייתכן שהמשימה צריכה דוגמאות, פיצול לשלבים או בדיקה בקוד.