יום 13 - פלט מובנה
ראינו אתמול איך מודלים מאומנים להבין איך לעבוד עם כלים, לבקש הפעלת כלים ולקרוא תשובות מכלים. סוג נוסף של אימון שמודלים עוברים הוא אימון ליצירת פלט מובנה כלומר פלט שיגיע אלינו בתור אוביקט JSON ולא ידרוש פיענוח טקסטואלי. היום נראה איך זה עובד ומתי מומלץ להשתמש במנגנון זה.
1. איך זה עובד
המודלים של OpenAI ומודלים מסוימים נוספים מאומנים כדי להבין הודעות בפורמט מיוחד שמבקש מהמודל פלט מובנה. כשאנחנו כותבים הודעה למודל שמאומן לכך אנחנו יכולים לבקש מבנה מסוים של פלט ואז המודל יתאמץ לכתוב לנו את הפלט בפורמט שביקשנו.
דוגמה ראשונה מהתיעוד תראה לנו איך להפוך טקסט לאוביקט פייתון:
import asyncio
from pydantic import BaseModel
from agents import Agent, Runner
class CalendarEvent(BaseModel):
name: str
date: str
participants: list[str]
agent = Agent(
name="Calendar extractor",
instructions="Extract calendar events from text",
output_type=CalendarEvent,
)
async def main():
result = await Runner.run(agent, "We're having a party this Saturday night, 9:00pm with Mark and Dana")
print(result.final_output.__class__)
print(result.final_output)
if __name__ == "__main__":
asyncio.run(main())
הגדרתי קלאס שיורש מ BaseModel של pydantic, שזו ספריה להגדרת טיפוסים בפייתון. את הקלאס העברתי בתור output_type לסוכן ועכשיו כשאני מפעיל את run אני מקבל בחזרה אוביקט מהקלאס הזה.
נשים לב שלא כל המודלים מאומנים על הפעלת כלים או על פורמט הפעלת כלים שתואם את OpenAI ולכן אותה תוכנית עם מודל אחר לדוגמה DeepSeek-R1 תחזיר את השגיאה:
openai.UnprocessableEntityError: Error code: 422 - {'error': {'code': 'Invalid input', 'status': 422, 'message': 'invalid input error', 'details': [{'type': 'value_error', 'loc': ['body', 'response_format', 'type'], 'msg': "Value error, Response format was json_schema but must be either 'text' or 'json_object'.", 'input': 'json_schema', 'ctx': {'error': {}}}]}}
2. מתי נשתמש
כשאני ראיתי את היכולת של Structured Output בפעם הראשונה אני מודה שמאוד התלהבתי וניסיתי להשתמש בה כמה שיותר. לאורך הזמן בעבודה עם מודלים למדתי על החסרונות:
קשה להחליף למודלים אחרים כי לא כולם מאומנים על פלט מובנה באותו אופן.
כשהמודל לא יודע מה לענות הוא יחזיר שגיאה או None במקום הסבר מה קרה. לדוגמה כשאני מריץ את הפרומפט:
result = await Runner.run(agent, "Hello")
אני מקבל אוביקט אירוע בלוח שנה ששם האירוע הוא Hello אבל אין לו תאריך ואין לו רשימת מוזמנים. בעצם אני מכריח את המודל לענות לי בפורמט מסוים גם כשזה לא אפשרי ולכן מקבל תשובה שגויה.
דרך אחרת לכתוב את קוד הדוגמה היא:
import asyncio
from pydantic import BaseModel
from agents import Agent, Runner
from agents.extensions.models.litellm_model import LitellmModel
import os
import json
agent = Agent(
name="Calendar extractor",
model=LitellmModel(model="github/gpt-4.1", api_key=os.environ["GITHUB_TOKEN"]),
instructions="""
Extract calendar events from text. Return JSON object of format:
{
name: str,
date: str,
participants: list[str]
}
""",
)
async def main():
result = await Runner.run(agent, "We're having a party this Saturday night, 9:00pm with Mark and Dana")
data = json.loads(result.final_output)
print(data)
if __name__ == "__main__":
asyncio.run(main())
ועכשיו אני מקבל את התוצאה בתור JSON ויכול לטעון אותו למילון בפייתון, וגם אם אני מחליף למודל DeepSeek הקוד עדיין עובד. כמובן שגם פה יש חסרונות כי יש יותר סיכוי לקבל מהמודל תוצאה חלקית או לא נכונה.
3. עכשיו אתם
קראו על pydantic בתיעוד שלהם: https://docs.pydantic.dev/latest/#pydantic-examples
נסו את תוכנית הדוגמה עם מספר מודלים ובדקו איזה מודלים יודעים להחזיר פלט מובנה ואיזה יעדיפו להחזיר לכם מילון בפורמט JSON.