Instructor چیست؟ آموزش Structured Output با Python و API درواره

Instructor کتابخانه‌ای سبک برای تبدیل پاسخ مدل‌های زبانی به اشیای معتبر Pydantic است. در این آموزش یک سیستم استخراج اطلاعات با Python، FastAPI و API درواره می‌سازیم.

Share
Instructor چیست؟ آموزش Structured Output با Python و API درواره


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

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

  • موضوع درخواست
  • اولویت
  • احساس کاربر
  • خلاصه
  • واحد مسئول
  • نیاز یا عدم نیاز به بررسی انسانی

درخواست از مدل برای تولید JSON به‌تنهایی کافی نیست. ممکن است مدل:

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

Instructor کتابخانه‌ای برای دریافت خروجی‌های ساختاریافته و Type-safe از مدل‌های زبانی است. این کتابخانه با استفاده از Pydantic، ساختار خروجی را تعریف و پاسخ مدل را اعتبارسنجی می‌کند.

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

  • Instructor را نصب و پیکربندی می‌کند.
  • به API سازگار درواره متصل می‌شود.
  • متن فارسی را به داده ساختاریافته تبدیل می‌کند.
  • خروجی را با Pydantic اعتبارسنجی می‌کند.
  • در صورت خروجی نامعتبر Retry انجام می‌دهد.
  • فهرست‌ها و مدل‌های تو‌در‌تو را استخراج می‌کند.
  • Validation اختصاصی اجرا می‌کند.
  • از اجرای Async پشتیبانی می‌کند.
  • از طریق FastAPI به یک API قابل‌استفاده تبدیل می‌شود.
  • برای تست، مدیریت خطا و محیط Production آماده می‌شود.

Instructor چیست؟

Instructor یک کتابخانه سبک برای دریافت خروجی ساختاریافته از مدل‌های زبانی است. توسعه‌دهنده نوع پاسخ موردانتظار را با یک مدل Pydantic تعریف می‌کند و Instructor فرایند تبدیل Schema، دریافت پاسخ، Parse کردن، Validation و Retry را مدیریت می‌کند.

بر اساس مستندات رسمی Instructor، این کتابخانه قابلیت‌هایی مانند موارد زیر را ارائه می‌دهد:

  • خروجی Type-safe
  • اعتبارسنجی با Pydantic
  • Retry خودکار
  • مدل‌های تو‌در‌تو
  • استخراج فهرست
  • Streaming
  • اجرای Async
  • پشتیبانی از Providerهای مختلف
  • Validation مبتنی بر Context

فرایند کلی Instructor به این شکل است:

متن ورودی
↓
تعریف مدل Pydantic
↓
ارسال درخواست به مدل هوش مصنوعی
↓
دریافت خروجی ساختاریافته
↓
اعتبارسنجی با Pydantic
↓
Retry در صورت نامعتبر بودن
↓
شیء معتبر پایتون

نتیجه نهایی فقط یک رشته JSON نیست، بلکه نمونه‌ای از کلاس Pydantic است و می‌توان مستقیماً از فیلدهای آن در منطق برنامه استفاده کرد.

چرا از Instructor استفاده کنیم؟

می‌توان مستقیماً از مدل خواست که JSON تولید کند:

پاسخ را فقط به‌شکل JSON برگردان.

این روش برای یک نمونه آزمایشی ساده قابل‌استفاده است، اما در پروژه واقعی با مشکلاتی مواجه می‌شود:

  • Schema به‌شکل دقیق تعریف نشده است.
  • Parse کردن پاسخ را باید خودتان انجام دهید.
  • Validation نوع داده‌ها دستی است.
  • مدیریت فیلدهای حذف‌شده بر عهده برنامه است.
  • Retry خروجی نامعتبر باید جداگانه نوشته شود.
  • پیام خطای Validation باید دوباره به مدل ارسال شود.
  • مدل‌های تو‌در‌تو کد بیشتری نیاز دارند.
  • نگهداری Schemaهای متعدد دشوار می‌شود.

Instructor این مراحل را در یک رابط نزدیک به OpenAI Python SDK قرار می‌دهد.

نمونه ساده:

person = client.chat.completions.create(
    model=model_id,
    response_model=Person,
    messages=[
        {
            "role": "user",
            "content": "امیر ۴۹ سال دارد و در تهران زندگی می‌کند.",
        }
    ],
)

اکنون person یک نمونه معتبر از کلاس Person است:

print(person.name)
print(person.age)
print(person.city)

Instructor چه تفاوتی با Pydantic دارد؟

Pydantic یک کتابخانه عمومی برای مدل‌سازی و اعتبارسنجی داده در پایتون است. Instructor از Pydantic برای تعریف خروجی موردانتظار مدل هوش مصنوعی استفاده می‌کند.

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

from pydantic import BaseModel


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

Instructor همین مدل را به فرایند فراخوانی LLM متصل می‌کند:

person = client.chat.completions.create(
    model=model_id,
    response_model=Person,
    messages=[
        {
            "role": "user",
            "content": "نام و سن فرد را استخراج کن.",
        }
    ],
)

به‌طور خلاصه:

  • Pydantic ساختار و اعتبار داده را مدیریت می‌کند.
  • Instructor مدل زبانی را وادار می‌کند داده‌ای سازگار با آن ساختار تولید کند.
  • API درواره دسترسی به مدل هوش مصنوعی را فراهم می‌کند.

تفاوت Instructor با PydanticAI

Instructor و PydanticAI هر دو از Pydantic استفاده می‌کنند، اما هدف یکسانی ندارند.

