مستوى المقال: متوسط. محتاج تكون كتبت كود بيستدعي نموذج لغة قبل كده، وتعرف JSON يعني إيه. مش لازم تكون خبير في NLP.
لو رد النموذج بيتكسر عندك في json.loads مرة كل كام طلب، المشكلة مش في الموديل. المشكلة إنك بتطلب منه JSON بدل ما تجبره عليه. المقال ده هيوريك إزاي تخلّي المخرج مطابق لمخططك 100%.
المخرجات المنظّمة: من "اطلب JSON" إلى "اجبر على JSON"
المشكلة باختصار
الطريقة الشائعة إنك تكتب في البرومبت "رجّعلي JSON بالشكل ده" وتصلّي إنه يلتزم. الطريقة دي بتفشل بشكل عشوائي: مرة بيزوّد جملة "Sure, here is the data"، ومرة بينسى علامة اقتباس، ومرة بيحط "30" نص بدل رقم. لو عندك خدمة بتفكّك 5000 فاتورة يوميًا لـ JSON، وواحد بالمية بس بيرجع مكسور، يبقى 50 فاتورة بتقع كل يوم في الـ parser. المخرجات المنظّمة (Structured Outputs) بتشيل الاحتمالية دي من أساسها.
مثال يقرّب الفكرة قبل التعريف العلمي
تخيّل إنك طالب من موظف يملالك بيانات عميل. لو سبته يكتب على ورقة بيضا، هيكتب "الراجل ده عنده حوالي 30 سنة وساكن القاهرة تقريبًا". كلام مفهوم لبني آدم، لكن مستحيل تفكّكه بالكود. دلوقتي ادّيله استمارة بخانات محددة: خانة الاسم، خانة السن مكتوب جمبها "أرقام فقط"، خانة المدينة. الموظف مش هيقدر يحط كلمة "تقريبًا" في خانة الأرقام، لأن الخانة نفسها مش بتسمح. المخرجات المنظّمة هي الاستمارة دي، بس للنموذج.
علميًا: المخرجات المنظّمة تقنية بتحوّل مخطط JSON (JSON Schema) بتاعك إلى نحو (grammar)، وبتُجبر النموذج على توليد نص يطابق النحو ده عبر فك التشفير المقيّد (constrained decoding). النتيجة مضمونة تركيبيًا: الحقول موجودة، وأنواعها صح، والقوسين مقفولين.
الحل عمليًا: كود شغّال
مع OpenAI SDK، بتعرّف المخطط كـ Pydantic model، والمكتبة بتتكفّل بالباقي:
from pydantic import BaseModel
from openai import OpenAI
class Person(BaseModel):
name: str
age: int
city: str
client = OpenAI()
completion = client.beta.chat.completions.parse(
model="gpt-4o-2024-08-06",
messages=[{"role": "user", "content": "استخرج: علي، عمره 30، من القاهرة"}],
response_format=Person, # ده اللي بيفعّل التقييد
)
person = completion.choices[0].message.parsed
print(person.age + 1) # 31 — رقم فعلي تقدر تحسب بيه، مش نصخد بالك من person.age + 1. مفيش try/except، ومفيش int(...) ترقيعي. القيمة رجعت رقم لأن المخطط قال كده. الافتراض هنا إنك بتستخدم نموذجًا يدعم response_format زي إصدارات gpt-4o الحديثة.
ولو شغّال على نموذج مفتوح محليًا، نفس الفكرة عبر llama.cpp بنحو GBNF، أو مكتبة Outlines اللي بتاخد نفس الـ Pydantic model: