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 Coerciontool_choice ללא strict |
מודלים ישנים (3.5/4.0), צורך בדיאלקט JSON Schema גמיש יותר, או שילוב עם כלים אמיתיים. | גבוהה, אבל לא מובטחת |
| Tier 3, Prompt-only ("החזר JSON") | פרוטוטייפ של 10 דקות בלבד. אסור ב-production. | בינונית. תקבל markdown fences ופתיחות מנומסות. |
אזהרות שחייבים להכיר לפני שכותבים שורת קוד
- הטריק של prefill
{מת. על Opus 4.6 ו-Sonnet 4.5+ זה מחזיר HTTP 400. אם יש לכם קוד legacy שמעמיסassistant: "{"כדי לאלץ JSON, תעיפו אותו עכשיו. - הגבלות מספריות לא נאכפות. גם עם
strict:trueminimumו-maximumלא חלק מהדקדוק. Claude יכול להחזיר ציון 47 גם אם הגדרתם 1-10. וולידציה אחרי הקריאה היא חובה. - קאש דקדוק 24 שעות. הקריאה הראשונה לסכמה חדשה איטית (compile). זהה ל-24 שעות, fast path. שמרו על אותה סכמה בדיוק כדי לא לאבד את הקאש.
- סירוב בטיחות עוקף את הסכמה. אם Claude מסרב, תקבלו 200 + טקסט חופשי ולא JSON. תמיד עטפו ב-try/except ובדקו את ה-stop_reason.
- תקרת מורכבות: ~24 פרמטרים סך הכל בכלים strict, ו-nesting עמוק זורק "Schema is too complex".
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.
טעויות נפוצות (וחדשות)
- שימוש ב-prefill
{על Opus 4.6+. זה כבר לא טריק, זה 400 Bad Request. הקוד הישן שלכם שבור בשקט. - אמון ב-
minimum/maximum. הדקדוק לא אוכף אותם. תמיד post-validate. requiredעל שדה אופציונלי. ב-strict mode כל הסכמה חייבת להיות required + nullable. אם תסמנו אופציונלי בלבד, תקבלו שגיאת compile. הפיתרון:type: ["string", "null"].- tool_choice חסר. בלי
tool_choice: {"type": "tool", "name": "..."}"Claude עלול לענות בפרוזה ולא לקרוא לכלי. - סכמה משתנה בכל קריאה. כל שינוי קטן שובר את קאש 24 השעות. שמרו את הסכמה כקבוע גלובלי.
- התעלמות מ-stop_reason. סירוב בטיחות מחזיר 200 עם טקסט. תמיד בדקו
resp.stop_reason == "tool_use"לפניcontent[0].input.
ציטוט מהקהילה
בדיון ב-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 נותנת את שני העולמות.
