Structured Outputs چیست؟ آموزش دریافت خروجی JSON از مدل‌های هوش مصنوعی با API درواره

Structured Outputs قابلیتی برای دریافت خروجی JSON استاندارد و منطبق با Schema از مدل‌های هوش مصنوعی است. در این راهنمای فنی، تفاوت آن با JSON Mode و Tool Calling را بررسی می‌کنیم و پیاده‌سازی آن با Python، JavaScript، Pydantic، Zod و API درواره را آموزش می‌دهیم.

Share
Darvareh - Structured Outputs

مقدمه

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

فرض کنید می‌خواهید از مدل هوش مصنوعی برای استخراج اطلاعات یک فاکتور استفاده کنید. نرم‌افزار شما انتظار دارد نتیجه‌ای شبیه این دریافت کند:

{
  "invoice_number": "INV-2048",
  "seller": "شرکت نمونه",
  "total_amount": 12500000,
  "currency": "IRR",
  "payment_status": "unpaid"
}

اما یک مدل زبانی ممکن است چنین پاسخی تولید کند:

اطلاعات فاکتور با موفقیت استخراج شد:

شماره فاکتور: INV-2048
فروشنده: شرکت نمونه
مبلغ کل: ۱۲,۵۰۰,۰۰۰ ریال
وضعیت پرداخت: پرداخت‌نشده

این پاسخ برای انسان قابل‌فهم است، اما نرم‌افزار نمی‌تواند به‌سادگی آن را وارد دیتابیس کند، روی مبلغ محاسبات انجام دهد یا براساس وضعیت پرداخت تصمیم بگیرد.

حتی اگر در Prompt از مدل بخواهیم فقط JSON برگرداند، همچنان احتمال بروز مشکلاتی مانند موارد زیر وجود دارد:

  • اضافه‌شدن توضیح قبل یا بعد از JSON
  • حذف یکی از فیلدهای ضروری
  • تغییر نام فیلدها
  • بازگرداندن عدد به‌شکل رشته
  • تولید مقدار خارج از گزینه‌های مجاز
  • قراردادن JSON داخل Markdown
  • ایجاد JSON ناقص یا نامعتبر
  • اضافه‌کردن فیلدهای پیش‌بینی‌نشده
  • استفاده از null در محلی که برنامه انتظار رشته دارد

Structured Outputs برای حل همین مشکل ساخته شده است: تبدیل پاسخ احتمالی و آزاد مدل زبانی به یک قرارداد داده‌ای مشخص، قابل‌پردازش و قابل‌اعتماد.

Structured Outputs چیست؟

Structured Outputs یا «خروجی ساختاریافته» قابلیتی در API مدل‌های هوش مصنوعی است که به توسعه‌دهنده اجازه می‌دهد ساختار دقیق پاسخ را با یک Schema تعریف کند.

این Schema مشخص می‌کند:

  • پاسخ باید Object باشد یا Array
  • چه فیلدهایی در پاسخ وجود داشته باشند
  • نوع هر فیلد چیست
  • کدام فیلدها الزامی هستند
  • چه مقادیری برای یک فیلد مجاز است
  • آیا فیلدهای اضافی پذیرفته می‌شوند
  • ساختار Objectها و Arrayهای تو‌در‌تو چگونه است

برای مثال، می‌توانیم از مدل بخواهیم اطلاعات یک تیکت پشتیبانی را دقیقاً با ساختار زیر برگرداند:

{
  "type": "object",
  "properties": {
    "category": {
      "type": "string",
      "enum": ["billing", "technical", "account", "other"]
    },
    "priority": {
      "type": "string",
      "enum": ["low", "medium", "high", "urgent"]
    },
    "summary": {
      "type": "string"
    },
    "requires_human": {
      "type": "boolean"
    }
  },
  "required": [
    "category",
    "priority",
    "summary",
    "requires_human"
  ],
  "additionalProperties": false
}

در این قرارداد:

  • category باید یکی از چهار مقدار مشخص‌شده باشد.
  • priority فقط می‌تواند یکی از مقادیر مجاز باشد.
  • summary باید رشته باشد.
  • requires_human باید مقدار Boolean داشته باشد.
  • تمام فیلدها الزامی هستند.
  • مدل اجازه ندارد فیلد دیگری اضافه کند.

براساس مستندات رسمی OpenAI، Structured Outputs برای منطبق‌کردن پاسخ مدل با JSON Schema طراحی شده است و می‌تواند مشکلاتی مانند حذف کلیدهای ضروری یا تولید مقدار نامعتبر برای enum را کاهش دهد. این قابلیت همچنین امکان تشخیص برنامه‌نویسی‌شده امتناع مدل از پاسخ‌گویی را فراهم می‌کند. مستندات Structured Outputs در OpenAI

چرا خروجی متنی برای نرم‌افزار کافی نیست؟

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

برای مثال، اگر از مدل بخواهیم احساس مشتری را تشخیص دهد، ممکن است در اجراهای مختلف پاسخ‌های زیر را دریافت کنیم:

مثبت
احساس مشتری مثبت است.
{
  "sentiment": "positive"
}
نتیجه تحلیل: Positive
```json
{"sentiment":"positive"}

تمام این پاسخ‌ها از نظر معنایی یکسان‌اند، اما از دید نرم‌افزار ساختار یکسانی ندارند.

اگر نتیجه قرار است فقط روی صفحه نمایش داده شود، این تفاوت ممکن است مهم نباشد. اما اگر پاسخ قرار است:

- در دیتابیس ذخیره شود؛
- به API دیگری ارسال شود؛
- بخشی از Workflow باشد؛
- در محاسبات استفاده شود؛
- وضعیت یک سفارش را تغییر دهد؛
- یک تیکت را به تیم مشخصی ارجاع دهد؛
- ورودی مرحله بعدی یک Agent باشد؛
- یا به یک ابزار و تابع ارسال شود؛

ساختار پاسخ باید قابل پیش‌بینی باشد.

Structured Outputs فاصله میان «متن تولیدشده توسط مدل» و «داده قابل‌استفاده توسط نرم‌افزار» را کمتر می‌کند.

---

## JSON Schema چیست؟

JSON Schema استانداردی برای توصیف ساختار و محدودیت‌های یک سند JSON است.

با JSON Schema می‌توان تعیین کرد که یک داده JSON چه شکلی داشته باشد و برای هر قسمت آن چه قواعدی اعمال شود.

یک Schema ساده برای اطلاعات کاربر می‌تواند چنین باشد:

```json
{
  "type": "object",
  "properties": {
    "name": {
      "type": "string"
    },
    "age": {
      "type": "integer"
    },
    "is_active": {
      "type": "boolean"
    }
  },
  "required": ["name", "age", "is_active"],
  "additionalProperties": false
}

کلمات کلیدی مهم JSON Schema

type

نوع داده را مشخص می‌کند:

{
  "type": "string"
}

انواع متداول عبارت‌اند از:

  • string
  • number
  • integer
  • boolean
  • object
  • array
  • null

properties

فیلدهای مجاز یک Object را تعریف می‌کند:

{
  "type": "object",
  "properties": {
    "title": {
      "type": "string"
    },
    "price": {
      "type": "number"
    }
  }
}

required

مشخص می‌کند کدام فیلدها باید حتماً در پاسخ حضور داشته باشند:

{
  "required": ["title", "price"]
}

تعریف یک فیلد در properties به‌تنهایی به این معنا نیست که آن فیلد الزامی است. برای الزامی‌کردن آن باید نام فیلد در required نیز قرار گیرد.

