فاز 13 · درس 4

خروجی ساختاریافته در مدل‌های زبانی با JSON Schema و Pydantic

نوع
ساختنی
زمان
حدود 75 دقیقه
زبان‌ها
Python (stdlib, JSON Schema 2020-12 subset)
پیش‌نیاز
فاز 13 · درس 02 (بررسی عمیق فراخوانی توابع یا Function Calling)
یک قالب فلزی هندسی دقیق و منظم که داده‌های آزاد را در یک ساختار منظم فشرده و هدایت می‌کند
فهرست مطالب
  1. اهداف یادگیری
  2. صورت مسئله
  3. مفاهیم کلیدی
  4. استاندارد JSON Schema 2020-12 — زبان مشترک
  5. اتصال پایتون با Pydantic
  6. اتصال TypeScript با Zod
  7. امتناع‌ها (Refusals)
  8. دیکود مقید در مدل‌های بازمتن
  9. سه حالت شکست
  10. استراتژی تلاش مجدد (Retry)
  11. پشتیبانی در مدل‌های کوچک
  12. به کار ببندید
  13. تحویل پروژه
  14. تمرین‌ها
  15. اصطلاحات کلیدی
  16. برای مطالعه‌ی بیشتر

درخواست ساده در پرامپت برای دریافت پاسخ در قالب JSON حتی در قوی‌ترین مدل‌های زبانی نیز بین 5 تا 15 درصد مواقع با شکست مواجه می‌شود؛ از جا افتادن آکولاژ گرفته تا تولید فیلدهای ساختگی یا خروجی‌های ناقص. در پایپ‌لاین‌های عملیاتی و حساس نرم‌افزاری، چنین خطاهایی کل فرایند پردازش را مختل می‌کنند و روش‌های اعتبارسنجی پس از تولید نیز هزینه‌ی توکن و تأخیر پاسخ‌ها را به شدت افزایش می‌دهند.

راهکار پایدار برای این مسئله، استفاده از «دیکود مقید» (Constrained Decoding) و خروجی‌های ساختاریافته است؛ قابلیتی که در آن مدل به صورت ریاضی از تولید توکن‌های مغایر با اسکیما منع می‌شود. در این درس یاد می‌گیرید که چگونه با کمک JSON Schema 2020-12، حالت Strict در OpenAI و ابزارهایی مانند Pydantic و Zod، خروجی‌هایی با تضمین 100 درصدی ساختار بسازید و سناریوهای امتناع مدل (Refusal) را به صورت استاندارد مدیریت کنید.

اگر از ایران به API ارائه‌دهندگانی مثل OpenAI یا Gemini دسترسی دارید، ممکن است به دلیل محدودیت دسترسی بر پایه‌ی IP با چالش‌هایی مواجه شوید؛ همچنین به دلیل محدودیت کارت‌های بانکی ایرانی در پرداخت مستقیم، استفاده از گیت‌وی‌های واسط سازگار با OpenAI گزینه‌ی رایجی است. خوشبختانه سازوکار خروجی ساختاریافته روی کدهای سازگار با API و حتی مدل‌های بازمتن محلی نیز به خوبی پیاده‌سازی می‌شود.

«خواهش مؤدبانه از مدل برای برگرداندن JSON» حتی در پیشرفته‌ترین مدل‌ها (Frontier models) هم در 5 تا 15 درصد مواقع شکست می‌خورد. خروجی‌های ساختاریافته (Structured outputs) این شکاف را با دیکود مقید (Constrained decoding) پر می‌کنند: مدل عملاً نمی‌تواند توکنی تولید کند که اسکیما را نقض کند. حالت Strict در OpenAI، فراخوانی ابزار مقید به اسکیما در Anthropic، پارامتر responseSchema در Gemini، خروجی output_type در Pydantic AI و متد .parse در Zod، همگی پنج جلوه‌ی بیرونی از یک ایده‌ی واحدند. در این درس، اعتبارسنج اسکیما و قرارداد حالت Strict را می‌سازیم؛ ابزارهایی که در تمام پایپ‌لاین‌های استخراج داده در محیط پروداکشن به کارتان خواهند آمد.

