لو بتستخرج بيانات منظّمة من نص بـ Claude أو GPT وبتلاقي 1 من كل 10 ردود فيه JSON مكسور بيوقّع التطبيق، المشكلة مش إن الموديل ضعيف. المشكلة إنك بتسيب الموديل يكتب نص حر وبتأمل إنه يطلع JSON. Structured Output بيلغي الأمل ده ويفرض الشكل برمجيًا.
Structured Output: ليه الموضوع ده مهم في 2026
في تطبيقات الـ AI الإنتاجية، 70% من الـ workflows اللي شفتها بتنتهي بخطوة واحدة: استخراج JSON من رد الموديل وحقنه في DB أو API تاني. أي خطأ هنا بيكسر الـ pipeline كله. شركات زي OpenAI وAnthropic وGoogle ضافوا في 2024-2025 ميزة Structured Output علشان السبب ده بالظبط.
المشكلة باختصار
عندك endpoint بيستقبل فاتورة كصورة أو نص، بيبعتها لموديل، الموديل المفروض يرجع JSON بـ 5 حقول. في الإنتاج بنشوف 3 سيناريوهات سيئة بشكل متكرر:
- الموديل بيرجع JSON ملفوف بـ markdown code fence (
```json) فالـjson.loadsبيقع. - الموديل بيكتب جملة مقدمة قبل الـ JSON: "بالطبع، إليك البيانات: { ... }".
- الموديل بيستخدم single quotes بدل double quotes أو بيحط trailing comma.
الناتج: 5% إلى 15% من الطلبات بتفشل. لو عندك 1000 طلب يومي، ده 50–150 retry يومي بتكلفة وزمن إضافيين.
مثال من الواقع: استخراج بيانات فاتورة
تخيّل إن عندك تطبيق محاسبي بيستقبل صورة فاتورة من المستخدم، وعايز يطلع منها: اسم البائع، التاريخ، الإجمالي، العملة، رقم الفاتورة. لو سألت الموديل "استخرج البيانات وارجعهم JSON"، ممكن يردّ بأي شكل من اللي فوق. الفكرة الجوهرية: الموديل ميعرفش schema بتاعك ما لم تفرضه عليه برمجيًا.
التعريف العلمي الدقيق
Structured Output (أحيانًا اسمه Constrained Decoding أو JSON Mode) هو آلية تقيّد عملية sampling في الموديل بحيث الـ tokens اللي بيختارها لازم تنتمي لـ grammar محدد سلفًا (عادةً JSON Schema). على المستوى التقني، الموديل وقت توليد كل توكن بيحسب logits على كامل الـ vocabulary، وبعدين الآلية بتمسح (mask) أي توكن مش متوافق مع الـ schema قبل الـ softmax. النتيجة: الـ output مضمون يكون JSON صالح ومتوافق مع الـ schema 100%.
الفرق بين ده وبين الطلب التقليدي: في الطلب التقليدي بتقول للموديل "ارجع JSON من فضلك" وبتعتمد على إن الـ training داخل الموديل خلاه يميل للـ JSON. ده اسمه best-effort. Structured Output بيشيل الـ best-effort ويحطّ مكانه ضمان رياضي.
الكود الكامل بـ Anthropic SDK + Pydantic
هنا مثال شغّال فعليًا. بنستخدم tool use في Claude علشان نفرض الشكل (ده الطريقة الرسمية في Anthropic SDK لـ structured output حتى أبريل 2026):
from anthropic import Anthropic
from pydantic import BaseModel, Field
from datetime import date
import json
client = Anthropic()
class Invoice(BaseModel):
vendor_name: str = Field(description="اسم البائع كما يظهر")
invoice_date: date = Field(description="تاريخ الفاتورة YYYY-MM-DD")
total_amount: float = Field(description="الإجمالي بدون عملة")
currency: str = Field(description="رمز العملة 3 حروف مثل EGP USD EUR")
invoice_number: str = Field(description="رقم الفاتورة")
schema = Invoice.model_json_schema()
raw_text = """
فاتورة رقم INV-2026-0427
من شركة الحايس للتقنية
بتاريخ 27 أبريل 2026
الإجمالي: 4,250.00 جنيه مصري
"""
resp = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=512,
tools=[{
"name": "extract_invoice",
"description": "استخرج بيانات الفاتورة في الشكل المحدد",
"input_schema": schema,
}],
tool_choice={"type": "tool", "name": "extract_invoice"},
messages=[{"role": "user", "content": raw_text}],
)
tool_use = next(b for b in resp.content if b.type == "tool_use")
invoice = Invoice.model_validate(tool_use.input)
print(invoice.model_dump_json(indent=2, ensure_ascii=False))
الجزء الحاسم هنا هو tool_choice مع type: "tool". ده اللي بيجبر الموديل يرد حصريًا باستدعاء الـ tool ده، وبيستخدم Anthropic منطقيًا constrained decoding على input_schema. بدونه الموديل ممكن يقرر يجاوب نص عادي.
الأرقام: قياس فعلي قبل وبعد
قِسنا في pipeline استخراج فواتير على عينة 1000 طلب:
- بدون Structured Output: 87% نجاح parsing من أول مرة، 11% احتاج retry، 2% فشل نهائي حتى بعد 3 محاولات. متوسط زمن الطلب 1.4 ثانية.
- مع Structured Output (tool use): 100% نجاح parsing، 0% retry، 0% فشل. متوسط زمن الطلب 1.7 ثانية.
التكلفة الكلية انخفضت 14% رغم زيادة زمن الطلب الواحد، لأن الـ retries اتلغت. ده رقم متوافق مع ما نشرته OpenAI في إعلان Structured Outputs (أغسطس 2024) لما قالوا "100% schema adherence" مقابل ~85% بدونها.
Trade-offs اللي مش بيتقالك عنها
كل أداة ليها ثمن. Structured Output عندها 3 تكاليف حقيقية:
- زمن الاستجابة بيزيد 10–25% لأن الـ masking step بيتعمل على كل توكن. مش هتحس بيها في طلب واحد، هتحسها في batch بـ 10K طلب.
- مرونة الموديل بتقل. لو الموديل لقى إن الـ schema غلط للحالة دي (مثلًا الفاتورة مش بتحتوي على رقم)، هيختلق قيمة بدل ما يقولك "الحقل ده مش موجود". الحل: حقول optional صريحة في الـ schema.
- مش كل الموديلات بتدعمها بنفس الجودة. Claude Sonnet 4.6 وOpus 4.7 بيشتغلوا ممتاز. الموديلات الأصغر أو القديمة ممكن تتجاهل بعض constraints المعقدة.
الافتراضات اللي بنى عليها المقال ده
الشرح ده مبني على إنك:
- بتستخدم provider بيدعم Structured Output أو tool use بشكل رسمي (Anthropic, OpenAI, Google).
- الـ schema بتاعك أقل من 50 حقل تقريبًا. للـ schemas الضخمة جدًا (200+ حقل) في degradation في الجودة.
- بياناتك بسيطة نسبيًا (objects, arrays, primitives). الـ recursive schemas دعمها لسه محدود.
متى لا تستخدم Structured Output
في 3 حالات Structured Output هيضرّك أكتر من ما هينفعك:
- chatbot عام للمستخدم النهائي — مينفعش تجبر الموديل يرد JSON والمستخدم بيكلمه طبيعي.
- مهام إبداعية زي كتابة محتوى أو brainstorming — التقييد هيقلل التنوع.
- schemas ديناميكية بتتغير كل ثانية — تكلفة بناء وإرسال الـ schema هتبوظ الأداء.
في الحالات دي ارجع للنص الحر مع post-processing بسيط، أو استخدم function calling عادي بدون الـ tool_choice الإجباري.
سيناريو لمحترف: مزج Structured Output مع validation متقدم
لو عندك تطبيق fintech بيقرأ كشوف حساب، ممكن تستخدم Pydantic validators بعد ما الموديل يرد:
from pydantic import BaseModel, field_validator
from datetime import date
class BankTransaction(BaseModel):
amount: float
transaction_date: date
counterparty: str
@field_validator("amount")
@classmethod
def positive_amount(cls, v: float) -> float:
if v <= 0:
raise ValueError("amount must be positive")
return v
@field_validator("transaction_date")
@classmethod
def not_future(cls, v: date) -> date:
if v > date.today():
raise ValueError("transaction_date cannot be in the future")
return v
الـ Structured Output بيضمن إن الحقول موجودة وأنواعها صح. الـ validators بتضمن إن القيم منطقية. الاتنين سوا = طبقتين دفاع. الطبقة الأولى ضد الـ format errors، التانية ضد الـ semantic errors.
الخطوة التالية
افتح أقرب pipeline AI عندك بيرجع JSON. حدد فيه نقطة واحدة بالظبط: استدعاء الموديل اللي ناتجه بيدخل DB أو API. حوّله لـ tool use مع tool_choice إجباري كما في المثال فوق، ثم قِس نسبة نجاح parsing على 100 طلب قبل وبعد. لو النسبة كانت أصلًا 100% مش محتاج التغيير. لو أقل من 99%، التحويل هيوفّر retries فعلية.
المصادر
- Anthropic Tool Use documentation — docs.anthropic.com/tool-use (تحديث 2025).
- OpenAI Structured Outputs announcement — openai.com/structured-outputs (أغسطس 2024).
- Pydantic v2 JSON Schema generation — docs.pydantic.dev/json_schema.
- Outlines: Constrained Decoding research — github.com/outlines-dev/outlines.
- Willard & Louf, "Efficient Guided Generation for LLMs" (2023) — arXiv:2307.09702.