enum

مقادیر مجاز را محدود می‌کند:

{
  "type": "string",
  "enum": ["pending", "paid", "failed"]
}

این ویژگی برای وضعیت سفارش، اولویت تیکت، نوع سند، زبان، دسته‌بندی و موارد مشابه بسیار مفید است.

items

ساختار اعضای یک Array را تعیین می‌کند:

{
  "type": "array",
  "items": {
    "type": "string"
  }
}

یا برای Array شامل Object:

{
  "type": "array",
  "items": {
    "type": "object",
    "properties": {
      "name": {
        "type": "string"
      },
      "quantity": {
        "type": "integer"
      }
    },
    "required": ["name", "quantity"],
    "additionalProperties": false
  }
}

additionalProperties

مشخص می‌کند آیا فیلدهایی خارج از properties مجاز هستند یا خیر:

{
  "additionalProperties": false
}

در استاندارد JSON Schema، فیلدهای اضافی به‌صورت پیش‌فرض مجازند. قراردادن additionalProperties: false از حضور کلیدهای تعریف‌نشده جلوگیری می‌کند. راهنمای رسمی JSON Schema

description

هدف فیلد را برای مدل و توسعه‌دهنده توضیح می‌دهد:

{
  "type": "number",
  "description": "مبلغ نهایی فاکتور به ریال و بدون جداکننده"
}

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

تفاوت Prompt معمولی، JSON Mode و Structured Outputs

این سه روش یکسان نیستند.

۱. درخواست JSON در Prompt

ساده‌ترین روش این است که در متن Prompt بنویسیم:

پاسخ را فقط به‌صورت JSON برگردان.

این روش هیچ تضمین فنی ایجاد نمی‌کند. مدل ممکن است:

  • متن توضیحی اضافه کند؛
  • JSON را داخل Markdown قرار دهد؛
  • یکی از فیلدها را حذف کند؛
  • نام فیلد را تغییر دهد؛
  • یا خروجی نامعتبر تولید کند.

این روش برای نمونه‌سازی اولیه قابل‌استفاده است، اما برای سیستم‌های Production کافی نیست.

۲. JSON Mode

در JSON Mode از API می‌خواهیم پاسخ مدل یک JSON معتبر باشد.

در رابط‌های OpenAI-compatible معمولاً ساختار کلی آن چنین است:

{
  "response_format": {
    "type": "json_object"
  }
}

JSON Mode معمولاً تضمین می‌کند خروجی از نظر Syntax یک JSON معتبر باشد، اما الزاماً تضمین نمی‌کند JSON دقیقاً با ساختار مورد انتظار شما منطبق باشد.

برای مثال، شما انتظار دارید:

{
  "name": "علی",
  "age": 32
}

اما مدل ممکن است این JSON معتبر را برگرداند:

{
  "full_name": "علی",
  "age": "32",
  "description": "کاربر جدید"
}

خروجی JSON معتبر است، اما:

  • name به full_name تغییر کرده است.
  • age به‌جای عدد، رشته است.
  • یک فیلد اضافی ایجاد شده است.

۳. Structured Outputs

در Structured Outputs علاوه بر JSON معتبر، Schema مورد انتظار نیز تعریف می‌شود:

{
  "response_format": {
    "type": "json_schema",
    "json_schema": {
      "name": "user_profile",
      "strict": true,
      "schema": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "age": {
            "type": "integer"
          }
        },
        "required": ["name", "age"],
        "additionalProperties": false
      }
    }
  }
}

در این حالت هدف این است که خروجی هم JSON معتبر باشد و هم با Schema تعیین‌شده تطابق داشته باشد.

مقایسه سه روش

روشJSON معتبرتطابق با Schemaمناسب Production
درخواست JSON در Promptتضمین‌شده نیستخیرخیر
JSON Modeبلهخیربرای ساختارهای ساده
Structured Outputsبلهبله، در مدل پشتیبانی‌شدهبله
Tool Calling با حالت Strictآرگومان ساختاریافتهمطابق Schema ابزاربله، برای اجرای عملیات

تفاوت Structured Outputs و Tool Calling

Structured Outputs و Tool Calling هر دو از Schema استفاده می‌کنند، اما هدف متفاوتی دارند.

Structured Outputs

از Structured Outputs زمانی استفاده کنید که هدف، دریافت یک پاسخ ساختاریافته از مدل است.

مثال‌ها:

  • استخراج اطلاعات فاکتور
  • تحلیل رزومه
  • دسته‌بندی تیکت
  • ساخت اطلاعات محصول
  • تولید خروجی برای رابط کاربری
  • تحلیل احساس مشتری
  • تبدیل متن به رکورد دیتابیس

Tool Calling

از Tool Calling زمانی استفاده کنید که مدل باید تشخیص دهد یک تابع یا ابزار فراخوانی شود.

مثال‌ها:

  • جست‌وجوی سفارش
  • دریافت موجودی محصول
  • ثبت تیکت
  • ارسال درخواست بازپرداخت
  • رزرو جلسه
  • استعلام آب‌وهوا
  • اجرای Query روی سیستم داخلی

فرض کنید ابزار زیر را در اختیار مدل قرار می‌دهیم:

{
  "type": "function",
  "function": {
    "name": "get_order_status",
    "description": "دریافت وضعیت سفارش براساس شناسه سفارش",
    "parameters": {
      "type": "object",
      "properties": {
        "order_id": {
          "type": "string"
        }
      },
      "required": ["order_id"],
      "additionalProperties": false
    }
  }
}

مدل خودش سفارش را جست‌وجو نمی‌کند. فقط یک درخواست ساختاریافته برای اجرای تابع تولید می‌کند:

{
  "order_id": "ORD-4521"
}

سپس برنامه شما تابع واقعی را اجرا می‌کند.

قاعده ساده این است:

  • اگر به «داده ساختاریافته» نیاز دارید، از Structured Outputs استفاده کنید.
  • اگر مدل باید «عملی را در یک سیستم انجام دهد»، از Tool Calling استفاده کنید.
  • اگر هم تصمیم‌گیری و هم پاسخ ساختاریافته لازم است، می‌توان هر دو را در معماری Workflow ترکیب کرد.

پیش‌نیاز اتصال به API درواره

درواره یک API سازگار با OpenAI ارائه می‌دهد. بنابراین در بسیاری از ابزارها و SDKهای OpenAI-compatible کافی است base_url را به آدرس زیر تغییر دهید:

https://api.darvareh.ir/v1

همچنین باید کلید API درواره را در اختیار داشته باشید.

پشتیبانی از Structured Outputs به قابلیت مدل و ارائه‌دهنده بالادستی وابسته است. همه مدل‌ها الزاماً json_schema یا حالت strict را پشتیبانی نمی‌کنند. پیش از استفاده در محیط Production، قابلیت مدل انتخابی را بررسی و خروجی آن را آزمایش کنید.

در مثال‌ها به‌جای نام یک مدل خاص از مقدار زیر استفاده می‌کنیم:

MODEL_ID

آن را با شناسه یکی از مدل‌های سازگار در فهرست مدل‌های درواره جایگزین کنید.

آموزش Structured Outputs با Python

نصب SDK

ابتدا SDK پایتون OpenAI را نصب یا به‌روزرسانی کنید:

pip install -U openai

بهتر است کلید API را در متغیر محیطی نگه دارید:

export DARVAREH_API_KEY="YOUR_DARVAREH_API_KEY"