اهداف یادگیری

  • نوشتن یک JSON Schema بر پایه‌ی پیش‌نویس 2020-12 برای استخراج داده با قیدهای مناسب (enum، حداقل و حداکثر، فیلدهای اجباری، pattern).
  • درک این‌که چرا حالت Strict و دیکود مقید، تضمین‌هایی کاملاً متفاوت از روش «اعتبارسنجی پس از تولید» می‌دهند.
  • تفکیک سه حالت شکست (Failure modes): خطای تجزیه (Parse error)، نقض اسکیما (Schema violation) و امتناع مدل (Model refusal).
  • پیاده‌سازی پایپ‌لاین استخراج داده به همراه ترمیم تایپ‌شده و مدیریت ساختاریافته‌ی موارد امتناع.

صورت مسئله

ایجنتی که ایمیل سفارش خرید را می‌خواند، باید متن آزاد را به ساختار {customer, line_items, total_usd} تبدیل کند. برای این کار سه رویکرد وجود دارد:

رویکرد اول: درخواست JSON در پرامپت. «پاسخ را در قالب JSON با فیلدهای customer، line_items و total_usd برگردان.» این روش روی پیشرفته‌ترین مدل‌ها در 85 تا 95 درصد مواقع جواب می‌دهد، اما به 6 شکل شکست می‌خورد: جا افتادن آکولاژ، وجود کامای اضافه در انتها (Trailing comma)، نوع‌داده‌های اشتباه، فیلدهای ساختگی ناشی از توهم مدل، هرس شدن (Truncation) خروجی به خاطر محدودیت سقف توکن‌ها، و نشت متن‌های اضافی مثل «Here is your JSON:».

رویکرد دوم: اعتبارسنجی پس از تولید. مدل خروجی را آزادانه تولید می‌کند؛ سپس آن را پارس کرده و با اسکیما اعتبارسنجی می‌کنید و اگر خطا داشت، دوباره تلاش می‌کنید (Retry). این روش قابل اتکاست ولی گران تمام می‌شود؛ بابت هر تلاش مجدد باید هزینه بدهید، و هر بار باگ هرس رخ دهد، یک نوبت گفت‌وگو (Turn) اضافه به پایپ‌لاین تحمیل می‌کند.

رویکرد سوم: دیکود مقید. ارائه‌دهنده، اسکیما را مستقیماً هنگام رمزگشایی (Decode) اعمال می‌کند. توکن‌های نامعتبر از همان ابتدا از توزیع نمونه‌برداری حذف (Mask) می‌شوند. پارس شدن و انطباق قطعی خروجی با اسکیما کاملاً تضمین می‌شود. در این حالت، همه‌ی شکل‌های شکست فقط به یک حالت محدود می‌شوند: امتناع یا Refusal (مدل به این نتیجه می‌رسد که ورودی با اسکیما همخوانی ندارد).

تمام ارائه‌دهندگان پیشرو در سال 2026 شکلی از رویکرد سوم را پیاده کرده‌اند:

  • OpenAI. گزینه‌ی response_format: {type: "json_schema", strict: true} به همراه فیلد refusal در پاسخ، برای مواقعی که مدل از جواب‌دادن سر باز می‌زند.
  • Anthropic. اعمال اسکیما روی ورودی‌های فراخوانی ابزار (tool_use)؛ اینجا مقداری به نام stop_reason: "refusal" وجود ندارد، اما پایان نوبت (end_turn) بدون فراخوانی ابزار، نشانه‌ی امتناع است.
  • Gemini. پارامتر responseSchema در سطح درخواست؛ جمینای در سال 2026 برای تایپ‌های منتخب، قیدهای گرامری در سطح توکن ارائه می‌دهد.
  • Pydantic AI. آرگومان output_type=InvoiceModel یک RunResult ساختاریافته با تایپ دقیقِ InvoiceModel برمی‌گرداند.
  • Zod (در اکوسیستم TypeScript). پارسر زمان اجرا (Runtime) که خروجی ارائه‌دهنده را در برابر اسکیمای Zod اعتبارسنجی می‌کند؛ این ابزار با متد beta.chat.completions.parse در OpenAI جفت می‌شود.