ویژگیInstructorPydanticAI
هدف اصلیدریافت خروجی ساختاریافتهساخت Agent و Workflow هوش مصنوعی
پیچیدگیسبک‌ترگسترده‌تر
Tool Callingهدف اصلی نیستقابلیت اصلی
Agent Loopندارددارد
Dependency Injectionمحدود به Validation Contextپشتیبانی کامل برای Agent
Structured Outputقابلیت اصلییکی از قابلیت‌ها
کاربرد مناسباستخراج و طبقه‌بندی دادهAgentهای چندمرحله‌ای

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

اگر پروژه به Tool Calling، Agent Loop، Dependency Injection و Workflow چندمرحله‌ای نیاز دارد، PydanticAI انتخاب کامل‌تری خواهد بود.

Instructor چه تفاوتی با فراخوانی مستقیم API دارد؟

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

response = openai_client.chat.completions.create(
    model=model_id,
    messages=messages,
)

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

پس از آن باید:

  • JSON را استخراج کنید.
  • آن را با json.loads تبدیل کنید.
  • خطای Parse را مدیریت کنید.
  • داده را به مدل Pydantic بدهید.
  • خطای Validation را مدیریت کنید.
  • در صورت نیاز درخواست را تکرار کنید.

در Instructor بخش بزرگی از این فرایند با response_model انجام می‌شود:

result = instructor_client.chat.completions.create(
    model=model_id,
    response_model=DesiredOutput,
    messages=messages,
)

فراخوانی مستقیم برای پاسخ متنی ساده مناسب است. Instructor زمانی ارزشمند می‌شود که ساختار خروجی بخشی از قرارداد برنامه باشد.

پیش‌نیازهای آموزش

برای اجرای مثال‌ها به موارد زیر نیاز دارید:

  • Python نسخه ۳.۱۰ یا جدیدتر
  • محیط مجازی پایتون
  • کلید API درواره
  • شناسه یک مدل سازگار
  • آشنایی مقدماتی با Python
  • آشنایی مقدماتی با Pydantic

برای مشاهده شناسه، قابلیت و قیمت جاری مدل‌ها، صفحه مدل‌های درواره را بررسی کنید.

قابلیت مدل‌ها در تولید JSON، Tool Calling و رعایت Schema یکسان نیست. پیش از استفاده در Production، مدل انتخاب‌شده را روی داده‌های واقعی پروژه آزمایش کنید.

ساخت پروژه

یک پوشه جدید ایجاد کنید:

mkdir instructor-darvareh
cd instructor-darvareh

محیط مجازی را بسازید:

python -m venv .venv

فعال‌سازی در Linux و macOS:

source .venv/bin/activate

فعال‌سازی در Windows PowerShell:

.venv\Scripts\Activate.ps1

نصب Instructor

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

pip install instructor openai pydantic python-dotenv

برای بخش FastAPI:

pip install fastapi "uvicorn[standard]"

نمونه فایل requirements.txt:

instructor
openai
pydantic
python-dotenv
fastapi
uvicorn[standard]

در محیط Production بهتر است نسخه وابستگی‌ها را پس از آزمایش Pin کنید.

ساخت فایل تنظیمات محیطی

در ریشه پروژه یک فایل .env بسازید:

DARVAREH_API_KEY=YOUR_DARVAREH_API_KEY
DARVAREH_MODEL_ID=YOUR_MODEL_ID

فایل .gitignore:

.venv/
.env
__pycache__/
*.pyc
.pytest_cache/

آدرس پایه API درواره ثابت است:

https://api.darvareh.ir/v1

اتصال Instructor به API درواره

از آنجا که API درواره با ساختار OpenAI-compatible قابل‌استفاده است، ابتدا یک نمونه از OpenAI Client با base_url درواره ایجاد می‌کنیم و سپس آن را با Instructor ترکیب می‌کنیم.

فایل client.py:

import os

import instructor
from dotenv import load_dotenv
from openai import OpenAI


load_dotenv()

api_key = os.getenv("DARVAREH_API_KEY")
model_id = os.getenv("DARVAREH_MODEL_ID")

if not api_key:
    raise RuntimeError(
        "DARVAREH_API_KEY is not configured"
    )

if not model_id:
    raise RuntimeError(
        "DARVAREH_MODEL_ID is not configured"
    )

openai_client = OpenAI(
    api_key=api_key,
    base_url="https://api.darvareh.ir/v1",
)

instructor_client = instructor.from_openai(
    openai_client,
    mode=instructor.Mode.JSON,
)

در این کد:

  • کلید API از متغیر محیطی خوانده می‌شود.
  • شناسه مدل خارج از کد نگهداری می‌شود.
  • درخواست‌ها به API درواره ارسال می‌شوند.
  • Instructor پاسخ را مطابق مدل Pydantic دریافت می‌کند.
  • حالت JSON برای سازگاری عمومی‌تر انتخاب شده است.

بسته به مدل انتخاب‌شده، ممکن است حالت‌های دیگر Instructor نیز قابل‌استفاده باشند. Mode را بدون آزمایش روی مدل و مسیر API تغییر ندهید.

ساخت اولین استخراج‌کننده اطلاعات

فایل extract_person.py را ایجاد کنید:

from pydantic import BaseModel, Field

from client import instructor_client, model_id


class Person(BaseModel):
    name: str = Field(
        description="نام شخص"
    )

    age: int | None = Field(
        default=None,
        ge=0,
        le=120,
        description="سن شخص در صورت وجود",
    )

    city: str | None = Field(
        default=None,
        description="شهر محل سکونت در صورت وجود",
    )


person = instructor_client.chat.completions.create(
    model=model_id,
    response_model=Person,
    messages=[
        {
            "role": "system",
            "content": (
                "اطلاعات را فقط از متن کاربر استخراج کن. "
                "اگر یک مقدار در متن وجود ندارد، آن را null قرار بده."
            ),
        },
        {
            "role": "user",
            "content": (
                "امیر ۴۹ سال دارد و در تهران زندگی می‌کند."
            ),
        },
    ],
)