در Windows PowerShell:

$env:DARVAREH_API_KEY="YOUR_DARVAREH_API_KEY"

نمونه پایه

import os
import json
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["DARVAREH_API_KEY"],
    base_url="https://api.darvareh.ir/v1",
)

response = client.chat.completions.create(
    model="MODEL_ID",
    messages=[
        {
            "role": "system",
            "content": (
                "You extract structured customer-support data. "
                "Do not invent information that is not present."
            ),
        },
        {
            "role": "user",
            "content": (
                "سه روز است پرداخت کرده‌ام اما کیف پول حسابم شارژ نشده. "
                "لطفاً سریع بررسی کنید."
            ),
        },
    ],
    response_format={
        "type": "json_schema",
        "json_schema": {
            "name": "support_ticket_analysis",
            "strict": True,
            "schema": {
                "type": "object",
                "properties": {
                    "category": {
                        "type": "string",
                        "enum": [
                            "billing",
                            "technical",
                            "account",
                            "other",
                        ],
                    },
                    "priority": {
                        "type": "string",
                        "enum": [
                            "low",
                            "medium",
                            "high",
                            "urgent",
                        ],
                    },
                    "summary": {
                        "type": "string",
                    },
                    "requires_human": {
                        "type": "boolean",
                    },
                },
                "required": [
                    "category",
                    "priority",
                    "summary",
                    "requires_human",
                ],
                "additionalProperties": False,
            },
        },
    },
)

content = response.choices[0].message.content
data = json.loads(content)

print(data)
print(data["category"])
print(data["requires_human"])

خروجی احتمالی:

{
  "category": "billing",
  "priority": "high",
  "summary": "پرداخت انجام شده اما کیف پول پس از سه روز شارژ نشده است.",
  "requires_human": true
}

چرا باز هم json.loads لازم است؟

در Chat Completions، محتوای پاسخ معمولاً به‌شکل رشته JSON در message.content قرار می‌گیرد. برای تبدیل آن به Dictionary پایتون باید از json.loads استفاده کنید:

data = json.loads(response.choices[0].message.content)

منطبق‌بودن خروجی با Schema به این معنی نیست که نوع خروجی SDK همیشه خودکار به Object پایتون تبدیل می‌شود. این رفتار به endpoint و قابلیت‌های نسخه SDK بستگی دارد.

اعتبارسنجی خروجی با Pydantic

حتی با Structured Outputs بهتر است در مرز ورود داده به برنامه، اعتبارسنجی سمت سرور داشته باشید.

دلایل آن عبارت‌اند از:

  • ممکن است مدل انتخابی Structured Outputs را کامل پشتیبانی نکند.
  • ممکن است ارائه‌دهنده پارامتر را نادیده بگیرد یا تغییر دهد.
  • ممکن است درخواست ناقص شود.
  • ممکن است پاسخ Safety Refusal باشد.
  • ممکن است در آینده Schema یا مدل تغییر کند.
  • هیچ داده خارجی نباید بدون Validation وارد بخش حساس برنامه شود.

نصب Pydantic

pip install -U pydantic

تعریف مدل داده

from typing import Literal
from pydantic import BaseModel, ConfigDict


class SupportTicketAnalysis(BaseModel):
    model_config = ConfigDict(extra="forbid")

    category: Literal["billing", "technical", "account", "other"]
    priority: Literal["low", "medium", "high", "urgent"]
    summary: str
    requires_human: bool

گزینه extra="forbid" باعث می‌شود فیلدهای اضافی پذیرفته نشوند.

اعتبارسنجی پاسخ

import os
from openai import OpenAI
from pydantic import ValidationError

client = OpenAI(
    api_key=os.environ["DARVAREH_API_KEY"],
    base_url="https://api.darvareh.ir/v1",
)

response = client.chat.completions.create(
    model="MODEL_ID",
    messages=[
        {
            "role": "system",
            "content": "درخواست پشتیبانی را دقیق و بدون حدس تحلیل کن.",
        },
        {
            "role": "user",
            "content": "API من از صبح خطای 429 می‌دهد.",
        },
    ],
    response_format={
        "type": "json_schema",
        "json_schema": {
            "name": "support_ticket_analysis",
            "strict": True,
            "schema": SupportTicketAnalysis.model_json_schema(),
        },
    },
)

raw_content = response.choices[0].message.content

try:
    ticket = SupportTicketAnalysis.model_validate_json(raw_content)

    print(ticket.category)
    print(ticket.priority)
    print(ticket.summary)
except ValidationError as error:
    print("Invalid model output:", error)

Pydantic می‌تواند Schema را از Type Hintهای پایتون تولید و پاسخ را نیز با همان مدل اعتبارسنجی کند. این کار از ایجاد دو تعریف جداگانه و ناسازگار جلوگیری می‌کند.

نکته درباره Schema تولیدشده

تمام ویژگی‌های JSON Schema الزاماً توسط تمام مدل‌ها و ارائه‌دهندگان پشتیبانی نمی‌شوند. بنابراین Schema تولیدشده توسط Pydantic را بررسی کنید و در صورت نیاز آن را ساده‌تر کنید.

برای محیط‌های چندارائه‌دهنده‌ای بهتر است از زیرمجموعه‌ای رایج و ساده استفاده کنید:

  • type
  • properties
  • required
  • additionalProperties
  • enum
  • items
  • description
  • ساختارهای ساده تو‌در‌تو

آموزش Structured Outputs با JavaScript و TypeScript

نصب SDK و Zod

npm install openai zod

متغیر محیطی:

export DARVAREH_API_KEY="YOUR_DARVAREH_API_KEY"

نمونه مستقیم با JSON Schema

import OpenAI from "openai";

const client = new OpenAI({
  apiKey: process.env.DARVAREH_API_KEY,
  baseURL: "https://api.darvareh.ir/v1",
});

const completion = await client.chat.completions.create({
  model: "MODEL_ID",
  messages: [
    {
      role: "system",
      content:
        "You extract product information. Return only fields supported by the input.",
    },
    {
      role: "user",
      content:
        "هدفون بی‌سیم مدل X، رنگ مشکی، دارای حذف نویز فعال و ۳۰ ساعت شارژدهی، قیمت ۴۵۰۰۰۰۰ تومان",
    },
  ],
  response_format: {
    type: "json_schema",
    json_schema: {
      name: "product_information",
      strict: true,
      schema: {
        type: "object",
        properties: {
          name: {
            type: "string",
          },
          color: {
            type: ["string", "null"],
          },
          price_toman: {
            type: ["number", "null"],
          },
          features: {
            type: "array",
            items: {
              type: "string",
            },
          },
        },
        required: ["name", "color", "price_toman", "features"],
        additionalProperties: false,
      },
    },
  },
});

const rawContent = completion.choices[0].message.content;
const product = JSON.parse(rawContent);

console.log(product);

خروجی احتمالی:

{
  "name": "هدفون بی‌سیم مدل X",
  "color": "مشکی",
  "price_toman": 4500000,
  "features": [
    "حذف نویز فعال",
    "۳۰ ساعت شارژدهی"
  ]
}

اعتبارسنجی خروجی با Zod

Zod یک کتابخانه TypeScript-first برای تعریف و اعتبارسنجی Schema است. داده ورودی با parse یا safeParse بررسی می‌شود و در صورت اعتبار، نوع TypeScript نیز از Schema قابل استنتاج است. مستندات Zod

import OpenAI from "openai";
import { z } from "zod";

