Structured Outputs, Claude מחזיר JSON מדויק

📚 פיתוח עם Claude, Claude Code & API ⏱️ 8 דק׳ 🎓 בינוני ✓ חינם לגמרי
Structured Outputs, Claude מחזיר JSON מדויק

JSON שלא נשבר, סוף סוף יש דרך נכונה

עד נובמבר 2025 כל מי שרצה JSON נקי מClaude נאלץ לבחור בין שני רעות: להתפלל שהמודל יחזיר JSON תקין, או להעמיס Tool Use עם tool_choice רק כדי לחלץ מבנה. ב-14 בנובמבר 2025 Anthropic השיקה את Structured Outputs, דקדוק שמופעל בזמן ה-inference וחוסם פיזית טוקנים שיפרו את הסכמה. ב-Q1 2026 זה הפך ל-GA על Opus 4.5/4.6/4.7, Sonnet 4.5/4.6 ו-Haiku 4.5. הפוסט הזה מסביר באיזה מהשלוש שכבות לבחור.

שלוש השכבות, עץ ההחלטה

שכבה מתי אמינות
Tier 1, Structured Outputs (חדש)
output_config.format + strict:true
Production על מודלי 4.5+. ברירת המחדל החדשה. 100% schema-conforming (חוץ ממספרים)
Tier 2, Tool Use Coercion
tool_choice ללא strict
מודלים ישנים (3.5/4.0), צורך בדיאלקט JSON Schema גמיש יותר, או שילוב עם כלים אמיתיים. גבוהה, אבל לא מובטחת
Tier 3, Prompt-only ("החזר JSON") פרוטוטייפ של 10 דקות בלבד. אסור ב-production. בינונית. תקבל markdown fences ופתיחות מנומסות.

אזהרות שחייבים להכיר לפני שכותבים שורת קוד

Tier 1: Structured Outputs, strict tool use בעברית

הוספת strict: true ברמת הכלי מבטיחה ש-tool_use.input תואם לסכמה בכל קריאה:

import anthropic, json
client = anthropic.Anthropic()

tools = [{
    "name": "extract_contact_info",
    "description": "חילוץ פרטי קשר מטקסט בעברית",
    "strict": True,                       # <-- המפתח החדש
    "input_schema": {
        "type": "object",
        "properties": {
            "name":  {"type": "string", "description": "שם פרטי ומשפחה"},
            "phone": {"type": ["string", "null"], "description": "טלפון או null"},
            "email": {"type": ["string", "null"], "description": "אימייל או null"}
        },
        "required": ["name", "phone", "email"],
        "additionalProperties": False
    }
}]

text = "היי, אני דני לוי ממיקרוסופט ישראל. 054-1234567"

resp = client.messages.create(
    model="claude-opus-4-7",
    max_tokens=512,
    tools=tools,
    tool_choice={"type": "tool", "name": "extract_contact_info"},
    messages=[{"role": "user", "content": text}]
)

result = resp.content[0].input
# מובטח: {"name": "דני לוי", "phone": "054-1234567", "email": null}

שימו לב לשני שינויים מ-2024: strict: True ברמת הכלי, וכל שדה אופציונלי הופך ל-type: ["string", "null"] ונכלל ב-required. זו דרישת ה-strict-mode, בלי "שדה מתעלם ממנו". null זה הערך הלגיטימי לחסר.

Tier 1 גרסה ב': output_config.format ללא Tools

אם אתם לא צריכים לערבב כלים אמיתיים, הפורמט החדש output_config חוסך את כל ה-tool dance:

resp = client.messages.create(
    model="claude-sonnet-4-6",
    max_tokens=512,
    output_config={
        "format": {
            "type": "json_schema",
            "name": "feedback_analysis",
            "schema": {
                "type": "object",
                "properties": {
                    "sentiment": {"type": "string", "enum": ["positive","negative","neutral"]},
                    "score":     {"type": "integer", "minimum": 1, "maximum": 10},
                    "topics":    {"type": "array", "items": {"type": "string"}},
                    "summary":   {"type": "string"}
                },
                "required": ["sentiment","score","topics","summary"],
                "additionalProperties": False
            }
        }
    },
    messages=[{"role": "user", "content": "השירות היה מעולה אבל ההמתנה ארוכה."}]
)

data = json.loads(resp.content[0].text)   # תמיד JSON תקין