print(person)
print(person.model_dump())

خروجی به‌شکل یک شیء Pydantic در دسترس است:

print(person.name)
print(person.age)
print(person.city)

دیگر نیازی به اجرای دستی json.loads یا بررسی وجود فیلدها ندارید.

طراحی درست Response Model

کیفیت خروجی Instructor فقط به مدل زبانی وابسته نیست. طراحی مدل Pydantic نیز تأثیر زیادی دارد.

یک Response Model مناسب باید:

  • نام فیلدهای روشن داشته باشد.
  • برای هر فیلد Description مشخص ارائه دهد.
  • مقادیر محدود را با Literal یا Enum تعریف کند.
  • فیلدهای اختیاری را واقعاً Optional کند.
  • محدودیت طول و بازه داشته باشد.
  • ساختار بیش از حد پیچیده نداشته باشد.
  • با اطلاعات موجود در ورودی سازگار باشد.

نمونه نامناسب:

class Result(BaseModel):
    value: str
    data: str
    status: str

این فیلدها مبهم هستند و مدل نمی‌داند چه چیزی باید داخل آن‌ها قرار گیرد.

نمونه بهتر:

from typing import Literal

from pydantic import BaseModel, Field


class SupportTicket(BaseModel):
    category: Literal[
        "technical",
        "billing",
        "account",
        "feature_request",
        "other",
    ]

    priority: Literal[
        "low",
        "medium",
        "high",
    ]

    summary: str = Field(
        min_length=5,
        max_length=300,
        description="خلاصه کوتاه درخواست به زبان فارسی",
    )

    requires_human_review: bool = Field(
        description=(
            "اگر اطلاعات ناکافی یا موضوع حساس است، true باشد"
        )
    )

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

فایل ticket_analyzer.py:

from typing import Literal

from pydantic import BaseModel, Field

from client import instructor_client, model_id


class TicketAnalysis(BaseModel):
    category: Literal[
        "technical",
        "billing",
        "account",
        "feature_request",
        "general",
    ]

    priority: Literal[
        "low",
        "medium",
        "high",
    ]

    sentiment: Literal[
        "positive",
        "neutral",
        "negative",
    ]

    summary: str = Field(
        min_length=5,
        max_length=300,
    )

    suggested_team: Literal[
        "technical_support",
        "finance",
        "customer_success",
        "product",
    ]

    requires_human_review: bool

    confidence: float = Field(
        ge=0,
        le=1,
    )


def analyze_ticket(message: str) -> TicketAnalysis:
    return instructor_client.chat.completions.create(
        model=model_id,
        response_model=TicketAnalysis,
        max_retries=2,
        messages=[
            {
                "role": "system",
                "content": (
                    "پیام پشتیبانی را فقط براساس اطلاعات موجود "
                    "تحلیل کن. در صورت ابهام، confidence را کاهش "
                    "بده و requires_human_review را true قرار بده."
                ),
            },
            {
                "role": "user",
                "content": message,
            },
        ],
    )


analysis = analyze_ticket(
    "سلام، از دیشب تمام درخواست‌های API من با Timeout مواجه می‌شوند."
)

print(analysis.model_dump())

اکنون منطق برنامه می‌تواند مستقیماً از نتیجه استفاده کند:

if analysis.priority == "high":
    send_to_priority_queue(analysis)

if analysis.requires_human_review:
    assign_to_human_agent(analysis)

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

استخراج فهرست با Instructor

فرض کنید یک متن شامل چند محصول است و می‌خواهیم همه آن‌ها را استخراج کنیم.

from pydantic import BaseModel, Field

from client import instructor_client, model_id


class Product(BaseModel):
    name: str
    price: int | None = Field(
        default=None,
        ge=0,
    )
    available: bool | None = None


class ProductList(BaseModel):
    products: list[Product]


text = """
کیبورد مکانیکی با قیمت ۴ میلیون و ۵۰۰ هزار تومان موجود است.
ماوس بی‌سیم با قیمت ۱ میلیون و ۸۰۰ هزار تومان موجود است.
پایه لپ‌تاپ فعلاً موجود نیست و قیمت آن در متن ذکر نشده است.
"""

result = instructor_client.chat.completions.create(
    model=model_id,
    response_model=ProductList,
    max_retries=2,
    messages=[
        {
            "role": "system",
            "content": (
                "فقط اطلاعات صریح متن را استخراج کن. "
                "مقادیر ناموجود را حدس نزن."
            ),
        },
        {
            "role": "user",
            "content": text,
        },
    ],
)

for product in result.products:
    print(product.model_dump())

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

مدل‌های تو‌در‌تو

برای استخراج داده پیچیده می‌توان از مدل‌های Nested استفاده کرد.

from pydantic import BaseModel, Field


class ContactInfo(BaseModel):
    email: str | None = None
    phone: str | None = None


class Address(BaseModel):
    city: str | None = None
    street: str | None = None
    postal_code: str | None = None


class Customer(BaseModel):
    full_name: str
    contact: ContactInfo
    address: Address
    interests: list[str] = Field(
        default_factory=list
    )

فراخوانی:

customer = instructor_client.chat.completions.create(
    model=model_id,
    response_model=Customer,
    messages=[
        {
            "role": "system",
            "content": (
                "اطلاعات مشتری را استخراج کن. "
                "هیچ مقدار ناموجودی را حدس نزن."
            ),
        },
        {
            "role": "user",
            "content": (
                "علی رضایی از شیراز است. ایمیل او "
                "ali@example.com است و به برنامه‌نویسی "
                "و هوش مصنوعی علاقه دارد."
            ),
        },
    ],
)

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