const ProductSchema = z
  .object({
    name: z.string(),
    color: z.string().nullable(),
    price_toman: z.number().nullable(),
    features: z.array(z.string()),
  })
  .strict();

type Product = z.infer<typeof ProductSchema>;

const client = new OpenAI({
  apiKey: process.env.DARVAREH_API_KEY,
  baseURL: "https://api.darvareh.ir/v1",
});

const jsonSchema = z.toJSONSchema(ProductSchema);

const completion = await client.chat.completions.create({
  model: "MODEL_ID",
  messages: [
    {
      role: "system",
      content:
        "Extract product information. Do not guess missing values; use null.",
    },
    {
      role: "user",
      content:
        "مانیتور ۲۷ اینچی مدل A با پنل IPS و نرخ نوسازی ۱۴۴ هرتز",
    },
  ],
  response_format: {
    type: "json_schema",
    json_schema: {
      name: "product_information",
      strict: true,
      schema: jsonSchema,
    },
  },
});

const rawContent = completion.choices[0].message.content;

if (!rawContent) {
  throw new Error("The model returned an empty response.");
}

const untrustedData: unknown = JSON.parse(rawContent);
const result = ProductSchema.safeParse(untrustedData);

if (!result.success) {
  console.error(result.error.flatten());
  throw new Error("The model output did not match ProductSchema.");
}

const product: Product = result.data;

console.log(product.name);
console.log(product.features);

Zod 4 تبدیل مستقیم Schema به JSON Schema را با z.toJSONSchema() ارائه می‌کند. بعضی نوع‌های JavaScript و Zod مانند Date، Map، Set، BigInt و Transformهای خاص معادل مستقیم و قابل‌حملی در JSON Schema ندارند؛ در چنین مواردی بهتر است از نمایش‌های ساده JSON مانند رشته ISO برای تاریخ استفاده شود. تبدیل JSON Schema در Zod

پروژه عملی اول: استخراج اطلاعات فاکتور

یکی از بهترین کاربردهای Structured Outputs، تبدیل متن فاکتور یا نتیجه OCR به داده ساختاریافته است.

Schema فاکتور

{
  "type": "object",
  "properties": {
    "invoice_number": {
      "type": ["string", "null"],
      "description": "شماره فاکتور؛ اگر وجود ندارد null"
    },
    "issue_date": {
      "type": ["string", "null"],
      "description": "تاریخ دقیقاً مطابق سند"
    },
    "seller": {
      "type": ["string", "null"]
    },
    "buyer": {
      "type": ["string", "null"]
    },
    "currency": {
      "type": "string",
      "enum": ["IRR", "TOMAN", "USD", "EUR", "UNKNOWN"]
    },
    "items": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "description": {
            "type": "string"
          },
          "quantity": {
            "type": ["number", "null"]
          },
          "unit_price": {
            "type": ["number", "null"]
          },
          "total_price": {
            "type": ["number", "null"]
          }
        },
        "required": [
          "description",
          "quantity",
          "unit_price",
          "total_price"
        ],
        "additionalProperties": false
      }
    },
    "subtotal": {
      "type": ["number", "null"]
    },
    "tax": {
      "type": ["number", "null"]
    },
    "total": {
      "type": ["number", "null"]
    },
    "warnings": {
      "type": "array",
      "items": {
        "type": "string"
      }
    }
  },
  "required": [
    "invoice_number",
    "issue_date",
    "seller",
    "buyer",
    "currency",
    "items",
    "subtotal",
    "tax",
    "total",
    "warnings"
  ],
  "additionalProperties": false
}

Prompt مناسب استخراج

اطلاعات را فقط از متن سند استخراج کن.

قواعد:
1. هیچ مقدار گمشده‌ای را حدس نزن.
2. برای مقدار اسکالر ناموجود از null استفاده کن.
3. اعداد را بدون جداکننده هزارگان برگردان.
4. ریال و تومان را با یکدیگر تبدیل نکن.
5. اگر واحد پول نامشخص است، UNKNOWN برگردان.
6. اگر جمع اقلام با مبلغ کل سازگار نیست، یک هشدار اضافه کن.
7. متن فارسی را به زبان دیگری ترجمه نکن.

Structured Outputs ساختار را کنترل می‌کند، اما صحت معنایی داده همچنان به مدل، کیفیت سند و Prompt وابسته است.

برای مثال، Schema می‌تواند تضمین کند total عدد یا null باشد، اما نمی‌تواند به‌تنهایی تضمین کند عدد استخراج‌شده واقعاً مبلغ کل صحیح فاکتور است.

به همین دلیل باید کنترل‌های قطعی برنامه‌نویسی نیز اجرا شوند:

calculated_total = sum(
    item["total_price"] or 0
    for item in invoice["items"]
)

if invoice["subtotal"] is not None:
    difference = abs(calculated_total - invoice["subtotal"])

    if difference > 1:
        invoice["warnings"].append(
            "Sum of items does not match subtotal."
        )

پروژه عملی دوم: دسته‌بندی تیکت پشتیبانی

فرض کنید متن زیر از کاربر دریافت شده است:

بعد از پرداخت، پول از حسابم کم شد ولی کیف پول شارژ نشده و شماره پیگیری هم دارم.

Schema:

{
  "type": "object",
  "properties": {
    "department": {
      "type": "string",
      "enum": [
        "finance",
        "technical",
        "sales",
        "account",
        "general"
      ]
    },
    "priority": {
      "type": "string",
      "enum": [
        "low",
        "normal",
        "high",
        "critical"
      ]
    },
    "sentiment": {
      "type": "string",
      "enum": [
        "positive",
        "neutral",
        "negative"
      ]
    },
    "summary": {
      "type": "string"
    },
    "suggested_tags": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "auto_reply_allowed": {
      "type": "boolean"
    }
  },
  "required": [
    "department",
    "priority",
    "sentiment",
    "summary",
    "suggested_tags",
    "auto_reply_allowed"
  ],
  "additionalProperties": false
}

خروجی:

{
  "department": "finance",
  "priority": "high",
  "sentiment": "negative",
  "summary": "کسر مبلغ بدون شارژشدن کیف پول پس از پرداخت",
  "suggested_tags": [
    "payment",
    "wallet",
    "missing-credit"
  ],
  "auto_reply_allowed": false
}

برنامه می‌تواند براساس این داده:

  • تیکت را به واحد مالی ارسال کند؛
  • اولویت را بالا قرار دهد؛
  • پاسخ خودکار را متوقف کند؛
  • شماره پیگیری را درخواست کند؛
  • و رویداد را در سیستم مانیتورینگ ثبت کند.

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

پروژه عملی سوم: تحلیل رزومه

Structured Outputs برای تبدیل رزومه‌های غیرساختاریافته به اطلاعات قابل‌جست‌وجو نیز مفید است.

{
  "type": "object",
  "properties": {
    "full_name": {
      "type": ["string", "null"]
    },
    "email": {
      "type": ["string", "null"]
    },
    "phone": {
      "type": ["string", "null"]
    },
    "skills": {
      "type": "array",
      "items": {
        "type": "string"
      }
    },
    "years_of_experience": {
      "type": ["number", "null"]
    },
    "languages": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "level": {
            "type": "string",
            "enum": [
              "unknown",
              "basic",
              "intermediate",
              "advanced",
              "native"
            ]
          }
        },
        "required": ["name", "level"],
        "additionalProperties": false
      }
    },
    "education": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "degree": {
            "type": ["string", "null"]
          },
          "field": {
            "type": ["string", "null"]
          },
          "institution": {
            "type": ["string", "null"]
          }
        },
        "required": [
          "degree",
          "field",
          "institution"
        ],
        "additionalProperties": false
      }
    }
  },
  "required": [
    "full_name",
    "email",
    "phone",
    "skills",
    "years_of_experience",
    "languages",
    "education"
  ],
  "additionalProperties": false
}