نقطه‌ی اشتراک همه‌ی این‌ها یک چیز است: اسکیما را یک بار تعریف کنید و آن را سرتاسر پایپ‌لاین اعمال کنید.

مفاهیم کلیدی

استاندارد JSON Schema 2020-12 — زبان مشترک

همه‌ی ارائه‌دهندگان، استاندارد JSON Schema 2020-12 را قبول دارند. پرکاربردترین ساختارها عبارتند از:

  • type: یکی از مقادیر object، array، string، number، integer، boolean یا null.
  • properties: نگاشت نام فیلد به زیر‌اسکیمای مربوطه.
  • required: فهرستی از فیلدهایی که باید حتماً در خروجی باشند.
  • enum: مجموعه‌ای بسته از مقادیر مجاز.
  • minimum / maximum (برای اعداد)، minLength / maxLength / pattern (برای رشته‌ها).
  • items: زیر‌اسکیمایی که روی تک‌تک عناصر یک آرایه اعمال می‌شود.
  • additionalProperties: مقدار false اجازه‌ی اضافه‌کردن فیلدهای ناشناخته را نمی‌دهد (پیش‌فرض آن در حالت‌های مختلف فرق دارد).

حالت Strict در OpenAI سه شرط دیگر هم اضافه می‌کند: تک‌تک فیلدها باید در لیست required بیایند، همه‌جا additionalProperties: false باشد، و هیچ $ref حل‌نشده‌ای وجود نداشته باشد. در صورت نقض این شروط، API در همان لحظه‌ی درخواست خطای 400 می‌دهد.

اتصال پایتون با Pydantic

نسخه‌ی 2 پایدنتیک (Pydantic) با متد model_json_schema() از روی مدل‌های ساختاریافته به شکل Dataclass، اسکیمای JSON استاندارد تولید می‌کند. کتابخانه‌ی Pydantic AI این سازوکار را در قالب بسته‌ای تمیز درآورده تا کافی باشد این‌طور بنویسید:

class Invoice(BaseModel):
    customer: str
    line_items: list[LineItem]
    total_usd: Decimal

سپس فریم‌ورک ایجنت، این اسکیما را به تناسب ارائه‌دهنده به حالت Strict در OpenAI، فیلد input_schema در Anthropic یا responseSchema در Gemini تبدیل می‌کند. خروجی مدل به صورت یک نمونه‌ی تایپ‌شده از کلاس Invoice برمی‌گردد. در صورت بروز خطای اعتبارسنجی نیز استثنای ValidationError با مسیرهای خطای مشخص بالا می‌آید.

اتصال TypeScript با Zod

کتابخانه‌ی Zod با سینتکس z.object({customer: z.string(), ...}) معادل همین سازوکار در تایپ‌اسکریپت است. پکیج Node رسمی OpenAI تابعی به نام zodResponseFormat(Invoice) ارائه می‌دهد که این ساختار را به اسکیمای استاندارد JSON Schema برای ارسال به API تبدیل می‌کند.

امتناع‌ها (Refusals)

حالت Strict نمی‌تواند مدل را به پاسخ دادن مجبور کند. اگر ورودی در اسکیما نگنجد (مثلاً ایمیل دریافتی به جای فاکتور، یک شعر باشد)، مدل فیلدی به نام refusal همراه با دلیلِ امتناع برمی‌گرداند. کد شما باید با این سناریو به عنوان یک نتیجه‌ی معتبر و رده‌اول برخورد کند، نه به چشم یک خطای سیستمی. فیلد امتناع برای بررسی ایمنی هم کاربرد دارد: اگر از مدل بخواهید شماره‌ی کارت اعتباری را از ایمیلی با محتوای محافظت‌شده استخراج کند، پاسخی از نوع امتناع همراه با دلیل امنیتی پس می‌دهد.

دیکود مقید در مدل‌های بازمتن