استفاده از Enum

برای مقادیر محدود می‌توان از Enum استفاده کرد:

from enum import Enum

from pydantic import BaseModel


class Priority(str, Enum):
    LOW = "low"
    MEDIUM = "medium"
    HIGH = "high"


class Ticket(BaseModel):
    title: str
    priority: Priority

Enum از تولید مقادیری مانند urgent, critical یا normal خارج از قرارداد برنامه جلوگیری می‌کند.

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

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

from pydantic import BaseModel, Field, field_validator


class ArticleSummary(BaseModel):
    title: str = Field(
        min_length=5,
        max_length=100,
    )

    summary: str = Field(
        min_length=30,
        max_length=500,
    )

    keywords: list[str] = Field(
        min_length=3,
        max_length=8,
    )

    @field_validator("keywords")
    @classmethod
    def normalize_keywords(
        cls,
        values: list[str],
    ) -> list[str]:
        cleaned = []

        for value in values:
            keyword = value.strip()

            if keyword and keyword not in cleaned:
                cleaned.append(keyword)

        if len(cleaned) < 3:
            raise ValueError(
                "at least three unique keywords are required"
            )

        return cleaned

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

جزئیات این قابلیت در مستندات رسمی Validation در Instructor ارائه شده است.

Retry خودکار چگونه کار می‌کند؟

ممکن است مدل در اولین تلاش خروجی نامعتبر تولید کند. برای مثال:

  • مقدار Enum اشتباه باشد.
  • فیلد ضروری حذف شود.
  • متن بیش از حد طولانی باشد.
  • عدد خارج از بازه باشد.
  • Validator اختصاصی خطا بدهد.

Instructor می‌تواند اطلاعات خطای Validation را در اختیار مدل قرار دهد و از آن بخواهد پاسخ را اصلاح کند.

result = instructor_client.chat.completions.create(
    model=model_id,
    response_model=TicketAnalysis,
    max_retries=2,
    messages=[
        {
            "role": "user",
            "content": user_message,
        }
    ],
)

Retry را محدود نگه دارید. مقدار زیاد می‌تواند:

  • هزینه را افزایش دهد.
  • Latency را بالا ببرد.
  • خطای دائمی را پنهان کند.
  • تعداد درخواست‌ها را غیرقابل‌پیش‌بینی کند.

اگر مدل پس از یک یا دو Retry همچنان Schema را رعایت نمی‌کند، بهتر است:

  • Schema را ساده‌تر کنید.
  • Description فیلدها را بهبود دهید.
  • Prompt را روشن‌تر کنید.
  • مدل مناسب‌تری انتخاب کنید.
  • نمونه را برای بررسی انسانی ارسال کنید.

راهنمای رسمی Retry در Instructor جزئیات بیشتری درباره این فرایند ارائه می‌دهد.

تفاوت Validation ساختاری و معنایی

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

  • فیلد وجود دارد.
  • مقدار یک عدد است.
  • عدد در بازه مشخص قرار دارد.
  • رشته طول قابل‌قبولی دارد.
  • مقدار عضو Enum است.
  • ساختار Nested معتبر است.

اما Pydantic به‌تنهایی نمی‌تواند تضمین کند:

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

بنابراین دو سطح Validation نیاز داریم:

Validation ساختاری

با Pydantic، Schema، نوع داده و Validatorها انجام می‌شود.

Validation محتوایی

با روش‌هایی مانند موارد زیر انجام می‌شود:

  • مقایسه با منبع
  • بررسی در پایگاه داده
  • قواعد کسب‌وکار
  • اجرای کد
  • Evals
  • بازبینی انسانی
  • مدل داور مستقل

Structured Output شکل داده را کنترل می‌کند، نه حقیقت آن را.

Validation مبتنی بر Context

گاهی اعتبار یک مقدار به اطلاعات زمان اجرا وابسته است. برای مثال، دسته محصول باید عضو فهرستی باشد که از پایگاه داده دریافت شده است.

from pydantic import (
    BaseModel,
    ValidationInfo,
    field_validator,
)


class ClassifiedProduct(BaseModel):
    product_name: str
    category: str

    @field_validator("category")
    @classmethod
    def category_must_be_allowed(
        cls,
        value: str,
        info: ValidationInfo,
    ) -> str:
        allowed_categories = info.context.get(
            "allowed_categories",
            [],
        )

        if value not in allowed_categories:
            raise ValueError(
                f"category must be one of {allowed_categories}"
            )

        return value

فراخوانی:

result = instructor_client.chat.completions.create(
    model=model_id,
    response_model=ClassifiedProduct,
    context={
        "allowed_categories": [
            "computer",
            "mobile",
            "accessories",
        ]
    },
    max_retries=2,
    messages=[
        {
            "role": "user",
            "content": (
                "کیبورد مکانیکی را در دسته مناسب قرار بده."
            ),
        }
    ],
)

این قابلیت برای موارد زیر مناسب است:

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

مستندات Reask و Context Validation این قابلیت را توضیح می‌دهد.

مدیریت مقدار ناموجود

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

مدل نامناسب:

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

اگر متن فقط نام را داشته باشد، مدل ممکن است سن و شهر را حدس بزند.

مدل بهتر:

class Person(BaseModel):
    name: str
    age: int | None = None
    city: str | None = None

در Prompt نیز صریحاً بنویسید:

اگر اطلاعات یک فیلد در متن وجود ندارد، مقدار آن را null قرار بده.
هیچ اطلاعاتی را حدس نزن.