نکته مهم این است که مدل نباید برای اطلاعات ناموجود حدس بزند. برای نمونه، اگر سال شروع و پایان مشاغل در رزومه مشخص نیست، بهتر است years_of_experience برابر null باشد.

همچنین نباید از خروجی مدل برای تصمیم‌گیری خودکار و نهایی درباره استخدام استفاده کرد. مدل می‌تواند در استخراج و سازمان‌دهی داده کمک کند، اما تصمیم انسانی و بررسی سوگیری همچنان ضروری است.

طراحی فیلدهای اختیاری

یکی از چالش‌های Structured Outputs، نمایش اطلاعات اختیاری است.

اگر یک فیلد ممکن است در ورودی وجود نداشته باشد، دو راه رایج داریم.

حذف فیلد از required

{
  "type": "object",
  "properties": {
    "phone": {
      "type": "string"
    }
  }
}

الزامی‌بودن فیلد با امکان null

{
  "type": "object",
  "properties": {
    "phone": {
      "type": ["string", "null"]
    }
  },
  "required": ["phone"],
  "additionalProperties": false
}

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

{
  "phone": null
}

این روش پردازش سمت برنامه را ساده‌تر می‌کند، زیرا لازم نیست در هر مرحله حضور یا عدم حضور کلید بررسی شود.

مدیریت Refusal، پاسخ ناقص و خطا

Structured Outputs به این معنا نیست که هر درخواست حتماً به یک Object معتبر منتهی می‌شود.

ممکن است:

  • مدل به‌دلایل ایمنی از پاسخ خودداری کند؛
  • پاسخ به سقف Token برسد؛
  • درخواست Timeout شود؛
  • ارائه‌دهنده خطا برگرداند؛
  • Schema پشتیبانی نشود؛
  • پاسخ خالی باشد؛
  • مدل یا endpoint انتخابی پارامتر را نپذیرد.

الگوی مناسب مدیریت خطا در Python

import json
from openai import (
    APIConnectionError,
    APIStatusError,
    APITimeoutError,
    RateLimitError,
)
from pydantic import ValidationError


def parse_structured_response(response, model_class):
    if not response.choices:
        raise ValueError("No choices returned by the model.")

    choice = response.choices[0]
    message = choice.message

    refusal = getattr(message, "refusal", None)

    if refusal:
        return {
            "status": "refused",
            "reason": refusal,
            "data": None,
        }

    if choice.finish_reason == "length":
        return {
            "status": "incomplete",
            "reason": "Maximum output length reached.",
            "data": None,
        }

    if not message.content:
        return {
            "status": "empty",
            "reason": "The model returned no content.",
            "data": None,
        }

    try:
        validated = model_class.model_validate_json(
            message.content
        )

        return {
            "status": "success",
            "reason": None,
            "data": validated,
        }

    except (ValidationError, json.JSONDecodeError) as error:
        return {
            "status": "invalid",
            "reason": str(error),
            "data": None,
        }


try:
    response = client.chat.completions.create(
        # request parameters
    )

    result = parse_structured_response(
        response,
        SupportTicketAnalysis,
    )

except RateLimitError:
    result = {
        "status": "retryable_error",
        "reason": "Rate limit exceeded.",
        "data": None,
    }

except APITimeoutError:
    result = {
        "status": "retryable_error",
        "reason": "Request timed out.",
        "data": None,
    }

except APIConnectionError:
    result = {
        "status": "retryable_error",
        "reason": "Could not connect to the API.",
        "data": None,
    }

except APIStatusError as error:
    result = {
        "status": "api_error",
        "reason": f"HTTP {error.status_code}",
        "data": None,
    }

Retry هوشمند

Retry باید فقط برای خطاهای موقت انجام شود:

  • Timeout
  • خطای اتصال
  • 429 Too Many Requests
  • برخی خطاهای 5xx

برای خطاهای زیر Retry بدون تغییر درخواست معمولاً فایده‌ای ندارد:

  • API Key نامعتبر
  • Schema نامعتبر
  • مدل ناسازگار
  • پارامتر پشتیبانی‌نشده
  • خطای Validation تکرارشونده
  • درخواست غیرمجاز

از Exponential Backoff استفاده کنید:

import random
import time


def retry_delay(attempt):
    base = min(2 ** attempt, 30)
    return base + random.uniform(0, 1)

آیا با Structured Outputs دیگر Validation لازم نیست؟

خیر.

Structured Outputs احتمال دریافت ساختار نادرست را بسیار کمتر می‌کند، اما Validation سمت برنامه همچنان باید حفظ شود.

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

Validation می‌تواند در چند سطح انجام شود:

۱. اعتبار ساختاری

آیا پاسخ JSON معتبر و مطابق Schema است؟

ابزارهای مناسب:

  • Pydantic در Python
  • Zod در TypeScript
  • Ajv برای JSON Schema
  • JSON Schema Validatorهای زبان‌های دیگر

۲. اعتبار معنایی

آیا مقادیر از نظر کسب‌وکار منطقی‌اند؟

مثال:

if invoice.total is not None and invoice.total < 0:
    raise ValueError("Invoice total cannot be negative.")

۳. اعتبار رابطه‌ای

آیا فیلدها با یکدیگر سازگارند؟

if order.payment_status == "paid" and order.paid_at is None:
    raise ValueError("paid_at is required for paid orders.")

۴. اعتبار خارجی

آیا داده با سیستم واقعی مطابقت دارد؟

مثلاً مدل ممکن است یک customer_id معتبر از نظر ساختاری تولید کند، اما برنامه باید بررسی کند چنین مشتری‌ای واقعاً در دیتابیس وجود دارد.

Structured Outputs مانع Hallucination نمی‌شود

یکی از مهم‌ترین نکات این است که «ساختار معتبر» با «محتوای صحیح» یکسان نیست.

این خروجی ممکن است کاملاً با Schema منطبق باشد:

{
  "invoice_number": "INV-9999",
  "total": 25000000,
  "currency": "IRR"
}

اما اگر شماره فاکتور یا مبلغ در سند اصلی وجود نداشته باشد، مدل اطلاعات را ساخته است.

Structured Outputs تضمین می‌کند ظرف داده شکل صحیحی دارد؛ الزاماً تضمین نمی‌کند محتوای داخل ظرف واقعی است.

برای کاهش Hallucination:

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

اضافه‌کردن شواهد به Schema

{
  "type": "object",
  "properties": {
    "value": {
      "type": ["string", "null"]
    },
    "evidence": {
      "type": ["string", "null"],
      "description": "عبارت کوتاه موجود در سند که مقدار از آن استخراج شده است"
    },
    "confidence": {
      "type": "string",
      "enum": ["low", "medium", "high"]
    }
  },
  "required": ["value", "evidence", "confidence"],
  "additionalProperties": false
}

البته confidence تولیدشده توسط خود مدل احتمال آماری کالیبره‌شده نیست. از آن فقط به‌عنوان سیگنال کمکی استفاده کنید.

چرا نباید JSON را با Regex استخراج کنیم؟

در پروژه‌های اولیه گاهی چنین کدی دیده می‌شود:

import re

