تخطَّ إلى المحتوى

١٩ سبتمبر ٢٠٢٦

Structured Outputs مقابل طلب JSON في الـ Prompt: ما الفرق؟

شارك المقال:

لو قرأت أيّ مقال عن LLM، سمعت "اطلب من النموذج JSON". هل هذا كافٍ؟ قصير: لا. لأنّ النموذج احتمالي — أحيانًا يعطيك JSON، أحيانًا يعطيك نصًّا عاديًا، أحيانًا يعطيك JSON مكسورًا. الحلّ الحقيقي: Structured Outputs — تقييد على مستوى الـ API يفرض بنية المخرج.

أوّلًا: ما هو "طلب JSON في الـ Prompt"؟

الطريقة الكلاسيكية التي يستخدمها الجميع تقريبًا:

import json
from openai import OpenAI

client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])
model = os.environ["OPENAI_MODEL"]

response = client.responses.create(
    model=model,
    input="""
    أعِد JSON بمفتاح name و age.

    النصّ: "أحمد عمره 28 سنة"
    """,
)

text = response.output_text
print(text)
# '{"name": "أحمد", "age": 28}'   ← أحيانًا هكذا
# 'أحمد عمره 28 سنة.'             ← وأحيانًا هكذا ❌
# '{"name": "أحمد", "age": "28"}' ← وأحيانًا هكذا ❌

# ثم تحتاج:
data = json.loads(text)   # ← قد يفشل

المشاكل:

1. النموذج قد يعطي نصًّا عاديًا بدل JSON.
2. الحقول قد تنقص.
3. الحقول قد تكون من نوع خاطئ (age "28" بدل 28).
4. قد يحزم JSON داخل ``` ``` markdown.
5. لا ضمان بأنّه سيحترم بنية محدّدة.

ثانيًا: ما هو Structured Outputs؟

Structured Outputs = أنت ترسل JSON Schema مع الطلب، والمزوّد يقيّد decoding ليضمن أنّ المخرج يطابق الـ schema حرفيًا.

from pydantic import BaseModel
from openai import OpenAI

class Person(BaseModel):
    name: str
    age: int

client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])
model = os.environ["OPENAI_MODEL"]

response = client.responses.parse(
    model=model,
    input="استخرج من النصّ: أحمد عمره 28 سنة.",
    text_format=Person,
)

person = response.output_parsed
print(person.name)   # "أحمد"
print(person.age)    # 28 (int، مضمون)

ما يحدث داخليًا:

1. SDK يحوّل Pydantic class إلى JSON Schema.
2. يرسل الـ schema مع الطلب.
3. المزوّد يقيد vocabulary في كل خطوة decoding.
4. المخرج يطابق الـ schema بنسبة عالية جدًا.
5. SDK يحلّل الـ JSON إلى Person instance تلقائيًا.

الفرق الجوهري