Optional بودن فیلد باید با واقعیت داده‌های پروژه هماهنگ باشد.

افزودن شواهد به خروجی

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

from pydantic import BaseModel


class ExtractedValue(BaseModel):
    value: str | None
    evidence: str | None

نمونه کامل‌تر:

class InvoiceData(BaseModel):
    invoice_number: ExtractedValue
    customer_name: ExtractedValue
    total_amount: ExtractedValue
    payment_status: ExtractedValue

اکنون برنامه می‌تواند بررسی کند که Evidence واقعاً در متن ورودی وجود دارد. این روش جایگزین Validation مستقل نیست، اما بررسی خروجی را ساده‌تر می‌کند.

دریافت متادیتای پاسخ

گاهی علاوه بر شیء Parseشده، به اطلاعات پاسخ خام یا Usage نیاز دارید. Instructor برای این هدف قابلیت create_with_completion ارائه می‌دهد.

result, completion = (
    instructor_client.chat.completions.create_with_completion(
        model=model_id,
        response_model=TicketAnalysis,
        messages=[
            {
                "role": "user",
                "content": user_message,
            }
        ],
    )
)

print(result.model_dump())
print(completion)

ساختار دقیق Usage به مدل و Provider وابسته است. پیش از اتکا به فیلدهای متادیتا، خروجی مسیر انتخاب‌شده را بررسی کنید.

اجرای Async

در FastAPI یا برنامه‌هایی با درخواست‌های هم‌زمان، از Async Client استفاده کنید.

فایل async_client.py:

import os

import instructor
from dotenv import load_dotenv
from openai import AsyncOpenAI


load_dotenv()

api_key = os.getenv("DARVAREH_API_KEY")

if not api_key:
    raise RuntimeError(
        "DARVAREH_API_KEY is not configured"
    )

async_openai_client = AsyncOpenAI(
    api_key=api_key,
    base_url="https://api.darvareh.ir/v1",
)

async_instructor_client = instructor.from_openai(
    async_openai_client,
    mode=instructor.Mode.JSON,
)

استفاده:

from async_client import async_instructor_client
from client import model_id


async def analyze_message(
    message: str,
) -> TicketAnalysis:
    return await (
        async_instructor_client.chat.completions.create(
            model=model_id,
            response_model=TicketAnalysis,
            max_retries=2,
            messages=[
                {
                    "role": "user",
                    "content": message,
                }
            ],
        )
    )

در Endpointهای Async از Client هم‌زمان استفاده نکنید؛ زیرا می‌تواند Event Loop را مسدود کند.

پردازش چند ورودی به‌صورت هم‌زمان

برای تحلیل چند متن می‌توان از asyncio.gather همراه با محدودیت هم‌زمانی استفاده کرد.

import asyncio


semaphore = asyncio.Semaphore(5)


async def analyze_with_limit(
    message: str,
) -> TicketAnalysis:
    async with semaphore:
        return await analyze_message(message)


async def analyze_many(
    messages: list[str],
) -> list[TicketAnalysis]:
    tasks = [
        analyze_with_limit(message)
        for message in messages
    ]

    return await asyncio.gather(*tasks)

محدودیت هم‌زمانی از ارسال ناگهانی تعداد زیادی درخواست جلوگیری می‌کند. مقدار مناسب را براساس محدودیت API، Latency، هزینه و ظرفیت برنامه انتخاب کنید.

ساخت API با FastAPI

ساختار پروژه:

instructor-darvareh/
├── .env
├── .gitignore
├── client.py
├── async_client.py
├── schemas.py
├── services.py
├── main.py
└── requirements.txt

فایل schemas.py:

from typing import Literal

from pydantic import BaseModel, Field


class AnalyzeRequest(BaseModel):
    message: str = Field(
        min_length=5,
        max_length=5000,
    )


class TicketAnalysis(BaseModel):
    category: Literal[
        "technical",
        "billing",
        "account",
        "feature_request",
        "general",
    ]

    priority: Literal[
        "low",
        "medium",
        "high",
    ]

    summary: str = Field(
        min_length=5,
        max_length=300,
    )

    requires_human_review: bool

    confidence: float = Field(
        ge=0,
        le=1,
    )


class AnalyzeResponse(BaseModel):
    data: TicketAnalysis

فایل services.py:

from async_client import async_instructor_client
from client import model_id
from schemas import TicketAnalysis


async def analyze_ticket(
    message: str,
) -> TicketAnalysis:
    return await (
        async_instructor_client.chat.completions.create(
            model=model_id,
            response_model=TicketAnalysis,
            max_retries=2,
            messages=[
                {
                    "role": "system",
                    "content": (
                        "پیام پشتیبانی را فقط براساس متن "
                        "کاربر تحلیل کن. اطلاعاتی را که در "
                        "متن وجود ندارد حدس نزن."
                    ),
                },
                {
                    "role": "user",
                    "content": message,
                },
            ],
        )
    )

فایل main.py:

from fastapi import FastAPI, HTTPException
from openai import (
    APIConnectionError,
    APITimeoutError,
    AuthenticationError,
    RateLimitError,
)

from schemas import (
    AnalyzeRequest,
    AnalyzeResponse,
)
from services import analyze_ticket


app = FastAPI(
    title="Darvareh Instructor API",
    version="1.0.0",
)


@app.get("/health")
async def health() -> dict[str, str]:
    return {"status": "ok"}