پیاده‌سازی‌های مدل‌های بازمتن معمولاً از سه تکنیک استفاده می‌کنند:

  1. رمزگشایی مبتنی بر گرامر (در پکیج‌های outlines، guidance و lm-format-enforcer): ساخت یک ماشین متناهی قطعی (FSM) از روی اسکیما؛ در هر گام، لاجیت‌های (Logits) توکن‌هایی که این ماشین را نقض کنند ماسک می‌شوند.
  2. ماسک کردن لاجیت با پارسر JSON: اجرای هم‌گام یک پارسر استریمینگ JSON در کنار مدل؛ در هر گام، مجموعه‌ی توکن‌های مجاز بعدی محاسبه می‌شود.
  3. دیکودینگ حدسی (Speculative Decoding) به همراه مدل اعتبارسنج (Verifier): یک مدل پیش‌نویس (Draft Model) کم‌هزینه توکن‌ها را پیشنهاد می‌دهد و مدل اعتبارسنج انطباق با اسکیما را تضمین می‌کند.

پلتفرم‌های تجاری در پشت‌صحنه یکی از همین سه روش را انتخاب می‌کنند. در سال 2026، سرعت این فناوری برای خروجی‌های ساختاریافته‌ی کوتاه حتی از تولید متن عادی هم بیشتر است و برای خروجی‌های طولانی تقریباً برابری می‌کند.

سه حالت شکست

  1. خطای تجزیه (Parse error): خروجی، ساختار JSON معتبری ندارد. در حالت Strict این سناریو غیرممکن است، اما روی ارائه‌دهندگان دیگر پیش می‌آید.
  2. نقض اسکیما (Schema violation): خروجی به‌درستی پارس می‌شود ولی قواعد اسکیما را نقض می‌کند. در حالت Strict رخ نمی‌دهد، اما بیرون از آن شایع است.
  3. امتناع (Refusal): مدل از پاسخ‌دادن سر باز می‌زند. باید به عنوان یک نتیجه‌ی ساختاریافته و استاندارد مدیریت شود.

استراتژی تلاش مجدد (Retry)

وقتی بیرون از حالت Strict کار می‌کنید (مثل فراخوانی ابزار در Anthropic، مدل‌های بدون حالت Strict در OpenAI یا نسخه‌های قدیمی‌تر Gemini)، الگوی جبران خطا به این شکل خواهد بود:

generate -> parse -> validate -> if fail, inject error and retry, max 3x

یک بار تلاش مجدد معمولاً مشکل را رفع می‌کند. سه بار تلاش، ناهماهنگی‌های مدل‌های ضعیف‌تر را می‌پوشاند. بیشتر از سه بار تلاش نشانه‌ی نقص در خود اسکیماست؛ یعنی مدل برای بعضی ورودی‌ها اصلاً قادر به تأمین شروط اسکیما نیست و باید پرامپت یا خود اسکیما بازنگری شوند.

پشتیبانی در مدل‌های کوچک

دیکود مقید روی مدل‌های کوچک هم عملکرد فوق‌العاده‌ای دارد. یک مدل بازمتن با 3 میلیارد پارامتر مجهز به قیدهای گرامری، در تسک‌های ساختاریافته بهتر از یک مدل 70 میلیارد پارامتری عمل می‌کند که فقط با پرامپت خام کار می‌کند. دلیل اصلی ارزش خروجی‌های ساختاریافته در پروداکشن دقیقاً همین است: اتکاپذیری سیستم را از ابعاد و حجم مدل مستقل می‌کند.

دیکود مقیدروی JSON بکشید
خروجی ساختاریافته گرامری را در زمان رمزگشایی (Decode) اعمال می‌کند. در هر موقعیت، اسکیما توکن‌های مجاز بعدی را محاسبه کرده و لاجیت سایر توکن‌ها قبل از نمونه‌برداری به منفی بی‌نهایت هدایت می‌شود. مدل همچنان دست به انتخاب می‌زند، اما فقط از میان مجموعه مجاز؛ بنابراین نتیجه همیشه یک JSON قابل‌پارس خواهد بود، نه متن آزادی که برحسب تصادف شبیه JSON به نظر برسد.

به کار ببندید

