Structured Output
למה טקסט חופשי לא תמיד מספיק
בכל הדוגמאות עד כה קיבלנו את תשובת ה-agentLLM שמופעל בלולאה, עם מטרה וסט כלים, ובכל סיבוב מחליט אם להפעיל כלי נוסף או לסיים ולהחזיר תשובה. מתוך AI Agents, Agents והלולאה האגנטית. כטקסט חופשי — result.messages.at(-1)?.content. זה מצוין לצ'אט מול משתמש אנושי, אבל ברגע שקוד אחר צריך לצרוך את התשובה — לצייר כרטיס ב-UI, לשמור בבסיס נתונים, או להעביר לפונקציה אחרת — טקסט חופשי הוא מקור בעיות: הניסוח יכול להשתנות מעט בכל הרצה, ופענוח שלו עם regex או parsing ידני הוא שביר ולא אמין.
פלט מובנהסכימה שמתארת את צורת התשובה הסופית, כך שה-agent מחזיר אובייקט שתואם לה ולא רק טקסט חופשי. מתוך LangChain, Structured Output. (structured output) פותר את זה: במקום טקסט, ה-agent מחזיר אובייקט שתואם סכימה שהגדרתם מראש, מאומת אוטומטית — בדיוק כמו שקבלת JSON תקין מ-API הרבה יותר נוח לעבודה מאשר לפרסר תשובה בשפה טבעית.
הגדרת פלט מובנה עם responseFormat ו-zod
מעבירים ל-createAgent שדה responseFormat עם סכימת zod שמתארת את צורת התשובה הסופית הרצויה. ה-agentLLM שמופעל בלולאה, עם מטרה וסט כלים, ובכל סיבוב מחליט אם להפעיל כלי נוסף או לסיים ולהחזיר תשובה. מתוך AI Agents, Agents והלולאה האגנטית. עדיין מריץ את הלולאה האגנטיתהמחזור של סוכן: תצפית, תכנון ופעולה, כשה-harness מבצע את הכלי ומוסיף את התוצאה לקונטקסט לפני הסיבוב הבא. מתוך AI Agents, Agents והלולאה האגנטית. הרגילה (tool calls וכו') כרגיל, אבל בסיום הוא מחזיר גם result.structuredResponse — אובייקט שכבר תואם את הסכימה שהגדרתם.
import { z } from "zod";
const WeatherReport = z.object({
city: z.string(),
temperatureCelsius: z.number(),
recommendation: z.string(),
});
const agent = createAgent({
model: "anthropic:claude-sonnet-5",
tools: [getWeather],
responseFormat: WeatherReport,
});
const result = await agent.invoke({
messages: [{ role: "user", content: "מה מזג האוויר בתל אביב?" }],
});
console.log(result.structuredResponse);
// → { city: "תל אביב", temperatureCelsius: 22, recommendation: "..." }במה זה שונה מ-tool עם zod schema
בפרק הקודם ראינו tool עם schema של zod — זו הגדרה של פעולה שהמודל יכול לבחור להפעיל תוך כדי הלולאה, ורק אם הוא מחליט שהוא זקוק לה. responseFormatסכימה שמתארת את צורת התשובה הסופית, כך שה-agent מחזיר אובייקט שתואם לה ולא רק טקסט חופשי. מתוך LangChain, Structured Output. הוא דבר שונה לגמרי: זו לא פעולה אופציונלית, אלא אילוץ על צורת התשובה הסופית שה-agentLLM שמופעל בלולאה, עם מטרה וסט כלים, ובכל סיבוב מחליט אם להפעיל כלי נוסף או לסיים ולהחזיר תשובה. מתוך AI Agents, Agents והלולאה האגנטית. חייב להחזיר בסיום כל הרצה, בלי קשר לכמה tool calls היו בדרך.
שני המנגנונים חולקים את אותה שפת אימות (zod), אבל את תפקידים שונים לגמרי: tool הוא יכולת שהמודל בוחר להשתמש בה; responseFormat הוא חוזה שהתשובה הסופית חייבת לקיים.
מה קורה כשהפלט לא תואם את הסכימה
גם עם responseFormatסכימה שמתארת את צורת התשובה הסופית, כך שה-agent מחזיר אובייקט שתואם לה ולא רק טקסט חופשי. מתוך LangChain, Structured Output. מוגדר, המודל עדיין מנסה "לנחש" איך למלא את השדות — הוא לא מקבל אכיפה מתמטית של הסכימה, אלא הנחיה חזקה. LangChainפריימוורק בקוד פתוח לבניית אפליקציות מבוססות LLM ו-agents, שעוטף רכיבים חוזרים בממשק אחיד. מתוך LangChain, מבוא ל-LangChain. מאמת את התשובה מול הסכימה, ואם היא לא תואמת (שדה חסר, טיפוס שגוי), ברירת המחדל היא לנסות שוב אוטומטית עם הודעת שגיאה שמוחזרת למודל ומסבירה מה היה לא תקין.
חשוב לזכור: גם פלט "מובנה" הוא עדיין תוצר של מודל שפה, לא ערבות פורמלית — שווה להתייחס אליו כמו לכל קלט חיצוני אחר ולא לסמוך על תקינותו בעיוורון בקוד קריטי, גם אם הוא עבר אימות סכימה בסיסי.
דוגמה מלאה: agent שמחזיר החלטה מובנית
בדוגמה הבאה ה-agentLLM שמופעל בלולאה, עם מטרה וסט כלים, ובכל סיבוב מחליט אם להפעיל כלי נוסף או לסיים ולהחזיר תשובה. מתוך AI Agents, Agents והלולאה האגנטית. בודק את ההזמנות של המשתמש (עם getUserOrders מהפרק הקודם) ומחזיר החלטה מובנית — approved ו-reason — במקום פסקת טקסט חופשי שהקוד שלנו היה צריך לפענח כדי לדעת אם לאשר את הבקשה.
const OrderDecision = z.object({
approved: z.boolean(),
reason: z.string(),
});
const agent = createAgent({
model: "anthropic:claude-sonnet-5",
tools: [getUserOrders],
responseFormat: OrderDecision,
systemPrompt:
"בדוק אם למשתמש יש הזמנה פתוחה מעל 500 ש\"ח, ואשר או דחה בקשת זיכוי בהתאם.",
});
const result = await agent.invoke(
{ messages: [{ role: "user", content: "אפשר לאשר לי זיכוי על ההזמנה האחרונה?" }] },
{ configurable: { thread_id: "chat-3", userId: "user-42" } }
);
if (result.structuredResponse.approved) {
await issueRefund(result.structuredResponse.reason);
}שאלות נפוצות
במה responseFormat שונה מלבקש בפרומפט "תחזיר JSON"?
בקשה בפרומפט היא רק הנחיה: אין אימות, ואם ה-JSON לא תקין או חסר בו שדה, הקוד שלכם יגלה את זה רק כשהוא נשבר. עם responseFormat הסכימה מועברת למודל בצורה מובנית, LangChain מאמת את התשובה ומנסה שוב כשהיא לא תואמת, ואתם מקבלים אובייקט עם טיפוסים.
האם פלט מובנה מאריך את זמן התגובה?
לפעמים מעט. כשהמודל מחזיר פלט שלא תואם את הסכימה, ברירת המחדל היא ניסיון חוזר, וכל ניסיון הוא עוד קריאה למודל. סכימה פשוטה עם תיאור ברור לכל שדה מקטינה את הסיכוי לכך, ולכן כדאי להימנע מסכימות עמוקות ומסובכות כשאין בהן צורך.
אפשר לקבל גם טקסט חופשי וגם פלט מובנה מאותה הרצה?
כן. ההודעות של ההרצה עדיין זמינות ב-result.messages, לצד result.structuredResponse. אם צריך גם הסבר קריא למשתמש וגם נתונים לקוד, עדיף לכלול את ההסבר כשדה בסכימה עצמה (כמו reason בדוגמה), כך ששניהם מגיעים מאותה תשובה ותואמים זה לזה.