@app.post(
    "/analyze-ticket",
    response_model=AnalyzeResponse,
)
async def analyze_ticket_endpoint(
    body: AnalyzeRequest,
) -> AnalyzeResponse:
    try:
        result = await analyze_ticket(body.message)

        return AnalyzeResponse(data=result)

    except AuthenticationError as exc:
        raise HTTPException(
            status_code=500,
            detail="Model service is not configured",
        ) from exc

    except RateLimitError as exc:
        raise HTTPException(
            status_code=429,
            detail="Request rate limit reached",
        ) from exc

    except (APITimeoutError, APIConnectionError) as exc:
        raise HTTPException(
            status_code=503,
            detail="Model service is temporarily unavailable",
        ) from exc

    except Exception as exc:
        raise HTTPException(
            status_code=502,
            detail="Unable to generate a valid response",
        ) from exc

اجرای برنامه:

uvicorn main:app --reload

مستندات Swagger:

http://127.0.0.1:8000/docs

نمونه درخواست:

curl -X POST "http://127.0.0.1:8000/analyze-ticket" \
  -H "Content-Type: application/json" \
  -d '{"message":"شارژ کیف پول من انجام شده اما موجودی تغییر نکرده است."}'

نمونه پاسخ:

{
  "data": {
    "category": "billing",
    "priority": "high",
    "summary": "شارژ انجام شده اما موجودی کیف پول تغییر نکرده است.",
    "requires_human_review": true,
    "confidence": 0.96
  }
}

مدیریت خطاها

فراخوانی Instructor ممکن است در چند سطح شکست بخورد:

خطای تنظیمات

  • کلید API وجود ندارد.
  • شناسه مدل تنظیم نشده است.
  • آدرس Base URL اشتباه است.

این خطاها در زمان Startup بررسی شوند.

خطای Provider

  • Rate Limit
  • Timeout
  • اختلال شبکه
  • خطای موقت سرویس
  • مدل ناموجود
  • دسترسی نامعتبر

خطای Structured Output

  • مدل JSON معتبر تولید نمی‌کند.
  • Schema را رعایت نمی‌کند.
  • Validation شکست می‌خورد.
  • تعداد Retry به پایان می‌رسد.

خطای منطق برنامه

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

پیام خام Provider، Prompt داخلی یا جزئیات زیرساخت را مستقیماً به کاربر نمایش ندهید. جزئیات لازم را در Log داخلی ثبت و یک پیام عمومی مناسب برگردانید.

Retry را در چند لایه تکرار نکنید

ممکن است Retry در این بخش‌ها وجود داشته باشد:

  • OpenAI Client
  • Instructor Validation
  • Service Layer
  • Queue Worker
  • API Gateway

اگر هر لایه چند بار Retry کند، تعداد واقعی فراخوانی‌ها می‌تواند به‌شکل غیرمنتظره افزایش یابد.

برای طراحی مناسب:

  • Retry خروجی نامعتبر را به Instructor بسپارید.
  • Retry خطاهای موقت شبکه را در یک لایه مشخص انجام دهید.
  • برای هر درخواست Timeout کلی تعیین کنید.
  • تعداد تلاش‌ها را ثبت کنید.
  • برای خطاهای دائمی Retry انجام ندهید.
  • هزینه تمام تلاش‌ها را محاسبه کنید.

انتخاب مدل مناسب در درواره

برای Instructor، فقط کیفیت پاسخ متنی اهمیت ندارد. مدل باید بتواند Schema را با ثبات مناسبی رعایت کند.

معیارهای انتخاب مدل:

  • کیفیت تولید JSON
  • پشتیبانی از Structured Output
  • رعایت فیلدهای ضروری
  • کیفیت زبان فارسی
  • عملکرد روی ساختارهای Nested
  • دقت طبقه‌بندی
  • نرخ Validation موفق
  • تعداد Retry
  • سرعت پاسخ
  • هزینه
  • Context Window

مدل‌ها را روی Dataset یکسان آزمایش کنید. برای مثال:

TEST_CASES = [
    {
        "input": "درخواست‌های API من Timeout می‌شوند.",
        "expected_category": "technical",
    },
    {
        "input": "مبلغ شارژ کیف پول ثبت نشده است.",
        "expected_category": "billing",
    },
    {
        "input": "لطفاً خروجی Excel اضافه کنید.",
        "expected_category": "feature_request",
    },
]

فهرست و قیمت جاری مدل‌ها در صفحه مدل‌های درواره در دسترس است.

تست بدون فراخوانی مدل

بخش‌هایی را که به LLM وابسته نیستند جداگانه تست کنید.

نمونه تست Schema:

from schemas import TicketAnalysis


def test_ticket_analysis_schema():
    result = TicketAnalysis(
        category="technical",
        priority="high",
        summary="درخواست‌های API با Timeout مواجه می‌شوند.",
        requires_human_review=True,
        confidence=0.9,
    )

    assert result.category == "technical"
    assert result.priority == "high"

نمونه تست Validator:

import pytest
from pydantic import ValidationError


def test_invalid_confidence():
    with pytest.raises(ValidationError):
        TicketAnalysis(
            category="technical",
            priority="high",
            summary="نمونه پیام معتبر",
            requires_human_review=False,
            confidence=2,
        )

در تست Endpoint نیز تابع analyze_ticket را Mock کنید تا Unit Test به مدل واقعی و هزینه API وابسته نباشد.

Evals روی مدل واقعی

Unit Test ساختار برنامه را بررسی می‌کند، اما کیفیت مدل را نمی‌سنجد. برای این کار به Evals نیاز دارید.

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

  • نرخ خروجی معتبر
  • دقت دسته‌بندی
  • دقت استخراج فیلدها
  • نرخ مقادیر ساختگی
  • تعداد Retry
  • Latency
  • Token مصرفی
  • هزینه هر نمونه
  • کیفیت فارسی
  • نرخ ارجاع صحیح به انسان

Dataset باید شامل نمونه‌های متنوع باشد:

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

معماری پیشنهادی Production

