פלטים מובנים ו-Tool Use
הצעד שהופך "צ'אט" ל"מוצר": לגרום ל-LLM להחזיר JSON תקין ולהפעיל כלים. בלי זה, אי אפשר לתכנת מעל מודל שפה.
למה זה קריטי
טקסט חופשי נהדר לבני אדם, אבל קוד לא יכול לעבוד איתו. אם המודל עונה "הליד נראה איכותי, בערך 85, כדאי לחזור אליו" — התוכנה שלך לא יודעת מה הציון. אבל אם הוא מחזיר {"score": 85, "priority": "high"} — אפשר להזין את זה ישר למסד נתונים, לתנאי, או לשלב הבא בזרימה.
פלטים מובנים (Structured Outputs) הם היכולת להכריח את המודל להחזיר מבנה נתונים קבוע ותקין. Tool Use (או Function Calling) זה השלב הבא — המודל לא רק מחזיר נתונים, אלא בוחר איזה פונקציה להפעיל ועם אילו פרמטרים. שני אלו הם הבסיס לכל אוטומציה, סוכן או אינטגרציה רצינית.
אם הפלט של ה-LLM נכנס לשלב הבא במערכת (ולא ישר לעיני משתמש) — הוא חייב להיות מובנה ומאומת.
JSON ו-Structured Output
הדרך הבסיסית: לבקש JSON בפרומפט ולהגדיר סכמה. במודלים מודרניים יש מצב ייעודי שמבטיח פלט תקין. דוגמה עם OpenAI (Python):
from openai import OpenAI
client = OpenAI()
resp = client.chat.completions.create(
model="gpt-5.6",
response_format={"type": "json_schema", "json_schema": {
"name": "lead_score",
"schema": {
"type": "object",
"properties": {
"score": {"type": "integer", "minimum": 0, "maximum": 100},
"priority": {"type": "string", "enum": ["high", "medium", "low"]},
"reason": {"type": "string"}
},
"required": ["score", "priority", "reason"],
"additionalProperties": False
}
}},
messages=[
{"role": "system", "content": "דרג את איכות הליד לפי הפרטים."},
{"role": "user", "content": "חברת SaaS, 50 עובדים, תקציב מוגדר, דחיפות גבוהה."}
],
)
import json
data = json.loads(resp.choices[0].message.content)
print(data["score"], data["priority"])
הסכמה מבטיחה ש-score יהיה מספר בין 0 ל-100 ו-priority אחד מהערכים המותרים. זה הרבה יותר אמין מ"בקש JSON" בפרומפט בלבד.
Tool Use / Function Calling
כאן המודל מחליט לבד אילו כלים להפעיל. אתה מגדיר את הכלים הזמינים, והמודל מחזיר איזה לקרוא ועם אילו פרמטרים. דוגמה עם Anthropic (Claude):
import anthropic
client = anthropic.Anthropic()
tools = [{
"name": "get_weather",
"description": "מחזיר את מזג האוויר הנוכחי בעיר נתונה",
"input_schema": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"]
}
}]
msg = client.messages.create(
model="claude-opus-4-8",
max_tokens=1024,
tools=tools,
messages=[{"role": "user", "content": "מה מזג האוויר בתל אביב?"}],
)
for block in msg.content:
if block.type == "tool_use":
print(block.name, block.input) # get_weather {'city': 'תל אביב'}
# כאן אתה מריץ את הפונקציה האמיתית ומחזיר את התוצאה למודל
הזרימה המלאה: (1) המודל מבקש להפעיל כלי; (2) הקוד שלך מריץ את הפונקציה האמיתית; (3) מחזירים את התוצאה למודל; (4) המודל מנסח תשובה סופית. זה בדיוק המנגנון שמאחורי סוכני AI ו-MCP.
ולידציה ו-Retry — אל תסמוך, תבדוק
גם עם json_schema, כדאי לאמת בצד שלך לפני שמזינים למערכת. ב-Python נהוג Pydantic; ב-TypeScript — Zod.
from pydantic import BaseModel, Field, ValidationError
class LeadScore(BaseModel):
score: int = Field(ge=0, le=100)
priority: str
reason: str
def parse_with_retry(client, messages, tries=2):
for attempt in range(tries):
raw = call_model(client, messages) # קריאה למודל
try:
return LeadScore.model_validate_json(raw) # אימות
except ValidationError as e:
# מחזירים למודל את השגיאה ומבקשים לתקן
messages.append({"role": "user",
"content": f"הפלט לא תקין: {e}. החזר JSON תקין בלבד."})
raise RuntimeError("failed to get valid output")
התבנית הזו — נסה, אמת, ואם נכשל החזר את השגיאה למודל ובקש תיקון — היא סטנדרט תעשייתי. היא הופכת מערכת שבירה ליציבה.
טיפים וטעויות נפוצות
- טמפרטורה נמוכה (0-0.2) לפלטים מובנים — פחות "יצירתיות", יותר עקביות.
- additionalProperties: false בסכמה — מונע מהמודל להוסיף שדות לא צפויים.
- שמות שדות ברורים באנגלית עוזרים למודל —
due_dateעדיף עלd. - אל תבקש טקסט חופשי + JSON באותה תשובה. זה שובר את הפרסור. הפרד קריאות.
- תמיד עטוף ב-try/except. גם המודל הכי טוב יכול להחזיר פלט לא תקין לפעמים.
- הגבל את מספר הכלים. יותר מדי tools מבלבלים את המודל — קבץ לפי הקשר.
הצעד הבא
עכשיו שהפלט מובנה — הצעד הבא הוא לחבר ידע (RAG) ולמדוד איכות (Evals).