فایل code/main.py یک اعتبارسنج کمینه‌ی استاندارد JSON Schema 2020-12 با استفاده از کتابخانه‌ی استاندارد پایتون پیاده کرده است که مواردی مثل نوع داده‌ها، فیلدهای اجباری، مقادیر مجاز (enum)، شروط کمینه/بیشینه، الگوها، آرایه‌ها و مشخصه‌ی additionalProperties را بررسی می‌کند. این اسکریپت با اسکیمای Invoice، خروجی‌های ساختگی یک LLM را ارزیابی می‌کند تا مسیرهای خطای تجزیه، نقض اسکیما و امتناع را عملاً نشان دهد. در محیط پروداکشن، خروجی ساختگی را با پاسخ واقعی هر ارائه‌دهنده‌ای جایگزین کنید.

به این بخش‌ها دقت کنید:

  • اعتبارسنج، فهرستی تایپ‌شده از جنس [ValidationError] همراه با مسیر خطا و پیام برمی‌گرداند. این دقیقاً همان ساختاری است که باید به پرامپت تلاش مجدد تحویل دهید.
  • branch مربوط به امتناع اصلاً تلاش مجدد انجام نمی‌دهد؛ بلکه لاگ می‌اندازد و یک امتناع تایپ‌شده برمی‌گرداند. فاز 14 · 09 از این امتناع‌ها به عنوان سیگنال ایمنی استفاده می‌کند.
  • بررسی additionalProperties: false روی ورودی تستی مخرب فعال می‌شود؛ این تست نشان می‌دهد چرا حالت Strict راه ورود فیلدهای ساختگی بر اثر توهم مدل را کاملاً می‌بندد.

تحویل پروژه

خروجی این درس فایل outputs/skill-structured-output-designer.md است. این مهارت با گرفتن یک هدف استخراج از متن آزاد (مثل فاکتورها، تیکت‌های پشتیبانی، رزومه‌ها و موارد مشابه)، یک اسکیمای JSON Schema 2020-12 سازگار با حالت Strict و یک مدل Pydantic متناظر با آن را می‌سازد؛ طوری که مدیریت تایپ‌شده‌ی امتناع و تلاش مجدد هم درون آن قالب‌بندی شده باشد.

تمرین‌ها

  1. فایل code/main.py را اجرا کنید. یک مورد تست چهارم اضافه کنید که مقدار total_usd در آن عددی منفی باشد. مطمئن شوید اعتبارسنج آن را به خاطر قید minimum رد می‌کند.

  2. اعتبارسنج را طوری توسعه دهید که از شرط oneOf همراه با فیلد تفکیک‌کننده (Discriminator) پشتیبانی کند؛ کاربرد رایج: وقتی هر قلم در line_item کالا (Product) یا خدمت (Service) باشد که با فیلد kind تفکیک می‌شوند. حالت Strict در اینجا قواعد حساسی دارد؛ راهنمای structured outputs در مستندات OpenAI را ببینید.

  3. همان اسکیمای Invoice را در قالب یک BaseModel در Pydantic بنویسید و خروجی متد model_json_schema() را با اسکیمای دست‌نویس خود مقایسه کنید. آن یک فیلدی را که Pydantic به‌طور پیش‌فرض تنظیم می‌کند ولی در نسخه‌ی دستی وجود ندارد پیدا کنید.

  4. نرخ امتناع را اندازه بگیرید. 10 ورودی آماده کنید که اطلاعاتی برای استخراج نداشته باشند (مثل متن ترانه، اثبات ریاضی یا ایمیل خالی) و آن‌ها را در حالت Strict به یک مدل واقعی بفرستید. تعداد امتناع‌ها را در مقایسه با خروجی‌های ساختگی بشمارید. این آمار، داده‌ی واقعی (Ground truth) شما برای تست تلاش‌های مجدد آگاه به امتناع خواهد بود.

  5. راهنمای structured outputs در OpenAI را از ابتدا تا انتها بخوانید. آن ساختاری را که در JSON Schema معمولی مجاز است ولی در حالت Strict صریحاً ممنوع شده پیدا کنید. سپس اسکیمایی بسازید که از این ساختار به شکلی غیرضروری استفاده کرده باشد و آن را طوری بازنویسی کنید که با حالت Strict سازگار شود.