match = re.search(r"\{.*\}", model_output, re.DOTALL)

این روش شکننده است، زیرا JSON ممکن است شامل موارد زیر باشد:

  • Objectهای تو‌در‌تو
  • Array
  • آکولاد داخل رشته
  • چند Object جداگانه
  • Markdown
  • Escape Character
  • پاسخ ناقص

Regex ابزار مناسبی برای Parseکردن ساختار کامل JSON نیست.

ترتیب مناسب این است:

  1. از Structured Outputs استفاده کنید.
  2. محتوای پاسخ را با JSON Parser استاندارد Parse کنید.
  3. نتیجه را با Pydantic، Zod یا JSON Schema Validator اعتبارسنجی کنید.
  4. در صورت شکست، خطا را مدیریت یا درخواست کنترل‌شده‌ای را Retry کنید.

اصول طراحی Schema مناسب برای مدل‌های هوش مصنوعی

Schema را تا حد ممکن ساده نگه دارید

Schema بسیار پیچیده احتمال ناسازگاری میان مدل‌ها و ارائه‌دهندگان را افزایش می‌دهد.

به‌جای ساختارهای بیش‌ازحد انتزاعی، از Objectها و Arrayهای واضح استفاده کنید.

نام فیلدها واضح باشد

نام ضعیف:

{
  "v": 1200,
  "s": "p"
}

نام بهتر:

{
  "total_amount": 1200,
  "payment_status": "paid"
}

از description استفاده کنید

{
  "type": "number",
  "description": "مبلغ نهایی به ریال؛ بدون علامت واحد پول و جداکننده هزارگان"
}

گزینه‌های محدود را با enum تعریف کنید

نامناسب:

{
  "priority": {
    "type": "string"
  }
}

مناسب:

{
  "priority": {
    "type": "string",
    "enum": ["low", "medium", "high", "critical"]
  }
}

مقدار ناموجود را مشخص کنید

در Prompt و Schema روشن کنید که مدل برای اطلاعات ناموجود چه کاری انجام دهد:

  • null
  • رشته خالی
  • Array خالی
  • مقدار unknown

در بیشتر موارد:

  • برای مقدار اسکالر ناموجود از null استفاده کنید.
  • برای فهرست بدون عضو از [] استفاده کنید.
  • برای وضعیت دسته‌بندی‌نشده از unknown استفاده کنید.

فیلدهای اضافی را ببندید

{
  "additionalProperties": false
}

واحد اندازه‌گیری را در نام یا توضیح مشخص کنید

مبهم:

{
  "price": 250000
}

شفاف‌تر:

{
  "price_irr": 250000
}

یا:

{
  "price": 250000,
  "currency": "IRR"
}

تاریخ را استاندارد کنید

برای پردازش نرم‌افزاری بهتر است تاریخ میلادی را در قالب ISO 8601 دریافت کنید:

{
  "created_at": "2026-07-11T10:30:00Z"
}

اگر سند دارای تاریخ شمسی است و تبدیل دقیق اهمیت دارد، بهتر است هم مقدار خام و هم مقدار نرمال‌شده را نگه دارید:

{
  "raw_date": "۱۴۰۵/۰۴/۲۰",
  "normalized_date": "2026-07-11"
}

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

Versioning برای Schema

Schema بخشی از قرارداد API داخلی شماست و باید نسخه‌بندی شود.

برای مثال:

{
  "schema_version": "1.0",
  "data": {
    "category": "billing",
    "priority": "high"
  }
}

اگر بعداً فیلدهای جدیدی اضافه شوند، مصرف‌کنندگان قدیمی ممکن است دچار مشکل شوند. راهکارهای مناسب:

  • نگهداری نسخه Schema
  • ثبت Migration
  • تست Consumerها
  • جلوگیری از تغییر ناگهانی نام و نوع فیلدها
  • استفاده از Feature Flag
  • اجرای موازی نسخه جدید پیش از جایگزینی کامل

Schema را مانند یک Interface نرم‌افزاری مدیریت کنید، نه بخشی موقت از Prompt.

تست Structured Outputs

یک نمونه موفق برای Production کافی نیست.

مجموعه تست شما باید ورودی‌های مختلفی داشته باشد:

  • متن کامل و واضح
  • متن ناقص
  • متن بسیار طولانی
  • متن فارسی و انگلیسی ترکیبی
  • اعداد فارسی و لاتین
  • اطلاعات متناقض
  • سند بدون یکی از فیلدهای اصلی
  • درخواست نامرتبط
  • متن دارای Prompt Injection
  • متن دارای کاراکترهای ویژه
  • پاسخ احتمالی Safety Refusal
  • ورودی خالی
  • چند سند در یک پیام

تست قرارداد در Python

from pydantic import ValidationError


def test_output_contract(raw_output: str):
    try:
        result = SupportTicketAnalysis.model_validate_json(
            raw_output
        )
    except ValidationError as error:
        return False, str(error)

    return True, result

معیارهایی که باید ثبت شوند

  • نرخ خروجی معتبر
  • نرخ Refusal
  • نرخ پاسخ ناقص
  • نرخ Retry
  • زمان پاسخ
  • تعداد Token ورودی و خروجی
  • هزینه هر استخراج
  • دقت معنایی روی Dataset ارزیابی
  • خطا به تفکیک مدل
  • خطا به تفکیک نسخه Schema

داشتن JSON معتبر به‌تنهایی معیار موفقیت نیست. برای مثال، ممکن است ۱۰۰٪ پاسخ‌ها Schema معتبر داشته باشند اما دقت استخراج مبلغ فقط ۸۰٪ باشد.

امنیت Structured Outputs

خروجی ساختاریافته همچنان داده غیرقابل‌اعتماد است.

نباید خروجی مدل را مستقیماً در عملیات حساس استفاده کنید:

# ناامن
database.execute(model_output["sql"])

یا:

// ناامن
eval(modelOutput.code);

یا:

# خطرناک
transfer_money(
    account=model_output["account"],
    amount=model_output["amount"],
)

برای استفاده امن:

  • خروجی را Validate کنید.
  • مقادیر را با Allowlist محدود کنید.
  • مجوز کاربر را مستقل از مدل بررسی کنید.
  • Queryهای دیتابیس را Parameterized اجرا کنید.
  • ابزارها را با حداقل دسترسی طراحی کنید.
  • برای عملیات مالی یا غیرقابل‌بازگشت تأیید انسانی بگیرید.
  • Log و Audit Trail نگه دارید.
  • اطلاعات محرمانه غیرضروری را وارد Prompt نکنید.
  • محتوای اسناد را داده غیرقابل‌اعتماد در نظر بگیرید.
  • دستور موجود در سند را از دستور سیستم جدا کنید.

Structured Outputs از تغییر شکل پاسخ جلوگیری می‌کند، اما جلوی Prompt Injection یا تصمیم نادرست مدل را به‌تنهایی نمی‌گیرد.

Structured Outputs در معماری Multi-Provider

هنگامی که از چند مدل یا ارائه‌دهنده استفاده می‌کنید، باید تفاوت قابلیت‌ها را در نظر بگیرید.

برخی مدل‌ها ممکن است:

  • json_schema را پشتیبانی کنند؛
  • فقط json_object داشته باشند؛
  • Structured Outputs را با پارامتر اختصاصی ارائه دهند؛
  • فقط Tool Calling ساختاریافته داشته باشند؛
  • تنها زیرمجموعه‌ای از JSON Schema را بپذیرند؛
  • یا هیچ‌کدام را به‌صورت Native پشتیبانی نکنند.

