تصور کنید یک دستیار هوش مصنوعی در سطح تولید (Production-grade) در حال ارسال دادهها به یک پایگاهداده است؛ در چنین محیطی، توهم زدن (Hallucinating) در نام یک فیلد یا حذف یک کلید ضروری، یک شکست غیرقابلقبول است. OpenAI با پیادهسازی خروجیهای ساختاریافته (Structured Outputs)، این ریسک را حذف کرده و وضعیتی را ایجاد کرده که در آن تخلف مدل از طرحواره JSON ارائه شده، از نظر ریاضی غیرممکن است.
این تغییر، هدف را از «تلاش برای قالببندی درست» (Best-effort formatting) به «تضمین ساختاری» (Structural guarantees) تغییر میدهد. برای توسعهدهندگان، این به معنای پایان دوران بلوکهای try/catch دفاعی، حلقههای تکرار برای اصلاح خطا (Retry loops) و ترفندهای Regex برای پاکسازی خروجیهای JSON.parse است. همانطور که در تحلیلهای قبلی ما دربارهی اینکه چگونه برخی APIها خروجیهای JSON معتبر اما از نظر معنایی غلط برمیگردانند اشاره کردیم، مشکل اصلی همواره عدم قطعیت در فرمت خروجی بود. در واقع، تضاد میان اعتبار نحوی و صحت معنایی یکی از بزرگترین چالشهای توسعهدهندگان در مواجهه با مدلهای زبانی بوده است.
یک مدل زبانی بزرگ (LLM) — مثل کتابخانهداری که میلیاردها صفحه را خوانده و حالا با همان لحن کتابها جواب میدهد — را در نظر بگیرید که وظیفه دارد نام مشتری، شماره سفارش و دستهبندی مشکل را از یک متن چت استخراج کند. در گذشته، مدل ممکن بود به جای category از عبارت issue_type استفاده کند و باعث شکست کد در مراحل بعدی شود. اما با خروجیهای ساختاریافته، مدل فیزیکاً قادر نیست توکنی را انتخاب کند که با طرحواره سازگار نباشد. طبق اعلام OpenAI، تا زمانی که کد شما پاسخ را دریافت میکند، تطابق آن با شکل درخواستی تضمین شده است.
سازوکار: رمزگشایی محدود
خروجیهای ساختاریافته با تبدیل یک JSON Schema به یک گرامر مستقل از متن (CFG) عمل میکنند. این گرامر در طول فرآیند استنتاج (Inference) — لحظهای که مدل واقعاً جواب تولید میکند، شبیه خودِ آشپزی و نه دورهی آموزش آشپز — به عنوان یک فیلتر عمل میکند.
در حالت عادی، یک مدل خودبازگشتی (Autoregressive) متن را توکن به توکن و بر اساس نمونهبرداری از یک توزیع احتمالی روی واژگان خود (با توجه به زمینه یا Context موجود) تولید میکند. در یک تنظیمات استاندارد، هر توکنی میتواند انتخاب شود. اما رمزگشایی محدود (Constrained Decoding) یک زبان رسمی معرفی میکند که از JSON Schema شما مشتق شده و تعیین میکند کدام توالیها مجاز هستند.
در هر گام تولید، سیستم تمام توکنهایی که منجر به یک وضعیت غیرمجاز (Illegal state) میشوند را ماسک (Mask) کرده و توزیع احتمالات را روی توکنهای باقیمانده بازتنظیم (Renormalize) میکند. مدل هرگز از محدوده زبان مجاز خارج نمیشود زیرا محدودیت در خودِ حلقه رمزگشایی قرار دارد.
پیادهسازی OpenAI طرحواره را به یک CFG تبدیل کرده، آن را به یک ساختار دادهای کششده (Cached data structure) پیشپردازش میکند و در هر گام تولید توکن به این ساختار مراجعه میکند. برای مثال، طرحوارهای را به این شکل در نظر بگیرید:
{
"type": "object",
"properties": {
"name": { "type": "string" },
"order_id": { "type": "integer" },
"category": { "enum": ["billing", "shipping", "returns"] }
},
"required": ["name", "order_id", "category"],
"additionalProperties": false
}
این طرحواره به گرامری تبدیل میشود که در آن یک شیء استخراجشده باید دقیقاً شامل این سه کلید باشد. گرامر موقعیت را ردیابی میکند: اینکه آیا مدل داخل یک شیء است و انتظار نام یک ویژگی (Property name) را دارد، یا داخل مقداری است که به نوع خاصی محدود شده، و یا داخل یک Enum است که فقط گزینههای لیست شده را میپذیرد. در هر پیشوند (Prefix)، وضعیت گرامر دقیقاً کدگذاری میکند که کدام توکنها توالی جزئی را در مسیری نگه میدارند که هنوز میتواند به یک سند JSON معتبر ختم شود.
این بدان معناست که مدل نمیتواند فیلدهای ضروری را حذف کند، نمیتواند ویژگیهای اضافی اضافه کند (زمانی که additionalProperties برابر false است)، نمیتواند به جای عدد صحیح از رشته استفاده کند و نمیتواند دستهبندیای خارج از Enum انتخاب کند. این موارد به «ناممکنهای سطح توکن» تبدیل میشوند.