JSON في PromptStructured Outputs
كيف يعملالنموذج يقرأ تعليماتك ويحاول الالتزامالمزوّد يقيد decoding فيزيائيًا
ضمان البنية❌ ضعيف (≈85-95% نجاح)✅ قوي (≈99-100% للنماذج المدعومة)
أنواع الحقول❌ قد يخطئ (string بدل int)✅ مضمونة
حقول إضافية✅ قد يضيف ما لم تمنعه❌ لا (إذا ضبطت additionalProperties: false)
Markdown wrapper✅ قد يلفّ JSON بـ ```json❌ لا
Cost overheadلاقد يزيد قليلًا (schema في الطلب)
Latencyعادةً أقلقد يزيد قليلًا (validation على كل خطوة)
سهولة الاستخدامسهل جدًايحتاج تعريف schema (Pydantic مثلاً)

مثال مقارنة واقعي

مع JSON في Prompt

import json

prompt = """
استخرج المعلومات التالية من النصّ وأعِد JSON:
{
  "title": "عنوان الوظيفة",
  "years_experience": "سنوات الخبرة (رقم)",
  "skills": ["قائمة مهارات"]
}

النصّ:
"مطلوب مطوّر Backend بخبرة 5 سنوات في Python و Django."
"""

response = client.responses.create(
    model=model,
    input=prompt,
)
text = response.output_text

# قد تحتاج:
text = text.strip().strip("```json").strip("```")
data = json.loads(text)   # ← قد يفشل!

# ثم تحقّق يدويًا:
assert isinstance(data["years_experience"], int)   # قد يكون "5"
assert "Python" in data["skills"]                  # قد لا يكون

مع Structured Outputs

from pydantic import BaseModel, conint

class JobPosting(BaseModel):
    title: str
    years_experience: conint(ge=0) | None
    skills: list[str]

response = client.responses.parse(
    model=model,
    input="استخرج من النصّ: مطلوب مطوّر Backend بخبرة 5 سنوات في Python و Django.",
    text_format=JobPosting,
)

job = response.output_parsed
# أنت متأكّد:
# - job.title is str
# - job.years_experience is int or None
# - job.skills is list[str]
# - لا markdown wrapper
# - لا حقول إضافية

مزايا Structured Outputs

1. لا parsing فاشل

JSON في Prompt:
  text = response.output_text
  data = json.loads(text)        # ← قد يرمي exception.

Structured Outputs:
  person = response.output_parsed  # ← instance مضمونة، أو None.

2. أنواع مضمونة

- age: int (ليس "28").
- is_active: bool (ليس "true").
- tags: list[str] (ليس سلسلة).

3. لا حقول زائدة

إذا ضبطت additionalProperties: false:
  - لن يضيف النموذج حقولًا لم تطلبها.
  - بنية المخرج مستقرّة.

4. لا markdown wrapper

المخرج خام JSON، بدون ``` ``` blocks.

5. validation مبكّرة

- Pydantic يتحقّق من الأنواع أثناء البناء.
- الحقول المطلوبة (required) موجودة.
- القيود (min_length, ge, le, pattern) محترمة.

عيوب Structured Outputs

1. ليس مدعومًا في كل المزودين

✅ مدعوم:
   - OpenAI (Responses API, Chat Completions).
   - Gemini (response_schema).
   - Anthropic (tool use كأقرب بديل).
   - بعض المزودين الآخرين.

❌ غير مدعوم أو جزئيًا:
   - نماذج قديمة.
   - بعض النماذج مفتوحة المصدر بدون طبقة schema-constrained decoding.

2. لا يصلح لكل المهام

- كتابة إبداعية (قصة، شعر) → لا تحتاج schema.
- شرح، تدريس → نصّ حرّ أفضل.
- تلخيص بدون بنية → لا حاجة.

3. schema معقّد جدًا قد يُرفض

- نماذج بأكثر من 100 خاصية → المزوّد قد يرفض الطلب.
- ابدأ بـ schema ضروري فقط.

4. لا يضمن دلالة الحقول

- Structured Outputs يضمن البنية، ليس المعنى.
- إذا طلبت "salary" وهو غير موجود بالنصّ، يضع None.
- إذا كان حقل خاطئًا لغويًا، يعطيك قيمة "معقولة" لكن قد لا تعكس الواقع.

متى تستخدم كلًّا؟

استخدم JSON في Prompt عندما:

✅ اختبار سريع لفكرة.
✅ مخرجات للنصوص (للقراءة البشرية).
✅ مهام لا تحتاج بنية صارمة (تلخيص، شرح).
✅ مزوّد لا يدعم Structured Outputs.

استخدم Structured Outputs عندما:

✅ مخرج يدخل في API response أو قاعدة بيانات.
✅ مخرج يُعالَج برمجيًا (Python/JS).
✅ تريد أنواعًا مضمونة (int، bool، list).
✅ تريد منع حقول زائدة (schema صارم).
✅ تريد parsing بدون try/except متكرّر.

مثال تطبيقي: وظيفتك القادمة

بدون Structured Outputs

def extract_job_unsafe(text):
    response = client.responses.create(
        model=model,
        input=f"استخرج معلومات الوظيفة من النصّ كـ JSON: {text}",
    )
    # ⏰ أنت الآن مسؤول عن:
    # - تنظيف markdown
    # - json.loads مع try/except
    # - تحقّق يدوي من كل حقل
    # - إعادة الطلب عند الفشل
    ...

مع Structured Outputs

from pydantic import BaseModel, Field
from openai import OpenAI

class JobPosting(BaseModel):
    title: str = Field(min_length=2, max_length=120)
    years_experience: int | None = Field(default=None, ge=0)
    skills: list[str] = Field(default_factory=list)
    location: str | None = None
    salary_range: tuple[int, int] | None = None

def extract_job_safe(text):
    response = client.responses.parse(
        model=model,
        input=f"استخرج: {text}",
        text_format=JobPosting,
    )
    # ✅ أنت متأكّد من:
    # - البنية (لا حقل ناقص ما لم يكن optional).
    # - الأنواع (int, str, list).
    # - القيود (length, ge).
    # - المخرج قابل للاستخدام مباشرة.
    return response.output_parsed

نصائح عملية

1. استخدم Pydantic

from pydantic import BaseModel, Field, conint, EmailStr

class User(BaseModel):
    name: str = Field(min_length=1)
    age: conint(ge=0, le=150)
    email: EmailStr
    role: Literal["admin", "user", "guest"]

2. ضَع additionalProperties: false

class Config:
    extra = "forbid"   # Pydantic v1
# أو في v2:
model_config = ConfigDict(extra="forbid")

3. اختبر الـ schema

# اختبر الـ schema يدويًا قبل الإرسال
test_data = {"name": "Test", "age": 30, "email": "a@b.com", "role": "user"}
User(**test_data)   # يرمي ValidationError لو البنية خاطئة

4. أضف validation يدوي

Structured Outputs قوية لكن ليست مثالية. أضف validation خاصة بمجالك:
  - تحقق من أنّ email صالح.
  - تحقق من منطق تجاري (مثلاً salary_min < salary_max).
  - تحقق من وجود بيانات فعلية (ليس None لجميع الحقول).

الخلاصة

JSON في Prompt:
  - سهل، سريع، مناسب للمبتدئين.
  - غير موثوق.
  - يحتاج parsing وvalidation يدوي.

Structured Outputs:
  - موثوق، مضمون، نظيف.
  - يحتاج schema (Pydantic مثلاً).
  - يصبح ضروريًا حين المخرج يدخل في API أو قاعدة بيانات.

القرار: ابدأ بـ JSON في Prompt للنماذج الأولية. حين يصبح المخرج مدخلًا لنظام آخر، انتقل إلى Structured Outputs.

الخطوات التالية

الـ TL;DR: Structured Outputs ليست مجرد "طلب JSON محسّن" — هي تقييد على مستوى الـ decoding يضمن بنية المخرج حرفيًا. استخدمها حين البنية المضمونة مهمّة.

📚 مصادر رسمية للتعمّق: توثيق JavaScript على MDN

الأسئلة الشائعة

هل Structured Outputs أسرع من طلب JSON في prompt؟

ليس بالضرورة أسرع في الزمن الكلي، لكنه يضمن النتيجة الصحيحة من أوّل مرة، فيقلّل الحاجة إلى retry وparsing errors. من حيث زمن الطلب نفسه قد يكون مشابهًا.

هل Structured Outputs مدعوم في كل المزودين؟

لا. OpenAI يدعمها رسميًا في Responses API و Chat Completions. مزودون آخرون لديهم بدائل مشابهة (Anthropic tool use، Google function calling). تحقق من docs المزوّد.

هل أحتاج Structured Outputs لمشاريع بسيطة؟

ليس بالضرورة. إذا كنت تختبر أفكارًا أو مخرجات للنصوص، JSON في prompt يكفي. Structured Outputs يصبح ضروريًا حين تكون البنية المضمونة مهمّة (API، قاعدة بيانات، معالجة لاحقة).

اقرأ أيضًا

تصفّح كل المقالات في المدوّنة، أو ابدأ التعلّم من المسارات و خرائط الطريق.