برای نمونه، رابط‌های OpenAI-compatible معمولاً از response_format استفاده می‌کنند، درحالی‌که API بومی Claude برای JSON Output از ساختار اختصاصی output_config.format و برای ابزارها از حالت Strict استفاده می‌کند. مستندات Structured Outputs در Claude

بنابراین در یک سیستم چندمدلی بهتر است Capability Matrix داشته باشید:

قابلیتمدل Aمدل Bمدل C
JSON Modeبلهبلهبله
JSON Schemaبلهخیربله
Strict Tool Callingبلهبلهخیر
Streaming ساختاریافتهبلهمحدودخیر
Schema تو‌در‌توبلهبلهمحدود

Router باید فقط درخواست Structured Outputs را به مدلی ارسال کند که قابلیت لازم را دارد.

Fallback نیز نباید صرفاً براساس در دسترس‌بودن مدل انجام شود. مدل جایگزین باید از قرارداد خروجی مورد نیاز پشتیبانی کند.

راهبرد جایگزین برای مدل‌های فاقد Structured Outputs

اگر مدل انتخابی json_schema را پشتیبانی نمی‌کند، می‌توان از یک مسیر چندلایه استفاده کرد:

سطح اول: Structured Outputs

بهترین گزینه برای مدل‌های دارای پشتیبانی Native.

سطح دوم: JSON Mode به‌همراه Validation

{
  "response_format": {
    "type": "json_object"
  }
}

سپس خروجی را با Pydantic یا Zod بررسی کنید.

سطح سوم: Prompt دقیق به‌همراه Validation و Retry

در Prompt:

فقط یک JSON معتبر و بدون Markdown برگردان.
تمام فیلدهای مشخص‌شده باید حضور داشته باشند.
هیچ فیلد اضافی تولید نکن.
برای مقدار ناموجود از null استفاده کن.

سپس:

  1. JSON.parse یا json.loads
  2. Validation
  3. در صورت خطا، Retry کنترل‌شده
  4. در صورت شکست مجدد، ارجاع به مسیر انسانی یا مدل جایگزین

سطح چهارم: مدل تعمیرکننده خروجی

می‌توان خروجی نامعتبر را برای تعمیر به یک مدل دیگر فرستاد، اما این راهکار:

  • هزینه را افزایش می‌دهد؛
  • Latency را بیشتر می‌کند؛
  • ممکن است معنای داده را تغییر دهد؛
  • و نباید جایگزین طراحی درست شود.

در صورت استفاده، متن اصلی و خروجی خراب را نگه دارید و نتیجه تعمیرشده را دوباره Validate کنید.

Streaming و خروجی ساختاریافته

Streaming برای پاسخ متنی ساده است، زیرا می‌توان هر Token را بلافاصله نمایش داد. اما JSON تا زمانی که کامل نشده باشد ممکن است قابل Parse نباشد.

مثلاً Chunkهای زیر به‌ترتیب می‌رسند:

{"name":
"محصول نمونه",
"price": 120
000}

هیچ‌یک از Chunkهای میانی به‌تنهایی JSON کامل نیستند.

راهکارهای رایج:

  • Chunkها را Buffer و پس از پایان Parse کنید.
  • از Parser افزایشی مخصوص JSON استفاده کنید.
  • برای عملیات Backend حساس Streaming را غیرفعال کنید.
  • پیشرفت عملیات را جدا از داده نهایی نمایش دهید.
  • فقط پس از دریافت و Validation کامل، داده را در دیتابیس ذخیره کنید.

هرگز یک Object ناقص Streaming را به‌عنوان نتیجه نهایی اجرا نکنید.

تأثیر Schema بر هزینه و سرعت

Schema نیز بخشی از ورودی درخواست است و می‌تواند Token مصرف کند. اگر Schema بسیار بزرگ باشد:

  • تعداد Token ورودی افزایش می‌یابد؛
  • هزینه بیشتر می‌شود؛
  • Latency ممکن است افزایش یابد؛
  • احتمال ناسازگاری بیشتر می‌شود؛
  • نگهداری Schema دشوارتر می‌شود.

برای بهینه‌سازی:

  • فقط فیلدهای لازم را نگه دارید.
  • توضیحات را دقیق اما کوتاه بنویسید.
  • Schemaهای بسیار بزرگ را به چند مرحله تقسیم کنید.
  • از Enumهای ضروری استفاده کنید.
  • داده خام غیرضروری را به مدل ارسال نکنید.
  • نرخ موفقیت و هزینه هر مدل را اندازه‌گیری کنید.
  • برای وظایف ساده از مدل‌های سریع‌تر و ارزان‌تر استفاده کنید.
  • در صورت پشتیبانی ارائه‌دهنده، از Prompt Caching برای بخش‌های تکراری بهره ببرید.

بهینه‌ترین مدل لزوماً قوی‌ترین یا گران‌ترین مدل نیست. مدل مناسب مدلی است که قرارداد داده را با دقت کافی، هزینه قابل‌قبول و Latency مناسب اجرا کند.

الگوی پیشنهادی Production

یک جریان امن برای استفاده از Structured Outputs می‌تواند به این شکل باشد:

ورودی کاربر یا سند
        ↓
پاک‌سازی و محدودکردن ورودی
        ↓
انتخاب مدل دارای قابلیت JSON Schema
        ↓
ارسال Prompt و Schema
        ↓
بررسی وضعیت HTTP و finish_reason
        ↓
تشخیص Refusal یا پاسخ ناقص
        ↓
Parseکردن JSON
        ↓
Validation با Pydantic یا Zod
        ↓
اعتبارسنجی قواعد کسب‌وکار
        ↓
بررسی مجوز و داده‌های مرجع
        ↓
ثبت نتیجه یا درخواست تأیید انسانی
        ↓
ذخیره Log، هزینه، Latency و نسخه Schema

این معماری خروجی مدل را مستقیماً به عملیات حساس متصل نمی‌کند و چند لایه کنترل میان آن‌ها قرار می‌دهد.

بهترین کاربردهای Structured Outputs

Structured Outputs برای وظایفی مناسب است که خروجی آن‌ها باید وارد یک سیستم نرم‌افزاری شود:

  • استخراج اطلاعات فاکتور و رسید
  • استخراج بندهای قرارداد
  • پردازش رزومه
  • تحلیل فرم‌ها
  • دسته‌بندی تیکت پشتیبانی
  • تحلیل احساس مشتری
  • تشخیص Intent
  • تولید Metadata محتوا
  • ساخت اطلاعات محصول
  • استخراج موجودیت‌ها
  • تبدیل متن به رکورد CRM
  • تحلیل نتایج نظرسنجی
  • تولید داده برای نمودار
  • ایجاد تنظیمات رابط کاربری
  • ساخت برنامه چندمرحله‌ای برای Agent
  • تولید ورودی برای Workflow
  • ارزیابی محتوای متنی براساس Rubric
  • تبدیل گزارش به داده قابل‌جست‌وجو

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

اشتباهات رایج

اتکا به جمله «فقط JSON برگردان»

Prompt به‌تنهایی قرارداد فنی محسوب نمی‌شود.

استفاده از JSON Mode به‌جای Schema

JSON معتبر الزاماً ساختار مورد انتظار را ندارد.

حذف Validation سمت سرور

حتی داده Schema-valid باید با قواعد کسب‌وکار بررسی شود.

پیچیده‌کردن بیش‌ازحد Schema