کاربر یا سیستم داخلی
↓
API Gateway
↓
احراز هویت و Rate Limit
↓
FastAPI
↓
Extraction Service
↓
Instructor
↓
OpenAI-compatible Client
↓
API درواره
↓
مدل انتخاب‌شده

پس از دریافت خروجی:

خروجی Pydantic
↓
قواعد کسب‌وکار
↓
بررسی داده مرجع
↓
ذخیره در پایگاه داده
↓
Workflow یا بررسی انسانی

Instructor باید در لایه Backend قرار گیرد. فرانت‌اند فقط به API برنامه شما متصل می‌شود.

ساختار پیشنهادی پروژه بزرگ‌تر

app/
├── api/
│   └── routes/
│       └── extraction.py
├── schemas/
│   ├── tickets.py
│   ├── invoices.py
│   └── products.py
├── services/
│   ├── instructor_client.py
│   ├── ticket_extractor.py
│   └── invoice_extractor.py
├── validators/
│   ├── categories.py
│   └── evidence.py
├── repositories/
│   ├── ticket_repository.py
│   └── product_repository.py
├── evals/
│   ├── datasets/
│   └── run_evals.py
├── core/
│   ├── config.py
│   ├── logging.py
│   └── exceptions.py
└── main.py

Schemaهای مختلف را داخل یک فایل بزرگ قرار ندهید. هر دامنه مانند تیکت، فاکتور و محصول بهتر است مدل‌ها و Validationهای مستقل داشته باشد.

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

Structured Output ممکن است به دلیل Retry یا Schema طولانی، مصرف بیشتری نسبت به یک درخواست متنی ساده داشته باشد.

برای کنترل هزینه:

  • Schema را فقط به فیلدهای ضروری محدود کنید.
  • Descriptionها را روشن اما کوتاه بنویسید.
  • تعداد Retry را محدود کنید.
  • متن‌های طولانی را پیش از ارسال بخش‌بندی کنید.
  • برای وظایف ساده از مدل متناسب استفاده کنید.
  • پاسخ خام طولانی درخواست نکنید.
  • Dataset واقعی برای مقایسه مدل‌ها بسازید.
  • نرخ موفقیت در اولین تلاش را ثبت کنید.
  • درخواست‌های تکراری را Cache کنید.
  • عملیات قابل‌حل با Regex یا کد را به LLM نسپارید.
  • برای هر نوع استخراج سقف هزینه تعیین کنید.

اگر یک مقدار با یک Parser قطعی و ساده قابل استخراج است، استفاده از LLM ضرورت ندارد.

چه زمانی از Instructor استفاده کنیم؟

Instructor برای این کاربردها مناسب است:

  • استخراج اطلاعات از متن
  • طبقه‌بندی تیکت
  • تحلیل بازخورد مشتری
  • تبدیل متن به JSON
  • استخراج اطلاعات محصول
  • پردازش رزومه
  • ساخت Metadata
  • استخراج موجودیت‌ها
  • تحلیل فرم‌های متنی
  • تبدیل درخواست کاربر به تنظیمات برنامه
  • ایجاد خروجی سازگار با پایگاه داده
  • تولید پاسخ Type-safe برای Backend

چه زمانی استفاده نکنیم؟

ممکن است Instructor انتخاب مناسبی نباشد اگر:

  • فقط پاسخ متنی برای نمایش به کاربر نیاز دارید.
  • ساختار با Regex یا Parser ساده قابل استخراج است.
  • Workflow به Tool Calling پیچیده نیاز دارد.
  • Agent باید چند مرحله تصمیم‌گیری کند.
  • خروجی کاملاً قطعی است و LLM ضرورتی ندارد.
  • مدل انتخاب‌شده Schema را با ثبات کافی رعایت نمی‌کند.
  • Latency بسیار پایین مهم‌تر از Validation و Retry است.

برای Agentهای چندمرحله‌ای، PydanticAI یا فریم‌ورک‌های Agent ممکن است مناسب‌تر باشند.

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

تعریف Schema بسیار پیچیده

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

اجباری‌کردن اطلاعات ناموجود

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

اعتماد کامل به Validation

Pydantic معتبر بودن شکل داده را بررسی می‌کند، نه صحت واقعی آن را.

Retry بیش از حد

Retry زیاد هزینه و زمان پاسخ را افزایش می‌دهد و ممکن است ضعف مدل یا Schema را پنهان کند.

Descriptionهای مبهم

فیلدهایی مانند value, status و data بدون توضیح کافی، مدل را دچار ابهام می‌کنند.

استفاده از مدل بدون Eval

عملکرد مناسب روی چند نمونه دستی برای Production کافی نیست.

ترکیب استخراج و تصمیم نهایی

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

استفاده از Client هم‌زمان در FastAPI

در Endpointهای Async از AsyncOpenAI و Client غیرهم‌زمان Instructor استفاده کنید.

ذخیره مستقیم خروجی در پایگاه داده

پیش از ذخیره، قواعد کسب‌وکار، ارتباط رکوردها و محدودیت‌های پایگاه داده را نیز بررسی کنید.

چک‌لیست انتشار

  • هدف استخراج دقیقاً مشخص شده است.
  • مدل Pydantic فقط فیلدهای ضروری را دارد.
  • Description تمام فیلدها روشن است.
  • مقادیر محدود با Enum یا Literal تعریف شده‌اند.
  • فیلدهای ناموجود Optional هستند.
  • Validation ساختاری و محتوایی از هم تفکیک شده‌اند.
  • تعداد Retry محدود شده است.
  • Timeout کلی وجود دارد.
  • خطاهای Provider مدیریت می‌شوند.
  • مدل و Prompt نسخه‌بندی شده‌اند.
  • Dataset ارزیابی ساخته شده است.
  • کیفیت زبان فارسی آزمایش شده است.
  • نرخ خروجی معتبر اندازه‌گیری می‌شود.
  • تعداد Retry و هزینه ثبت می‌شوند.
  • خروجی پیش از ذخیره با قواعد Backend بررسی می‌شود.
  • کلید API در متغیر محیطی نگهداری می‌شود.
  • Client غیرهم‌زمان در FastAPI استفاده می‌شود.
  • مسیر بررسی انسانی برای نمونه‌های مبهم وجود دارد.

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

