Structured output הוא תשובה בצורה שהחלטתם עליה מראש: אובייקט JSON עם מפתחות בעלי שם, טבלה עם עמודות קבועות, או תבנית עם אותן כותרות בכל פעם. צריך אותו בכל פעם שתוכנה קוראת את התשובה, והוא עוזר גם כשאדם קורא, כי אז כל תשובה נראית אותו דבר וקל לסרוק או להשוות.
הטריק הוא לתאר את הצורה בדיוק כזה שלא יישאר למודל מה לבחור. המילה "JSON" לבדה היא לא תיאור. הבלוק שלמטה מחלץ פרטים מדיווח על באג. השוו בקשה ל-"פרטים עיקריים" לבקשה שמפרטת את הסכמה.
אלה הפרטים העיקריים מהדיווח על הבאג:
- בעיה: האפליקציה קורסת בייצוא פרויקטים
- טריגר: לחיצה על ייצוא בפרויקטים עם יותר מ-50 תמונות
- התחיל: אחרי העדכון האחרון
- פלטפורמה: Android 14
- גרסת אפליקציה: 3.2.0
- השפעה: גבוהה, כי זה חוסם מסירה ללקוח
נראה שפרויקטים קטנים יותר מיוצאים בלי בעיות.
התשובה בטקסט חופשי מדויקת וקריאה, אבל אין שתי הרצות שישתמשו באותן תוויות, החומרה היא משפט ולא ערך, ותוכנה תצטרך לנחש איפה כל שדה מתחיל. תשובת ה-JSON יכולה להיכנס ישר למערכת מעקב באגים. שימו לב שהתשובה עדיין הגיעה בתוך בלוק קוד: אפליקציות צ'אט בדרך כלל עוטפות JSON כך, וזה משנה כשמפענחים אותו בקוד.
איך מבקשים JSON
בקשת JSON טובה עונה על כל שאלה שהמודל היה עונה עליה במקומכם:
- כל מפתח, באיות מדויק. כתבו את שמות המפתחות במירכאות בדיוק כפי שהם צריכים להופיע. אמרו "המפתחות האלה בדיוק" כדי שהמודל לא יוסיף מפתחות נוספים.
- הסוג של כל ערך. string, מספר, בוליאני, מערך של strings, אובייקט מקונן.
- הערכים המותרים לכל שדה עם קבוצה קבועה, כמו חומרה או קטגוריה. בלי הרשימה, תקבלו "High", "high", "severe" ו-"P1" על פני ארבע הרצות.
- מה לעשות כשחסר ערך בקלט. אם לא תכתבו "null אם לא צוין", המודל נוטה למלא את החור בניחוש סביר, וגרסת אפליקציה מנוחשת נראית בדיוק כמו אמיתית.
- כלום מסביב. "החזר רק את ה-JSON, בלי טקסט לפניו או אחריו" מסיר את משפט הפתיחה הידידותי.
כשהצורה מקוננת או לא שגרתית, להראות אובייקט דוגמה שלם עובד טוב יותר מלתאר אותה. זה few-shot prompting שמיושם על פורמט. שמרו על ערכי הדוגמה שונים בבירור מהקלט האמיתי, כדי שהמודל לא יעתיק אותם.
פרומפט חילוץ לשימוש חוזר
הבלוק הזה מכיל את אותה בקשה מחולקת לחלקים. כבו את חלק הפורמט, והמודל עדיין יחזיר JSON, כי המגבלות מבקשות זאת, אבל הוא יבחר שמות מפתחות משלו, כמו title במקום job_title, וקוד שמצפה למפתחות שלכם יישבר. חלק המגבלות הוא מה שמונע ממנו להשלים מספר טלפון חלקי או להסיק חברה מהדומיין של כתובת מייל. הדביקו חתימה אמיתית בשדה הקלט כדי לבדוק אותו.
{
"name": "string",
"job_title": "string או null",
"company": "string או null",
"email": "string או null",
"phone": "string או null"
}{
"name": "Priya Nair",
"job_title": "ראש תחום דאטה",
"company": "Northwind Labs",
"email": "priya.nair@northwind.example",
"phone": null
}
טבלאות ותבניות קבועות
Structured output הוא לא רק בשביל תוכנות. כשאתם קוראים את התשובה בעצמכם, טבלת markdown או תבנית קבועה נותנות את אותו יתרון: אתם יודעים איפה כל פיסת מידע תהיה עוד לפני שאתם מסתכלים.
| סוג | מסודר | ניתן לשינוי | מאפשר כפילויות | שימוש נפוץ |
|---|---|---|---|---|
| list | כן | כן | כן | רצף שמוסיפים לו, מוחקים ממנו או ממיינים אותו |
| tuple | כן | לא | כן | קבוצה קבועה של ערכים, כמו קואורדינטות |
| set | לא | כן | לא | הסרת כפילויות ובדיקת שייכות מהירה |
תבנית עובדת באותה צורה לטקסט ארוך יותר: תנו את הכותרות לפי הסדר ואמרו מה נכנס תחת כל אחת. "ענה עם שלוש תוויות מודגשות: סיבה, תיקון, איך לבדוק" מפיק את אותן שלוש תוויות בכל פעם, מה שהופך אצווה של תשובות לקלה להשוואה.
JSON mode ב-API
לכמה ממשקי API של מודלים יש הגדרה שמכריחה JSON תקין תחבירית. ב-OpenAI Python SDK זה response_format. JSON mode דורש שהמילה "JSON" תופיע איפשהו בהודעות שלכם, ולכן פרומפט המערכת שלמטה מזכיר אותה ומפרט את המפתחות.
import json
from openai import OpenAI
client = OpenAI()
MODEL = "your-model-id" # e.g. from your provider's model list
report_text = "Since yesterday's update the app crashes when I tap Export..."
response = client.chat.completions.create(
model=MODEL,
response_format={"type": "json_object"},
messages=[
{
"role": "system",
"content": (
"Extract the bug report into JSON with the keys "
"summary (string), severity (one of low, medium, high, critical) "
"and steps_to_reproduce (array of strings)."
),
},
{"role": "user", "content": report_text},
],
)
data = json.loads(response.choices[0].message.content)
JSON mode מבטיח שהטקסט יעבור פענוח (אלא אם התשובה נקטעת במגבלת הטוקנים), לא שהוא תואם לסכמה שלכם. מפתח עדיין יכול להיות חסר, או שהחומרה עדיין יכולה להיות "urgent". כמה ספקים מקבלים גם JSON Schema מלא, כפורמט פלט או כסכמת הקלט של הגדרת כלי (function), וחלק מהמצבים האלה מגבילים את התשובה לסכמה. בדקו בתיעוד של הספק שלכם את הפרמטר המדויק, כי התכונות האלה שונות בין ממשקי API.
בדקו את התוצאה בקוד
התייחסו ל-JSON של המודל כמו לכל קלט שמגיע מחוץ לתוכנה: פענחו אותו, ואז בדקו אותו. הפענוח תופס תחביר שבור. בדיקה תופסת אובייקט תקין עם תוכן שגוי.
ALLOWED_SEVERITIES = {"low", "medium", "high", "critical"}
def problems(data):
if not isinstance(data, dict):
return ["the answer must be a JSON object"]
found = []
if not isinstance(data.get("summary"), str):
found.append("summary must be a string")
if data.get("severity") not in ALLOWED_SEVERITIES:
found.append("severity must be low, medium, high or critical")
steps = data.get("steps_to_reproduce")
if not isinstance(steps, list) or not all(isinstance(s, str) for s in steps):
found.append("steps_to_reproduce must be an array of strings")
return found
כשהבדיקה נכשלת, ניסיון חוזר אחד מתקן את זה לעתים קרובות: שלחו למודל את הפלט שלו יחד עם רשימת הבעיות ובקשו JSON מתוקן. הגבילו את מספר הניסיונות החוזרים, ותעדו את הכישלונות בלוג, כי שדה שנכשל לעתים קרובות הוא סימן שהפרומפט לא ברור. בפרויקטים גדולים יותר, ספריית ולידציה כמו Pydantic או validator של JSON Schema מחליפים את הפונקציה שנכתבה ידנית.
שני הרגלים נוספים מונעים שגיאות שקטות. קבעו את מגבלת טוקני הפלט גבוה מספיק לתשובה הגדולה ביותר שאתם מצפים לה, כי אובייקט קטוע לעולם לא עובר פענוח. וכשהקלט ארוך או מגיע ממשתמשים, הפרידו אותו מההוראות שלכם עם מפרידים או תגיות XML, כדי שהסיכוי שטקסט בתוכו ייקרא כהוראה יהיה קטן יותר. אם פרומפט אחד צריך גם לחשוב וגם להפיק JSON, שקלו prompt chaining: תנו לשלב אחד לחשוב בטקסט חופשי ולשלב שני להפוך את התוצאה למבנה.
שאלות נפוצות
איך גורמים ל-ChatGPT להחזיר JSON?
אמרו שהתשובה חייבת להיות JSON, פרטו כל מפתח עם הסוג שלו, ותנו את הערכים המותרים לכל שדה עם קבוצה קבועה. הוסיפו "החזר רק את ה-JSON, בלי טקסט לפניו או אחריו." ב-API, הפעילו גם את JSON mode עם response_format={"type": "json_object"}, שדורש שהמילה JSON תופיע בהודעות שלכם.
למה המודל מוסיף טקסט מסביב ל-JSON?
מודלי צ'אט מאומנים להיות שיחתיים, ולכן הם פותחים לעתים קרובות במשפט כמו "הנה ה-JSON" או עוטפים את האובייקט בבלוק קוד של markdown. בקשו את ה-JSON לבדו, ובקוד השתמשו ב-JSON mode של ה-API או הסירו את בלוק הקוד שמסביב לפני הפענוח.
מה זה JSON mode?
JSON mode היא הגדרה ב-API שגורמת למודל להפיק JSON תקין תחבירית, כל עוד התשובה לא נקטעת במגבלת טוקני הפלט. היא לא גורמת למודל לעקוב אחרי הסכמה שלכם: מפתחות עדיין יכולים להיות חסרים, מאויתים לא נכון או מהסוג הלא נכון. חלק מהספקים מציעים גם מצב קפדני יותר שמקבל JSON Schema מלא ומגביל את הפלט אליו.
האם LLM תמיד יכול להחזיר JSON תקין?
לא מפרומפט בלבד. גם הוראה ברורה נכשלת מדי פעם, ותשובה שנקטעה במגבלת טוקני הפלט תמיד לא תקינה. פענחו כל תשובה עם parser אמיתי של JSON, בדקו את השדות שאתם צריכים, ונסו שוב או היכשלו בקול רם כשהבדיקה לא עוברת.