ساختار ساده‌تر معمولاً قابل‌حمل‌تر و قابل‌اعتمادتر است.

اجباری‌کردن مقدار ناموجود

اگر Schema هیچ راهی برای نمایش اطلاعات ناموجود نداشته باشد، مدل ممکن است مجبور به حدس‌زدن شود.

اعتماد به confidence مدل

Confidence متنی مدل معیار آماری تضمین‌شده‌ای نیست.

انجام عملیات حساس بدون تأیید

Structured Outputs مجوز اجرای انتقال مالی، حذف اطلاعات یا ارسال پیام نیست.

فرض پشتیبانی همه مدل‌ها

قابلیت Structured Outputs باید برای مدل و endpoint انتخابی بررسی شود.

یکی‌دانستن ساختار صحیح با حقیقت

یک پاسخ می‌تواند Schema-valid اما از نظر محتوایی اشتباه باشد.

تغییر Schema بدون Versioning

مصرف‌کنندگان قدیمی ممکن است با تغییر ناگهانی قرارداد شکسته شوند.

چک‌لیست استفاده در محیط Production

پیش از انتشار، این موارد را بررسی کنید:

  • مدل انتخابی از JSON Schema پشتیبانی می‌کند.
  • Schema ساده و دارای نام‌گذاری واضح است.
  • تمام فیلدهای ضروری در required قرار دارند.
  • فیلدهای اختیاری امکان null دارند.
  • additionalProperties در محل مناسب برابر false است.
  • گزینه‌های محدود با enum تعریف شده‌اند.
  • واحد پول، زمان و اندازه مشخص شده است.
  • Prompt مدل را از حدس‌زدن منع می‌کند.
  • خروجی با Pydantic یا Zod اعتبارسنجی می‌شود.
  • قواعد کسب‌وکار جداگانه بررسی می‌شوند.
  • Refusal و پاسخ ناقص مدیریت می‌شوند.
  • Retry فقط برای خطاهای موقت انجام می‌شود.
  • Timeout تعریف شده است.
  • عملیات حساس تأیید مستقل دارند.
  • ورودی‌های Prompt Injection آزمایش شده‌اند.
  • نسخه Schema ثبت می‌شود.
  • هزینه، Latency و نرخ خطا مانیتور می‌شوند.
  • Fallback فقط به مدل سازگار انجام می‌شود.
  • Dataset ارزیابی واقعی وجود دارد.

پرسش‌های متداول

Structured Outputs چیست؟

Structured Outputs قابلیتی است که به توسعه‌دهنده امکان می‌دهد ساختار پاسخ مدل هوش مصنوعی را با یک Schema تعریف کند. هدف این است که پاسخ به‌جای متن آزاد، داده‌ای قابل‌پردازش و منطبق با قرارداد نرم‌افزار باشد.

تفاوت Structured Outputs و JSON Mode چیست؟

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

آیا Structured Outputs از Hallucination جلوگیری می‌کند؟

خیر. این قابلیت ساختار پاسخ را کنترل می‌کند، نه حقیقت محتوای آن را. مدل ممکن است داده‌ای از نظر ساختاری معتبر اما از نظر معنایی اشتباه تولید کند.

آیا بعد از Structured Outputs به Pydantic یا Zod نیاز داریم؟

بله. Validation سمت برنامه یک لایه دفاعی ضروری است. علاوه بر ساختار، باید قواعد کسب‌وکار، مجوزها و ارتباط داده با سیستم واقعی نیز بررسی شوند.

Structured Outputs چه تفاوتی با Tool Calling دارد؟

Structured Outputs برای دریافت پاسخ ساختاریافته است. Tool Calling زمانی استفاده می‌شود که مدل باید یک تابع یا ابزار را با آرگومان‌های مشخص فراخوانی کند.

آیا همه مدل‌ها از json_schema پشتیبانی می‌کنند؟

خیر. پشتیبانی به مدل، ارائه‌دهنده و endpoint بستگی دارد. برخی مدل‌ها فقط JSON Mode یا Tool Calling را ارائه می‌کنند.

برای اطلاعات ناموجود چه کاری انجام دهیم؟

بهتر است فیلد همیشه در پاسخ وجود داشته باشد اما نوع آن امکان null داشته باشد:

{
  "type": ["string", "null"]
}

در Prompt نیز صریحاً از مدل بخواهید اطلاعات ناموجود را حدس نزند.

آیا می‌توان خروجی را مستقیماً وارد دیتابیس کرد؟

خیر. ابتدا باید JSON Parse، سپس با Schema اعتبارسنجی و بعد با قواعد کسب‌وکار بررسی شود. Queryهای دیتابیس نیز باید Parameterized باشند.

آیا Structured Outputs برای زبان فارسی کار می‌کند؟

بله. ساختار JSON مستقل از زبان محتوای فیلدهاست. نام فیلدها می‌توانند انگلیسی و مقادیر فارسی باشند. برای سازگاری بهتر نرم‌افزاری، معمولاً نام کلیدها به انگلیسی و محتوای آن‌ها به زبان مورد نیاز نگهداری می‌شود.

آیا می‌توان از Structured Outputs همراه Streaming استفاده کرد؟

در صورت پشتیبانی مدل و API بله، اما Chunkهای میانی معمولاً JSON کامل نیستند. باید آن‌ها را Buffer کنید و فقط پس از پایان پاسخ، JSON نهایی را Parse و Validate کنید.

جمع‌بندی

Structured Outputs یکی از مهم‌ترین قابلیت‌ها برای تبدیل مدل‌های زبانی از ابزار تولید متن به اجزای قابل‌اعتمادتر یک سیستم نرم‌افزاری است.

با این قابلیت می‌توان ساختار پاسخ را از قبل مشخص کرد و خروجی‌هایی مناسب برای دیتابیس، CRM، پردازش اسناد، سیستم پشتیبانی، Workflowها و Agentهای هوش مصنوعی دریافت کرد.

بااین‌حال، Structured Outputs به‌تنهایی تمام مشکلات را حل نمی‌کند. یک پیاده‌سازی Production همچنان به این موارد نیاز دارد:

  • Schema ساده و دقیق
  • Prompt مناسب
  • مدل سازگار
  • Validation سمت سرور
  • کنترل قواعد کسب‌وکار
  • مدیریت Refusal و پاسخ ناقص
  • Retry کنترل‌شده
  • ملاحظات امنیتی
  • مانیتورینگ هزینه و خطا
  • ارزیابی صحت معنایی

مهم‌ترین اصل این است:

Structured Outputs شکل پاسخ را قابل‌اعتمادتر می‌کند؛ صحت محتوا و ایمنی عملیات همچنان مسئولیت برنامه شماست.

استفاده از Structured Outputs با API درواره

درواره دسترسی به مدل‌های مختلف هوش مصنوعی را از طریق یک API سازگار با OpenAI فراهم می‌کند. برای اتصال بسیاری از SDKها و ابزارهای OpenAI-compatible کافی است آدرس پایه را روی مقدار زیر قرار دهید:

https://api.darvareh.ir/v1

سپس می‌توانید براساس قابلیت مدل انتخابی از JSON Mode، Structured Outputs یا Tool Calling برای ساخت اپلیکیشن‌های هوش مصنوعی استفاده کنید.

پیش از استقرار نهایی، پشتیبانی مدل موردنظر از response_format، json_schema و حالت strict را بررسی کرده و خروجی را روی داده‌های واقعی پروژه خود آزمایش کنید.

مقالات مرتبط

Read more