לפני שנצלול לכלים ספציפיים — חשוב להבין שלא כל "פלט מובנה" נולד שווה. יש שלוש רמות מובחנות, וכל אחת מהן עם הבטחות איכות שונות לחלוטין.
רמה 1: הנדסת prompt (לא אמינה)
הגישה הפרימיטיבית: מבקשים מהמודל "החזר JSON" וסומכים על הטוב שבטבע האנושי. זה עובד 80%–95% מהזמן עבור מודלים מודרניים, אבל נכשל בשקט בקצוות (וזה החלק המעצבן). אין שום הבטחת טיפוסים, ובדרך כלל תקבלו markdown fences, הסברים מלפנים, או פשוט שדות חסרים.
# הגישה השברירית — אל תעשו זאת בייצור
response = client.chat.completions.create(
model="gpt-5",
messages=[{"role": "user", "content": "Return JSON with name and age"}]
)
data = json.loads(response.choices[0].message.content) # תתפלל
רמה 2: Function Calling / Tool Use (טוב יותר)
פרדיגמת ה-Tool Use של Anthropic ו-Function Calling של OpenAI עובדת 95%–99% מהזמן. הסכמה היא רמז למודל, לא אילוץ קשיח. עדיין יכולים להופיע טיפוסים לא נכונים (למשל "2" במקום 2), enums לא חוקיים, או מבנים מקוננים שגויים. בקיצור — טוב, אבל לא מספיק טוב.
רמה 3: Native Structured Output (הטוב ביותר)
הפיתוח האמיתי של 2026: הקידוד מוגבל (constrained decoding) עם JSON Schema. המודל לא מנסה להתאים לסכמה — הוא פיזית לא יכול לסטות ממנה. הסכמה נקבעת לסטייט-מאשין סופי (FSM), וטוקנים שיוצאים מהמסלול מקבלים logit של מינוס אינסוף לפני הסיגמואיד. זו הבטחה מתמטית, לא סטטיסטית. הבדל גדול.
אם אתם משלחים משהו לייצור ב-2026, אתם צריכים להיות ברמה 3. נקודה.
איך constrained decoding עובד מתחת למכסה המנוע
כשאתם שולחים JSON Schema ל-OpenAI עם strict: true (או tool ל-Anthropic עם strict: true), המנוע עושה משהו די מתוחכם: הוא מקמפל את הסכמה ל-Context-Free Grammar ובונה ממנה FSM. כל מצב ב-FSM מייצג מיקום חוקי במסלול הסכמה.
בכל צעד דקודינג:
- המודל מחשב logits עבור כל ה-vocabulary (~200K טוקנים).
- ה-FSM מסמן אילו טוקנים שומרים על מסלול חוקי במצב הנוכחי.
- טוקנים לא חוקיים מקבלים
-Infinity.
- הסיגמואיד והדגימה מתבצעים רק על הטוקנים החוקיים.
קיים overhead חד-פעמי של קומפילציית הסכמה (50–200ms בקריאה ראשונה), אבל בקריאות הבאות לאותה סכמה ה-FSM נשמר בקאש ולא מוסיף latency משמעותי. עבור רוב היישומים, זה זניח לעומת זמן ה-inference הכללי.
OpenAI Structured Outputs: Strict Mode
OpenAI השיקה את Strict Mode באוגוסט 2024 ועברה ל-CFG engine מלא ב-GPT-5. החל מ-2026, JSON Mode (type: "json_object") נחשב לגרסת מורשת — היא מבטיחה רק JSON תקין מבחינה תחבירית, לא תאימות לסכמה. Strict Mode עם json_schema הוא ברירת המחדל לייצור.
הדרך הטובה ביותר: Pydantic + SDK של Python
במקום לכתוב JSON Schema ביד (סיוט), השתמשו ב-Pydantic. ה-SDK של OpenAI יודע להמיר את המודל ל-schema, להפעיל strict mode, ולהחזיר אובייקט Pydantic מאומת:
from openai import OpenAI
from pydantic import BaseModel, Field
from typing import Literal
client = OpenAI()
class CustomerComplaint(BaseModel):
reasoning: str = Field(
description="Step-by-step analysis before classification"
)
sentiment: Literal["positive", "neutral", "negative"]
category: Literal["billing", "technical", "shipping", "other"]
priority: Literal["low", "medium", "high", "urgent"]
summary: str = Field(max_length=200)
response = client.chat.completions.parse(
model="gpt-5",
messages=[
{"role": "system", "content": "Classify customer complaints."},
{"role": "user", "content": transcript}
],
response_format=CustomerComplaint
)
result: CustomerComplaint = response.choices[0].message.parsed
print(result.priority) # Literal — מובטח לעבור validation
שימו לב לשדה reasoning. הוא מופיע לפני שדות הסיווג, וזה מכוון לחלוטין. כשאילצתם את המודל לייצר את ההיגיון בטוקן-ספייס לפני שהוא מחויב לתשובה, אתם מקבלים Chain of Thought חינם בלי prompt נוסף. טריק קטן עם השפעה גדולה.
JavaScript עם Zod
import OpenAI from "openai";
import { z } from "zod";
import { zodResponseFormat } from "openai/helpers/zod";
const Complaint = z.object({
reasoning: z.string(),
sentiment: z.enum(["positive", "neutral", "negative"]),
category: z.enum(["billing", "technical", "shipping", "other"]),
priority: z.enum(["low", "medium", "high", "urgent"]),
summary: z.string().max(200),
});
const completion = await openai.chat.completions.parse({
model: "gpt-5",
messages: [/* ... */],
response_format: zodResponseFormat(Complaint, "complaint"),
});
const result = completion.choices[0].message.parsed;
Anthropic: Tool Use ו-Structured Outputs
הגישה של Anthropic לפלטים מובנים שונה מבחינה ארכיטקטונית. במקום פרמטר response_format נפרד, Anthropic מציעה שני מנגנונים שעובדים יחד:
- JSON Outputs (
response_format): שולט בפורמט התשובה הסופית של Claude.
- Strict Tool Use (
strict: true): מאמת פרמטרים של קריאות לכלים.
התכונה יצאה מ-beta והפכה ל-GA בתחילת 2026. בלי strict mode, Claude עלול להחזיר "celsius" כמחרוזת כשציפיתם ל-enum, או "2" במקום 2. עם strict mode? הטיפוסים מובטחים.
import anthropic
from pydantic import BaseModel
class WeatherResult(BaseModel):
location: str
temperature_celsius: float
conditions: str
client = anthropic.Anthropic()
response = client.messages.parse(
model="claude-opus-4-7",
max_tokens=1024,
messages=[
{"role": "user", "content": "What's the weather in Tel Aviv?"}
],
response_format=WeatherResult
)
weather: WeatherResult = response.parsed
שילוב של JSON Outputs ו-Strict Tools
הצירוף הזה הופך לעוצמתי במיוחד עבור סוכנים: ה-tools משתמשים ב-strict mode כדי להבטיח שה-API שלכם מקבל פרמטרים תקינים, וה-response_format מבטיח שהתשובה הסופית למשתמש מובנת. שני העולמות בבת אחת.
tools = [{
"name": "search_inventory",
"description": "Search product inventory",
"input_schema": {
"type": "object",
"properties": {
"query": {"type": "string"},
"max_results": {"type": "integer", "minimum": 1, "maximum": 50}
},
"required": ["query", "max_results"]
},
"strict": True # פרמטרים מובטחים
}]
response = client.messages.create(
model="claude-opus-4-7",
tools=tools,
response_format=FinalRecommendation, # תשובה מובנית
messages=[...]
)
Google Gemini: Response Schema
Gemini תומכת ב-structured output דרך תכונה בשם responseSchema, המבוססת על תת-קבוצה של OpenAPI 3.0 schema. היא תומכת ב-enums, מבנים מקוננים, וטיפוסים מורכבים. נכון ל-2026, יחס המחיר-לאמינות שלה הוא לרוב הטוב ביותר בענף — לפחות לפי הניסיון שלי בעיבודי batch גדולים.
from google import genai
from google.genai import types
from pydantic import BaseModel
class Recipe(BaseModel):
name: str
ingredients: list[str]
prep_time_minutes: int
client = genai.Client()
response = client.models.generate_content(
model="gemini-2.5-pro",
contents="Suggest a 30-minute pasta recipe.",
config=types.GenerateContentConfig(
response_mime_type="application/json",
response_schema=Recipe,
),
)
recipe: Recipe = response.parsed
Instructor: ספרייה אחת, כל הספקים
אם אתם בונים מערכת multi-provider, או פשוט רוצים נטרול-ספק, Instructor היא הספרייה המובילה (11K+ כוכבי GitHub, 3M+ הורדות חודשיות — נתון לא רע בכלל). היא עוטפת את ה-SDK של כל ספק ומוסיפה Pydantic validation, retries אוטומטיים עם feedback, ו-streaming.
import instructor
from anthropic import Anthropic
from pydantic import BaseModel
class ExtractedInvoice(BaseModel):
invoice_number: str
total_amount: float
line_items: list[str]
# ניתן להחליף בין OpenAI, Anthropic, Gemini, Ollama, DeepSeek...
client = instructor.from_anthropic(Anthropic())
invoice = client.messages.create(
model="claude-opus-4-7",
response_model=ExtractedInvoice,
max_retries=3, # מנסה שוב עם הודעת שגיאה אם validation נכשל
messages=[{"role": "user", "content": invoice_pdf_text}],
)
היופי במנגנון ה-retries הוא ש-Instructor מזריק את שגיאת ה-Pydantic בחזרה ל-prompt, ככה שהמודל מקבל הזדמנות לתקן את הפלט. עם 3 ניסיונות, אחוז הכשלון יורד מתחת ל-0.01% גם עבור מודלים שלא תומכים ב-strict mode מקומית. כן, גם הקטנים מקבלים פה צ'אנס הוגן.
השוואת ספקים: שולחן עבודה ל-2026
| תכונה |
OpenAI Strict |
Anthropic Strict Tools |
Gemini Response Schema |
JSON Mode (legacy) |
| אכיפת סכמה |
מלאה (CFG) |
מלאה |
OpenAPI subset |
תחביר בלבד |
| אחוז כשלון בפרסור |
<0.1% |
<0.2% |
<0.3% |
2%–5% |
| אימות enum |
כן |
כן |
כן |
לא |
| תקורת טוקנים |
~80–120 |
~150–300 |
~60–100 |
~50 |
| תמיכה ב-Streaming |
כן |
חלקי |
כן |
כן |
חמש המכשלות הנפוצות (וכיצד להימנע מהן)
1. שדה reasoning אחרי שדה התשובה
זו הטעות הנפוצה ביותר, בלי תחרות. אם הסכמה שלכם מסודרת כ-{"answer": ..., "reasoning": ...}, המודל מחויב לתשובה לפני שהוא חושב. הזיזו את reasoning לראש הסכמה תמיד. תאמינו לי, זה משנה.
2. סכמות מקוננות עמוקות
סכמות עם 4+ רמות קינון מגדילות את אחוזי השגיאה משמעותית. שטחו את המבנה היכן שאפשר. אם אתם חייבים מבנה מקונן — פצלו ל-LLM call נפרד עבור כל רמה.
3. תיאורי שדות חסרים
בלי description, המודל מנחש את הכוונה (וזה הולך כמו שזה נשמע). הוסיפו תיאור לכל שדה לא טריוויאלי:
class Order(BaseModel):
customer_id: str = Field(
description="UUID v4 of the customer in our system, not external IDs"
)
delivery_window: Literal["morning", "afternoon", "evening"] = Field(
description="morning=08-12, afternoon=12-17, evening=17-21 local time"
)
4. ללא טיפול ב-null
אם השדה אופציונלי באמת — סמנו אותו Optional[X]. אחרת, אתם מאלצים את המודל להזות ערך כדי למלא שדה שלא קיים בנתונים. וזה כל הרעיון מאחורי "הזיות" שאף אחד לא רוצה.
5. סכמות עצומות
סכמות עם 50+ שדות פוגעות באיכות. אם אתם צריכים מבנה גדול, פצלו ל-stages: extraction ראשון, classification שני, summarization שלישי. כל stage עובד עם סכמה ממוקדת.
Structured Outputs מול Function Calling: מתי כל אחד
זוהי הבחנה ארכיטקטונית קריטית שצוותים רבים מתעלמים ממנה (וחבל).
|
Structured Outputs |
Function Calling / Tool Use |
| מטרה |
עיצוב צורת התשובה |
הפעלת פעולות חיצוניות |
| מקור המידע |
כבר ב-context |
צריך להישלף חיצונית |
| סוג תור |
תור יחיד |
מרובה (model ↔ tool ↔ model) |
| שימוש מובהק |
extraction, classification, formatting |
API, RAG, פעולות עולם אמיתי |
כלל אצבע: אם המודל זקוק לקרוא ל-API, להפעיל פעולה, או להחליט איזה tool להפעיל — תשתמשו ב-Function Calling. אם הוא מקבל את כל המידע ב-prompt וצריך רק לעצב אותו לסכמה — תשתמשו ב-Structured Outputs.
סוכנים מורכבים ב-2026 משלבים את שניהם: tools עם strict mode עבור הצעדים האמצעיים, ו-response_format עבור התשובה הסופית למשתמש. זה הסטנדרט החדש.
דפוסי ייצור: שילוב חשיבה ופלט מובנה
הדפוס המוביל ב-2026 הוא Two-Stage Generation: אל תאלצו את המודל לחשוב ולפלט במקביל.
הבעיה
כשאתם מאלצים LLM לפלט JSON תוך כדי משימת חשיבה מורכבת, האיכות נופלת. לא בגלל שהמודל לא יכול להתמודד עם מבנה — אלא בגלל שאילוץ פורמט בזמן תהליך החשיבה מפריע לחשיבה עצמה. תחשבו על זה כמו לבקש ממישהו לפתור משוואה תוך כדי שהוא ממלא טופס מס.
הפתרון
# שלב 1: חשיבה חופשית — ללא אילוצים
thinking_response = client.chat.completions.create(
model="gpt-5",
messages=[
{"role": "user", "content": f"Analyze this contract: {contract}"}
]
)
analysis = thinking_response.choices[0].message.content
# שלב 2: עיצוב מובנה — strict mode
final = client.chat.completions.parse(
model="gpt-5-mini", # מודל זול יכול לעשות formatting
messages=[
{"role": "user", "content": f"Format this analysis: {analysis}"}
],
response_format=ContractSummary,
)
הדפוס הזה גם חוסך עלויות, וזה בונוס יפה: מודל יקר עושה את החשיבה, מודל זול עושה את הפורמט. ניתוב היברידי כזה מוריד עלויות ב-40%–60% בלי לפגוע באמינות. אצלי בפרויקט אחד זה חתך את החשבון החודשי בחצי, פשוטו כמשמעו.
Streaming של structured outputs
גם הספקים המובילים תומכים ב-streaming של פלטים מובנים. ה-SDK של OpenAI מפענח partial JSON תוך כדי הזרימה, וזה משחק שלם:
with client.chat.completions.stream(
model="gpt-5",
response_format=Recipe,
messages=[...],
) as stream:
for event in stream:
if event.type == "content.delta":
if event.parsed is not None:
# עדכון UI ככל שהשדות מתמלאים
print(event.parsed)
final_recipe = stream.get_final_completion().choices[0].message.parsed
זה קריטי עבור UX של אפליקציות chat — המשתמש רואה את התשובה נבנית בזמן אמת, ולא ממתין שניות לפלט מלא. ההבדל בין "האפליקציה תקועה?" לבין "וואו, מהיר".
Observability עבור structured outputs
בייצור, חייבים לעקוב אחרי שלושה מדדים (לפחות):
- Schema compilation cache hit rate — אם הוא נמוך, אתם משלמים את הקנס של 50–200ms על כל קריאה. ודאו שהסכמה לא משתנה דינמית.
- Validation retry rate — באמצעות Instructor, ספרו כמה פעמים ה-validation נכשל ונדרש retry. שיעור גבוה מצביע על סכמה לא ברורה או מודל לא מתאים.
- Token overhead delta — השוו את כמות הטוקנים בין קריאות עם ובלי strict mode כדי לכמת את העלות.
שאלות נפוצות (FAQ)
מה ההבדל בין JSON Mode ל-Structured Outputs?
JSON Mode (type: "json_object") מבטיח רק שהפלט יהיה JSON תקין מבחינה תחבירית — אתם עלולים לקבל אובייקט עם שדות חסרים, טיפוסים שגויים, או ערכים מחוץ ל-enum. Structured Outputs (Strict Mode) משתמש ב-constrained decoding ומבטיח תאימות מלאה ל-JSON Schema שלכם. ב-2026, JSON Mode נחשב לגרסת מורשת, ויש להעדיף Structured Outputs לכל שימוש בייצור.
האם Structured Outputs מאט את התשובה?
יש overhead חד-פעמי של 50–200ms בקומפילציית הסכמה ל-FSM בקריאה הראשונה. בקריאות הבאות לאותה סכמה, ה-FSM מגיע מהקאש והתקורה זניחה (חד-ספרתי במילישניות). עבור רוב היישומים זה לא משמעותי בהשוואה לזמן ה-inference. אם אתם משתמשים בסכמות דינמיות שמתחלפות בכל קריאה, אתם משלמים את הקנס בכל פעם — וזו עוד סיבה להגדיר סכמות סטטיות.
האם אני יכול להשתמש ב-Structured Outputs עם מודלים מקומיים כמו Llama או Mistral?
כן, בהחלט. vLLM, SGLang ו-Ollama תומכים ב-constrained decoding ברמת ה-runtime באמצעות grammars. ספריות כמו Outlines ו-LMQL מאפשרות להגדיר סכמות בקוד ולהבטיח שהמודל המקומי מציית להן. הביצועים פחות מהוקצעים מהמיועדים מסחרית, אבל ההבטחה המתמטית של תאימות לסכמה זהה.
מה קורה אם המודל "רוצה" לפלוט משהו שאינו תואם לסכמה?
זה לא יכול לקרות פיזית. בכל צעד דקודינג, רק טוקנים שמשמרים מסלול חוקי ב-FSM מקבלים סבירות חיובית; כל השאר מקבלים -Infinity. המודל יבחר את הטוקן החוקי הכי סביר. במקרים נדירים שבהם הסכמה לא ניתנת לסיפוק (למשל, אילצתם maxLength: 5 אבל הצורך הוא משפט שלם), המודל יבחר במסלול הקצר ביותר האפשרי או יחתוך טקסט. תכננו את הסכמות שלכם להיות גמישות מספיק כדי לאפשר את המקרים הקצוונים שלכם.
איך אני בוחר בין OpenAI, Anthropic ו-Gemini עבור structured outputs?
OpenAI Strict Mode מציע את האמינות הגבוהה ביותר (פחות מ-0.1% כשלון) ואת הפעולה החלקה ביותר עם Pydantic ו-Zod. Anthropic מובילה כשאתם צריכים שילוב של tools ו-response format באותו flow agentic. Gemini מציעה את יחס המחיר-לאיכות הטוב ביותר עבור פעולות ברצף גבוה (data extraction בקנה מידה גדול). אם אתם צריכים נטרול-ספק — השתמשו ב-Instructor, היא מאפשרת להחליף בין ספקים בשורת קוד אחת.
סיכום: רשימת בדיקה לייצור
- השתמשו תמיד ב-Strict Mode / Structured Outputs לפלטים בייצור — לעולם לא ב-JSON Mode.
- הגדירו סכמות ב-Pydantic או Zod, לא ביד.
- הוסיפו
reasoning לפני שדות התשובה לטריגר Chain of Thought.
- הוסיפו
description לכל שדה לא טריוויאלי.
- הימנעו מסכמות עם 4+ רמות קינון או 50+ שדות.
- הפרידו חשיבה מפורמט: מודל יקר חושב, מודל זול מפרמט.
- השתמשו ב-Function Calling עבור פעולות, ב-Structured Outputs עבור עיצוב נתונים.
- שלבו Instructor אם אתם צריכים תאימות בין ספקים או retry אוטומטי.
- עקבו אחרי validation retry rate ו-cache hit rate ב-observability.
פלטים מובנים הם אחד התחומים הבודדים ב-LLM שעברו מ-"זה כמעט עובד" ל-"זה פתור" בתוך 18 חודשים. אם אתם עדיין מפענחים JSON עם regex או מטפלים בכשלי JSON.parse ב-2026 — זה הזמן לשדרג את ה-stack. רצינית.