تفاوت حالت JSON با خروجیهای ساختاریافته
بسیار مهم است که این قابلیت را با «حالت JSON» (JSON mode) قدیمی متمایز کنیم. در حالی که حالت JSON تضمین میکند که خروجی از نظر نحوی (Syntactically) یک JSON معتبر است، اما شکل (Shape) خاصی را تحمیل نمیکند. حالت JSON در APIهای OpenAI با تنظیم text.format روی {"type": "json_object"} فعال میشود.
- حالت JSON: تضمین میکند
JSON.parse()خطا نمیدهد، اما شیء حاصل میتواند کلیدها، انواع داده و تودرتویی (Nesting) دلخواهی داشته باشد. مدل ممکن است فیلدی را به جایorder_idبه صورتorderIdبنامد یا متنی توضیحی در کنار JSON برگرداند. - خروجیهای ساختاریافته: از
json_schemaو تنظیمstrict: trueاستفاده میکند تا گرامر عمومی JSON را با گرامری جایگزین کند که دقیقاً از طرحواره شما مشتق شده است. کلیدهای ضروری، انواع داده و عضویت در Enum همگی در طول تولید اجبار میشوند.
بر اساس ارزیابیهای داخلی برای مدل gpt-4o-2024-08-06، این تنظیمات سختگیرانه منجر به تطابق ۱۰۰ درصدی با طرحوارههای پیچیده شده است. برای یک دستیار پشتیبانی مشتری، این تفاوت، مرز بین یک تغییر شکستدهنده (Breaking change) در پاسخ API و یک قرارداد دادهای تضمینشده است.
رابطه با فراخوانی تابع
فراخوانی تابع (Function Calling) در واقع خروجیهای ساختاریافته در لباس مبدل است. وقتی توسعهدهندهای ابزاری را با یک JSON Schema برای توصیف پارامترهایش تعریف میکند، ارائهدهنده از رمزگشایی محدود استفاده میکند تا تضمین کند هر آرگومانی که برای فراخوانی تابع ارسال میشود با آن طرحواره مطابقت دارد. مدل نام تابع را انتخاب کرده و آرگومانها را پر میکند؛ گرامر تضمین میکند که آرگومانها با انواع declared و فیلدهای ضروری سازگار باشند.
برای استخراج دادههای خالص، توسعهدهندگان میتوانند یک تابع «صوری» (Dummy function) تعریف کنند که تنها هدفش پذیرش دادههای ساختاریافته مورد نظر است. این تابع هرگز در واقعیت اجرا نمیشود؛ بلکه صرفاً وجود دارد تا به موتور رمزگشایی یک طرحواره برای اجرا بدهد. برای دستیار ما، ممکن است تابعی مثل log_customer_issue(name: string, order_id: int, category: enum) تعریف کنید و فراخوانی تابع را به عنوان خروجی ساختاریافته خود در نظر بگیرید.
خروجیهای ساختاریافته در سطح پاسخ (Response-level) با استفاده از json_schema زمانی برتری دارند که به تودرتویی دلخواه نیاز دارید، میخواهید یک طرحواره را در فراخوانیهای متعدد بدون انتزاعِ تابع بازاستفاده کنید، یا پارادایم فراخوانی ابزار (Tool-calling) را برای استخراج دادههای خالص دشوار میبینید. هر دو مکانیسم بر یک زیرساخت رمزگشایی محدود تکیه دارند؛ انتخاب بین آنها ارگونومیک است، نه فنی.
موازنه «اتلاف تصویر» (Projection Loss)
رمزگشایی محدود بدون هزینه نیست. اجبار مدل به یک فضای توکنی محدودتر میتواند کیفیت خروجی را کاهش دهد، بهویژه زمانی که طرحواره بسیار سختگیرانه باشد یا مدل کوچک باشد.
این پدیده «اتلاف تصویر» نام دارد. مقاله Draft-Conditioned Constrained Decoding (DCCD) این موضوع را به عنوان یک «مالیات» بر هوش فرمولبندی میکند: شما در حال دور ریختن جرم احتمالی (Probability mass) هستید که به توالیهای غیرمجاز اختصاص یافته است. وقتی طرحواره بسیاری از مسیرهای با احتمال بالا را که مدل میخواهد طی کند حذف میکند، ممکن است استدلال ترجیحی مدل برای ارضای فرمت، ماسک شود.
برای حل این مشکل، مقاله DCCD یک رویکرد دو مرحلهای را پیشنهاد میدهد: جداسازی استدلال از قالببندی.
۱. پیشنویس بدون محدودیت: مدل ابتدا یک پیشنویس تولید میکند و اجازه مییابد بدون محدودیت فکر کند.
۲. نگاشت محدود: آن پیشنویس به عنوان زمینه (Context) به یک مرحله رمزگشایی محدود داده میشود که محتوا را به طرحواره نگاشت میکند.
این تکنیک صحت ساختاریافته را در بنچمارک GSM8K با استفاده از یک مدل ۱ میلیارد پارامتری از ۱۵.۲٪ به ۳۹.۰٪ افزایش داد. برای دستیار ما، اگر استخراج داده نیاز به استدلال داشته باشد (مثلاً: «این مشتری به سه مشکل مجزا اشاره کرده، کدام یک اصلی است؟»)، این استدلال باید در یک گذر اول بدون محدودیت اتفاق بیفتد. توکنهای استدلالی OpenAI و قابلیتهای تفکر گسترده (Extended thinking) در Anthropic با نگه داشتن استدلالهای داخلی خارج از پوسته طرحواره و محدود کردن تنها خروجی نهایی قابل مشاهده، به نتیجه مشابهی میرسند.
صحت ساختاری در برابر صحت معنایی
توسعهدهندگان باید درک کنند که تضمینهای ساختاری، تضمینهای معنایی نیستند. یک مدل میتواند یک شیء JSON را با فرمت بینقص تولید کند که حاوی یک نام محتمل اما کاملاً غلط است، یا یک شکایت مربوط به ارسال را به دسته «صورتحساب» اختصاص دهد. گرامر فقط فرم را محدود میکند، نه معنا را.
علاوه بر این، محدودیتهای عددی (مانند حداقل مقدار ۰) را نمیتوان در سطح توکن اجرا کرد. چون توکنها به صورت افزایشی تولید میشوند، یک CFG نمیتواند بداند آیا یک عدد تکمیلشده در یک محدوده قرار میگیرد یا خیر، تا زمانی که تولید عدد به پایان برسد. کتابخانههایی مانند Guidance صراحتاً ذکر میکنند که محدودیتهای عددی «واقعاً نمیتوانند در زمینه تولید LLM پشتیبانی شوند».
برای مدیریت این موضوع، خط لولههای تولیدی باید اعتبارسنجی معنایی را روی محدودیتهای نحوی لایهبندی کنند:
- Guardrails AI: فراخوانیهای LLM را با اعتبارسنجی JSON Schema و اعتبارسنجهای سطح پایتون میپوشاند. اگر یک فیلد باید یک ایمیل معتبر باشد یا یک URL باید قابل دسترسی باشد، شما یک اعتبارسنج مینویسید. در صورت شکست اعتبارسنجی، Guardrails مدل را با بازخورد خطا مجدداً پرامپت میکند.
- Instructor: رویکرد مشابهی را با استفاده از مدلهای Pydantic و تکرارهای خودکار (Automatic retries) برای تضمین یکپارچگی دادهها به کار میگیرد.
برای دستیار پشتیبانی مشتری، شما از خروجیهای ساختاریافته استفاده میکنید تا تضمین کنید order_id یک عدد صحیح و category یکی از سه مقدار Enum است. سپس، یک لایه اعتبارسنجی اضافه میکنید که order_id را با پایگاهداده شما چک کند و مواردی را که دستهبندی استخراجشده با وضعیت واقعی سفارش در تضاد است، علامتگذاری کند.
کالبدشکافی یک خط لوله (Pipeline) قابلاعتماد
یک خط لوله داده حرفهای اکنون از توالی خاصی پیروی میکند تا با LLM به عنوان یک جزء قابلاعتماد برخورد کند، نه یک «جعبه جادویی»:
۱. استدلال بدون محدودیت: مدل مسئله اصلی را در یک پیشنویس شناسایی میکند (مثلاً با استفاده از توکنهای استدلالی).
۲. رمزگشایی محدود: پیشنویس از طریق ماسک کردن توکنها به یک JSON Schema نگاشت میشود. OpenAI گرامر کامپایلشده را برای هر طرحواره کش میکند، بنابراین فقط اولین درخواست هزینه کامپایل را میپردازد و فراخوانیهای بعدی از آرتیفکت کششده استفاده میکنند.
۳. اعتبارسنجی معنایی: کد بررسی میکند که آیا دادهها (مثلاً شناسه سفارش) در پایگاهداده وجود دارند یا محدودیتهای عددی را رعایت میکنند.
۴. حلقه بازخورد: اگر اعتبارسنجی شکست بخورد، سیستم مدل را با زمینه خطای خاص برای اصلاح اشتباه معنایی مجدداً پرامپت میکند.
در حالی که ماسک کردن در سطح توکن سربار اندکی در هر گام اضافه میکند، حذف شکستهای تجزیه (Parsing failures) و تکرارها معمولاً باعث میشود کل سیستم سریعتر و ارزانتر از حالت JSON (به روش تلاش حداکثری) با کدهای دفاعی باشد.
مرجع سریع
| ویژگی | مقدار |
|---|---|
| سینتکس JSON mode در OpenAI | text.format: {"type": "json_object"} |
| سینتکس خروجیهای ساختاریافته OpenAI | response_format: {"type": "json_schema", "json_schema": {...}, "strict": true} |
| تضمین JSON mode | فقط JSON معتبر از نظر نحوی |
| تضمین خروجیهای ساختاریافته | JSON مطابق با طرحواره (ساختاری) |
| آنچه تضمین نمیشود | صحت معنایی، محدودیتهای عددی |
| هزینه کامپایل | یکبار برای هر طرحواره، کششده توسط ارائهدهنده |
| معادل در Anthropic | output_config.format با type: "json_schema" |
| کتابخانههای لایه اعتبارسنجی | Guardrails, Instructor, Pydantic |
| تکنیک رمزگشایی دو مرحلهای | Draft-Conditioned Constrained Decoding (DCCD) |
سوالات متداول
س: آیا خروجیهای ساختاریافته هزینه توکن یا تأخیر (Latency) بیشتری دارند؟
طرحواره جزو توکنهای ورودی محاسبه میشود و اولین درخواست با یک طرحواره جدید هزینه کامپایل یکبارهای دارد. با این حال، سربار تولید هر توکن کم است زیرا ارائهدهنده از ساختارهای دادهای کارآمد برای ماسک کردن توکنها استفاده میکند. هزینه نهایی اغلب کمتر است زیرا تکرارهای ناشی از خطاهای تجزیه حذف میشوند.
س: آیا میتوانم از خروجیهای ساختاریافته با استریمینگ (Streaming) استفاده کنم؟
بله. ارائهدهندگانی مانند OpenAI از استریمینگ پشتیبانی میکنند. مدل توکنها را همانطور که تولید میشوند ارسال میکند و تضمین میشود که این توکنها پیشوندهایی از یک سند JSON معتبر و مطابق با طرحواره هستند. بسته به تحمل پارسر شما در برابر JSONهای ناقص، ممکن است نیاز به بافر کردن خروجیهای جزئی داشته باشید.
س: اگر طرحواره من برای کامپایلر گرامر بیش از حد پیچیده باشد چه میشود؟
ارائهدهندگان از یک زیرمجموعه کاربردی از JSON Schema پشتیبانی میکنند. اشیاء تودرتو، آرایهها، Enumها، فیلدهای ضروری و additionalProperties به خوبی پشتیبانی میشوند. ویژگیهایی مانند $ref ،allOf و anyOf سطوح متفاوتی از پشتیبانی دارند. برای محدودیتهایی که در CFG قابل بیان نیستند (محدودههای عددی، وابستگیهای بین فیلدی)، از اعتبارسنجی پس از تولید (Post-hoc validation) استفاده کنید.
س: چگونه فیلدهای اختیاری (Optional) را در طرحواره مدیریت کنم؟
آنها را در JSON Schema تعریف کنید اما در آرایه required قرار ندهید. گرامر به مدل اجازه میدهد فیلدهای اختیاری را شامل شود یا حذف کند. اگر مدل فیلد اختیاری را حذف کند، JSON حاصل صرفاً آن کلید را نخواهد داشت. اگر فیلد را شامل شود، مقدار آن باید با نوع declared مطابقت داشته باشد.
س: اگر نتوانم از خروجیهای ساختاریافته بومی ارائهدهنده استفاده کنم، جایگزین چیست؟
از کتابخانههایی مانند Guardrails یا Instructor استفاده کنید. اینها خروجی مدل را تجزیه کرده (با مدیریت Markdown code fences)، با طرحواره شما اعتبارسنجی میکنند و در صورت شکست، با بازخورد خطا مجدداً پرامپت میکنند. این روش با هر ارائهدهندهای، از جمله مدلهای محلی، کار میکند اما به قیمت احتمال چندین فراخوانی LLM است.
آنچه این قابلیت برای این حوزه تغییر میدهد، فرض ما درباره قابلیت اطمینان LLM است. ما در حال حرکت از دنیای «مهندسی پرامپت برای فرمت» به سمت «اجبار در سطح کامپایلر برای شکل دادهها» هستیم.
گام بعدی شما
- اگر از
json_modeاستفاده میکنید، همین امروز آن را بهjson_schemaبا تنظیمstrict: trueارتقا دهید تا نیاز به بلوکهای try/catch حذف شود. - برای کارهای حساس، یک لایه اعتبارسنجی با کتابخانه Instructor یا Pydantic در پشت خروجی مدل قرار دهید.
- در مواردی که استخراج داده نیاز به تحلیل پیچیده دارد، ابتدا از مدل بخواهید استدلال خود را بنویسد و سپس خروجی ساختاریافته را تولید کند.
اما داستان سختافزاری این تحول و نحوه مدیریت کش گرامرها در مقیاس بالا حتی شگفتانگیزتر است — به تحلیل ما دربارهی بهینهسازیهای KV Cache مراجعه کنید.




گفتگو