Instructor چیست؟

Instructor کتابخانه‌ای برای دریافت خروجی ساختاریافته، معتبر و Type-safe از مدل‌های زبانی با استفاده از Pydantic است.

آیا Instructor یک مدل هوش مصنوعی است؟

خیر. Instructor یک کتابخانه میان برنامه شما و API مدل است. مدل هوش مصنوعی از طریق Provider یا API درواره فراخوانی می‌شود.

آیا می‌توان Instructor را به API درواره متصل کرد؟

بله. با ساخت یک نمونه OpenAI یا AsyncOpenAI و تنظیم base_url روی https://api.darvareh.ir/v1 می‌توان از Instructor همراه با API درواره استفاده کرد.

response_model چیست؟

response_model یک کلاس Pydantic است که شکل، نوع فیلدها و قواعد Validation خروجی مدل را مشخص می‌کند.

آیا Instructor همیشه JSON معتبر برمی‌گرداند؟

Instructor پاسخ را Parse و Validate می‌کند و می‌تواند در صورت نامعتبر بودن Retry انجام دهد؛ اما موفقیت نهایی به مدل، Mode، Prompt و پیچیدگی Schema وابسته است.

تفاوت Instructor با درخواست «فقط JSON بده» چیست؟

Instructor علاوه بر درخواست JSON، پاسخ را با مدل Pydantic بررسی، خطاهای ساختاری را شناسایی و در صورت تنظیم Retry برای اصلاح به مدل بازمی‌گرداند.

آیا Instructor از زبان فارسی پشتیبانی می‌کند؟

Instructor به زبان خاصی وابسته نیست. کیفیت استخراج فارسی به مدل انتخاب‌شده و طراحی Prompt بستگی دارد.

آیا Instructor از Async پشتیبانی می‌کند؟

بله. می‌توان آن را همراه با AsyncOpenAI در FastAPI و برنامه‌های غیرهم‌زمان استفاده کرد.

آیا Instructor صحت اطلاعات را تضمین می‌کند؟

خیر. Instructor اعتبار ساختاری داده را بررسی می‌کند. صحت واقعی اطلاعات باید با منبع، پایگاه داده، قواعد کسب‌وکار یا بررسی انسانی تأیید شود.

Instructor بهتر است یا PydanticAI؟

برای استخراج مستقیم داده ساختاریافته، Instructor معمولاً ساده‌تر است. برای Agentهای دارای Tool، Dependency Injection و اجرای چندمرحله‌ای، PydanticAI امکانات گسترده‌تری دارد.

آیا همه مدل‌های درواره با Instructor سازگارند؟

قابلیت مدل‌ها متفاوت است. مدلی را انتخاب کنید که تولید JSON یا Structured Output قابل‌قبولی داشته باشد و آن را روی Dataset پروژه آزمایش کنید.

جمع‌بندی

Instructor راهکاری سبک و کاربردی برای تبدیل پاسخ مدل‌های زبانی به اشیای معتبر Pydantic است. به‌جای Parse کردن دستی JSON، مدیریت فیلدهای حذف‌شده و نوشتن Retryهای پراکنده، می‌توان Schema خروجی را به‌شکل یک مدل پایتون تعریف کرد.

در این آموزش یاد گرفتیم چگونه:

  • Instructor را نصب کنیم.
  • آن را به API درواره متصل کنیم.
  • خروجی Type-safe دریافت کنیم.
  • فهرست‌ها و مدل‌های تو‌در‌تو بسازیم.
  • Validator اختصاصی تعریف کنیم.
  • Retry را مدیریت کنیم.
  • Validation Context به برنامه اضافه کنیم.
  • Client غیرهم‌زمان بسازیم.
  • Instructor را با FastAPI ترکیب کنیم.
  • برای تست، Evals و Production آماده شویم.

برای شروع، یک کاربرد محدود مانند طبقه‌بندی تیکت یا استخراج اطلاعات محصول انتخاب کنید. ابتدا Schema ساده‌ای بسازید و سپس مدل‌های مختلف را روی Dataset واقعی خود از نظر دقت، نرخ Validation موفق، تعداد Retry، هزینه و Latency مقایسه کنید.

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

منابع

مقالات مرتبط

برای مطالعه شرایط استفاده و محدودیت‌های مسئولیت، صفحه «سلب مسئولیت» را مشاهده کنید.

Read more

اتوماسیون هوش مصنوعی چیست؟ کاربردها و آموزش ساخت AI Automation

اتوماسیون هوش مصنوعی چیست؟ کاربردها و آموزش ساخت AI Automation

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

Agentic Commerce چیست؟ آینده خرید با ایجنت هوش مصنوعی

Agentic Commerce چیست؟ آینده خرید با ایجنت هوش مصنوعی

Agentic Commerce شیوه‌ای جدید برای خرید اینترنتی است که در آن ایجنت هوش مصنوعی می‌تواند نیاز کاربر را بفهمد، محصولات را جست‌وجو و مقایسه کند و فرایند خرید را پیش ببرد. در این راهنما با معماری، UCP، ACP و پیاده‌سازی آن با API درواره آشنا می‌شوید.