Structured Outputs: خلي رد الـ AI يطلع JSON صالح كل مرة
هتكسب من المقال ده طريقة عملية تخلي مخرجات الـ AI قابلة للاستخدام في التطبيق بدل ما تكسر الـ parser أو تحفظ بيانات ناقصة.
مستوى القارئ: متوسط
المشكلة باختصار
لو عندك نموذج AI بيرجع JSON عشان تعرضه في UI أو تحفظه في قاعدة بيانات، prompt من نوع “رجّع JSON فقط” مش كفاية. اللي بيحصل فعلاً إن النموذج ممكن ينسى مفتاح، يغيّر enum، يضيف تعليق نصي قبل JSON، أو يرجّع قيمة لا تناسب نوع الحقل.
سيناريو واقعي: عندك نظام دعم فني بيصنف 1,000 تذكرة في اليوم إلى billing وbug وaccount. لو 5% من الردود غير صالحة، عندك 50 تذكرة يوميًا محتاجة retry أو تدخل يدوي. ده مش bug صغير؛ ده طابور تشغيل.
الفكرة بمثال واضح
ركز في المثال ده. بدل ما تقول للنموذج “اكتب JSON فيه التصنيف والأولوية”، أنت بتعرّف عقد واضح. العقد يقول: التصنيف لازم يكون واحد من ثلاث قيم، والأولوية رقم من 1 إلى 5، والسبب جملة قصيرة. النموذج هنا مش بيكتب شكل حر؛ هو بيملأ نموذج بيانات محدد.
تشبيه عملي: بدل ما تبعت لموظف جديد “اكتب تقريرًا مناسبًا”، بتديه فورمة فيها خانات إجبارية. لو خانة ناقصة، التقرير يترفض قبل ما يدخل السيستم.
التطبيق العملي بـ Python وPydantic
الافتراض إن عندك backend Python بيستقبل نص التذكرة، ويحتاج يرجّع object آمن لباقي النظام. المثال التالي يوضح الفكرة: schema واحد، استدعاء واحد، ثم validation قبل التخزين.
from enum import Enum
from pydantic import BaseModel, Field, ValidationError
from openai import OpenAI
client = OpenAI()
class Category(str, Enum):
billing = "billing"
bug = "bug"
account = "account"
class TicketLabel(BaseModel):
category: Category
priority: int = Field(ge=1, le=5)
reason: str = Field(max_length=160)
schema = TicketLabel.model_json_schema()
response = client.responses.create(
model="gpt-4.1-mini",
input="صنف التذكرة: العميل لا يستطيع فتح الفاتورة بعد الدفع.",
text={
"format": {
"type": "json_schema",
"name": "ticket_label",
"strict": True,
"schema": schema,
}
},
)
raw_json = response.output_text
try:
label = TicketLabel.model_validate_json(raw_json)
print(label.model_dump())
except ValidationError as exc:
print("Invalid AI payload", exc)
أفضل طريقة هنا إنك تعتبر الـ schema جزء من contract التطبيق، مش جزء من البرومبت. البرومبت يشرح المهمة، والـ schema يحدد شكل الناتج المقبول.