וולידטור Pydantic, כי numeric constraints לא נאכפים

זה החלק שכולם מדלגים עליו ואז נשרפים: גם ב-strict mode, score יכול לחזור 47. תעטפו תמיד ב-Pydantic:

from pydantic import BaseModel, Field, ValidationError
from typing import Literal

class FeedbackAnalysis(BaseModel):
    sentiment: Literal["positive","negative","neutral"]
    score: int = Field(ge=1, le=10)        # <-- אכיפה אמיתית
    topics: list[str]
    summary: str

def analyze(text: str, retries: int = 2) -> FeedbackAnalysis:
    schema = FeedbackAnalysis.model_json_schema()
    schema["additionalProperties"] = False
    for attempt in range(retries + 1):
        resp = client.messages.create(
            model="claude-opus-4-7", max_tokens=512,
            tools=[{"name":"analyze","description":"","strict":True,"input_schema":schema}],
            tool_choice={"type":"tool","name":"analyze"},
            messages=[{"role":"user","content":text}]
        )
        try:
            return FeedbackAnalysis(**resp.content[0].input)
        except ValidationError as e:
            if attempt == retries: raise
            # retry, בדרך כלל בעיית minimum/maximum

השילוב של Structured Outputs (אכיפה מבנית) + Pydantic (אכיפה ערכית) הוא מה ש-Anthropic ממליצה רשמית. אחד בלי השני זה חצי פתרון.

Tier 2: Tool Use Coercion, מתי בכל זאת

אתם עדיין צריכים את הגישה הקלאסית (בלי strict) ב-3 מקרים: (1) מודלים ישנים שלא תומכים ב-strict, (2) סכמות שמשתמשות ב-features של JSON Schema ש-strict mode דוחה (regex patterns, oneOf מורכבים), (3) שילוב באותה קריאה של כלי extraction עם כלים פעילים אחרים. הקוד זהה לדוגמה למעלה, פשוט מורידים את strict: True. רוב הקוראים לא צריכים את זה ב-2026.

טעויות נפוצות (וחדשות)

ציטוט מהקהילה

בדיון ב-Hacker News על השקת ה-feature, jascha_eng כתב: "I feel like this is so core to any LLM automation it was crazy that anthropic is only adding it now." mparis הוסיף שעד עכשיו דווקא בחר ב-Anthropic "because it adds deterministic validation that errors on certain standard JSON Schema elements", כלומר OpenAI היה קפדן מדי על דיאלקט הסכמה. השכבה החדשה של Anthropic נותנת את שני העולמות.

messages=[{"role": "user", "content": "חלץ שם, טלפון, אימייל. החזר JSON בלבד:\n" + text}] # Claude עלול להחזיר: # 'בטח! הנה הJSON:\n```json\n{"name":"דני"}\n```\nתודה!' # json.loads() נשבר. אתה כותב regex לחלץ {...}. אתה מתחרט.
resp = client.messages.create( model="claude-opus-4-7", max_tokens=512, tools=[{ "name": "extract", "strict": True, "input_schema": { "type": "object", "properties": { "name": {"type": "string"}, "phone": {"type": ["string","null"]}, "email": {"type": ["string","null"]} }, "required": ["name","phone","email"], "additionalProperties": False } }], tool_choice={"type":"tool","name":"extract"}, messages=[{"role":"user","content":text}] ) result = resp.content[0].input # מובטח dict עם 3 השדות בדיוק
עובד כרגיל ומאלץ JSON
החזר HTTP 400, הטריק מת על Opus 4.6 ו-Sonnet 4.5+
Claude מתעלם מה-prefill ועונה כרגיל
עובד רק עם strict:true
כלום, הדקדוק אוכף את זה
לעטוף ב-Pydantic/Zod ולוולד אחרי כל קריאה, הדקדוק לא אוכף מספרים
להוסיף enforce_constraints:true
לפתוח באג ל-Anthropic
כדי לחזור על אותה תשובה
קומפילציית הדקדוק יקרה, קריאה ראשונה איטית, אחר כך מהיר 24 שעות
למנוע prompt injection
להוזיל חיוב

רוצה ללמוד עם מעקב התקדמות, קוויזים ותעודה?

כל 130 השיעורים פתוחים בחינם, כולל נגן אינטראקטיבי, שמירת התקדמות ותעודה דיגיטלית בסיום.