اصطلاحات کلیدی

اصطلاح اصطلاح عامیانه معنای دقیق فنی
JSON Schema 2020-12 «مشخصات اسکیما» لهجه‌ای از اسکیمای پیش‌نویس IETF که تمام ارائه‌دهندگان مدرن از آن پشتیبانی می‌کنند
Strict mode «اسکیمای تضمین‌شده» فلگی در OpenAI که اسکیما را از طریق دیکود مقید الزامی می‌کند
دیکود مقید (Constrained decoding) «ماسک کردن لاجیت» اعمال محدودیت در زمان رمزگشایی که جلوی توکن‌های بعدی نامعتبر را می‌گیرد
امتناع (Refusal) «پاسخ ندادن مدل» نتیجه‌ی تایپ‌شده برای شرایطی که متن ورودی با اسکیما جور درنمی‌آید
خطای تجزیه (Parse error) «JSON نامعتبر» متن خروجی به صورت JSON پارس نمی‌شود؛ در حالت Strict ناممکن است
نقض اسکیما (Schema violation) «ساختار اشتباه» خروجی پارس می‌شود اما تایپ‌ها، فیلدهای اجباری، مقادیر enum یا بازه‌ها نقض شده‌اند
additionalProperties: false «فیلد اضافه ممنوع» فیلدهای ناشناخته را ممنوع می‌کند؛ در حالت Strict پلتفرم OpenAI الزامی است
Pydantic BaseModel «خروجی تایپ‌شده» کلاس پایتون که JSON Schema را تولید و خروجی را با آن اعتبارسنجی می‌کند
Zod schema «تایپ خروجی در TypeScript» اسکیمای زمان اجرا در TS برای اعتبارسنجی خروجی ارائه‌دهنده
اعمال گرامر (Grammar enforcement) «دیکود مقید در مدل‌های بازمتن» ماسک کردن لاجیت‌ها بر پایه‌ی FSM، مانند کتابخانه‌های outlines یا guidance

برای مطالعه‌ی بیشتر

سؤالات متداول

تفاوت خروجی ساختاریافته با حالت Strict و اعتبارسنجی معمولی پس از تولید چیست؟

در حالت Strict و دیکود مقید، ارائه‌دهنده در زمان تولید هر توکن، احتمالات توکن‌هایی که ساختار اسکیما را نقض می‌کنند صفر می‌کند؛ بنابراین خطای ساختاری یا تجزیه‌ی JSON هرگز رخ نمی‌دهد، در حالی که در روش‌های معمولی خروجی به شکل متن آزاد تولید شده و ممکن است در فاز اعتبارسنجی رد شود و نیاز به تلاش مجدد و هزینه‌ی توکن داشته باشد.

فیلد Refusal در حالت خروجی ساختاریافته چه کاربردی دارد؟

اگر ورودی کاربر با اسکیمای درخواستی مطابقت نداشته باشد (مثلاً به جای متن فاکتور، شعر ارسال شود) یا به دلایل ایمنی امکان پاسخ وجود نداشته باشد، مدل به جای تولید ساختگی داده، یک فیلد امتناع (Refusal) همراه با دلیل آن برمی‌گرداند که باید به عنوان یک وضعیت معتبر در کد مدیریت شود.

آیا دیکود مقید فقط برای مدل‌های بزرگ ابری کاربرد دارد؟

خیر؛ دیکود مقید و قیدهای گرامری بر روی مدل‌های بازمتن کوچک (مانند مدل‌های 3 میلیارد پارامتری با کتابخانه‌های outlines یا guidance) نیز قابل اجرا هستند و در وظایف ساختاریافته دقتی بالاتر از مدل‌های بسیار بزرگ با پرامپت خام ارائه می‌دهند.

ما رو توی گوگل بیشتر ببین

با افزودن نکست ادمین به «منابع ترجیحی» گوگل، درس‌ها و مطالب ما را در بخش اخبار برتر جستجو بیشتر می‌بینید.