Rate Limit چیست؟ مدیریت محدودیت نرخ API هوش مصنوعی و خطای 429

Rate Limit یا محدودیت نرخ مشخص می‌کند یک کاربر یا نرم‌افزار در بازه زمانی معین چه تعداد درخواست یا توکن می‌تواند به API ارسال کند. در این راهنمای تخصصی با RPM، TPM، خطای 429، الگوریتم‌های Token Bucket، Exponential Backoff، Jitter، کنترل هم‌زمانی و معماری صف برای APIهای هوش مصنوعی آشنا می‌شوید.

Share
مدیریت Rate Limit و خطای 429 در API هوش مصنوعی

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

اگر برنامه در مدت کوتاهی درخواست‌های بیشتری از ظرفیت مجاز ارسال کند، API معمولاً پاسخ 429 Too Many Requests برمی‌گرداند.

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

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

  • درخواست اول: طبقه‌بندی یک پیام ۲۰ کلمه‌ای
  • درخواست دوم: تحلیل یک سند ۵۰ هزار توکنی و تولید پاسخ طولانی

به همین دلیل سرویس‌های هوش مصنوعی ممکن است هم‌زمان چند نوع محدودیت داشته باشند:

  • تعداد درخواست در دقیقه
  • تعداد توکن در دقیقه
  • تعداد درخواست روزانه
  • میزان پردازش هم‌زمان
  • ظرفیت صف
  • تعداد Jobهای فعال
  • مدت ویدئوی تولیدشده
  • تعداد تصاویر در بازه زمانی مشخص

در این مقاله، Rate Limit را از دید توسعه‌دهنده و معماری Production بررسی می‌کنیم و روش مدیریت آن را در Python و Node.js با API سازگار با OpenAI درواره پیاده‌سازی می‌کنیم.

Rate Limit چیست؟

Rate Limit سازوکاری برای کنترل تعداد یا حجم عملیاتی است که یک Client می‌تواند در یک بازه زمانی انجام دهد.

فرض کنید محدودیت یک API برابر با ۶۰ درخواست در دقیقه باشد. اگر برنامه طی یک دقیقه ۸۰ درخواست ارسال کند، بخشی از درخواست‌ها ممکن است با خطای 429 رد شوند.

Rate Limit معمولاً روی یکی از شناسه‌های زیر اعمال می‌شود:

  • حساب کاربری
  • سازمان
  • پروژه
  • کلید API
  • آدرس IP
  • مدل
  • Endpoint
  • کاربر نهایی
  • مسیر ارائه‌دهنده
  • ترکیبی از موارد بالا

برای مثال، ممکن است یک حساب در مجموع ۱۰۰۰ درخواست در دقیقه ظرفیت داشته باشد، اما یک مدل خاص فقط ۲۰۰ درخواست در دقیقه را بپذیرد.

چرا APIها Rate Limit دارند؟

جلوگیری از مصرف ناعادلانه

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

کنترل بار زیرساخت

Rate Limit مانع از آن می‌شود که افزایش ناگهانی درخواست‌ها، سرورها یا مدل‌های پردازشی را بیش از ظرفیت درگیر کند.

کاهش اثر خطاهای نرم‌افزاری

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

مدیریت هزینه

در سرویس‌های مصرف‌محور، ارسال کنترل‌نشده درخواست ممکن است هزینه زیادی ایجاد کند. Rate Limit یک لایه محافظتی در کنار Budget Limit و سهمیه مصرف است.

حفظ پایداری مدل

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

خطای 429 چیست؟

کد HTTP 429 Too Many Requests به این معناست که Client در یک بازه زمانی، درخواست‌های بیشتری از حد مجاز ارسال کرده است.

این Status Code در RFC 6585 تعریف شده و پاسخ می‌تواند Headerای به نام Retry-After داشته باشد که زمان مناسب برای تلاش مجدد را مشخص می‌کند.

نمونه پاسخ:

HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 12

{
  "error": {
    "type": "rate_limit_error",
    "message": "Too many requests"
  }
}

در این مثال، Client باید حداقل ۱۲ ثانیه صبر کرده و سپس در صورت مجازبودن Retry، درخواست را دوباره ارسال کند.

براساس استاندارد HTTP، مقدار Retry-After می‌تواند تعداد ثانیه یا یک تاریخ HTTP باشد.

آیا هر خطای 429 به معنای یک مشکل است؟

خیر. خطای 429 می‌تواند چند علت متفاوت داشته باشد:

افزایش ناگهانی نرخ درخواست

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

عبور از محدودیت درخواست

برنامه از سقف RPM یا RPS عبور کرده است.

عبور از محدودیت توکن

تعداد درخواست‌ها مجاز است، اما مجموع توکن ورودی یا خروجی از TPM فراتر رفته است.

تکمیل ظرفیت هم‌زمانی

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

پایان سهمیه یا اعتبار

بعضی سرویس‌ها برای پایان سهمیه یا ظرفیت حساب نیز پاسخ 429 برمی‌گردانند. در این حالت Retry معمولاً مشکل را حل نمی‌کند.

تکمیل ظرفیت مدل یا ارائه‌دهنده

ممکن است یک مدل یا مسیر پردازشی موقتاً ظرفیت خالی نداشته باشد.

بنابراین برنامه نباید هر پاسخ 429 را بدون تحلیل، بارها تکرار کند. ابتدا باید نوع خطا، Headerها و پیام پاسخ بررسی شوند.

RPM چیست؟

RPM مخفف Requests Per Minute و به معنای تعداد درخواست مجاز در هر دقیقه است.

اگر RPM برابر با ۶۰ باشد، به‌طور متوسط یک درخواست در ثانیه قابل ارسال است. بااین‌حال نحوه محاسبه دقیق به الگوریتم Rate Limiter بستگی دارد.

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

  • ارسال یک درخواست در هر ثانیه
  • ارسال ۶۰ درخواست در ثانیه اول و توقف تا پایان دقیقه

ممکن است الگوی اول پذیرفته شود اما الگوی دوم به‌دلیل Burst بالا با محدودیت مواجه شود.

RPS چیست؟

RPS مخفف Requests Per Second است. این معیار در APIهایی با ترافیک زیاد یا نیاز به پاسخ سریع استفاده می‌شود.

رابطه تقریبی:

RPS = RPM ÷ 60

برای مثال:

RPM = 600
RPS = 10

اما این محاسبه به معنای مجازبودن ارسال دقیق ۱۰ درخواست در هر ثانیه نیست. Burst Limit و الگوریتم محدودکننده نیز اهمیت دارند.

TPM چیست؟

TPM مخفف Tokens Per Minute و به معنای تعداد توکن قابل پردازش در یک دقیقه است.

در APIهای متنی، محدودیت TPM ممکن است براساس یکی از این مقادیر محاسبه شود:

  • توکن ورودی
  • توکن خروجی
  • مجموع ورودی و خروجی
  • برآورد خروجی براساس سقف تعریف‌شده
  • ظرفیت رزروشده برای درخواست

روش دقیق محاسبه به سرویس و مدل بستگی دارد.

برای مثال، اگر محدودیت برابر یک میلیون توکن در دقیقه باشد و هر درخواست به‌طور متوسط ۲۰۰۰ توکن مصرف کند:

1,000,000 ÷ 2,000 = 500

حد بالای نظری حدود ۵۰۰ درخواست در دقیقه خواهد بود؛ حتی اگر RPM عدد بیشتری باشد.

در عمل باید سربار، تغییر طول درخواست‌ها و Burst را نیز در نظر گرفت.

TPD و RPD چیست؟

TPD

Tokens Per Day میزان توکن قابل‌مصرف در یک روز است.

RPD

Requests Per Day تعداد درخواست مجاز روزانه است.

ممکن است برنامه از محدودیت دقیقه‌ای عبور نکند، اما به سقف روزانه برسد. این وضعیت با Retry کوتاه‌مدت برطرف نمی‌شود و تا بازنشانی سهمیه یا افزایش ظرفیت ادامه خواهد داشت.

محدودیت Concurrency چیست؟

Concurrency Limit مشخص می‌کند چه تعداد درخواست می‌توانند هم‌زمان در حال پردازش باشند.

فرض کنید محدودیت هم‌زمانی برابر ۱۰ باشد. اگر ۱۰ درخواست هنوز تمام نشده باشند، درخواست یازدهم ممکن است:

  • در صف قرار گیرد.
  • با خطای 429 رد شود.
  • با خطای ظرفیت پاسخ داده شود.
  • تا آزادشدن یک Slot منتظر بماند.

Concurrency با RPM متفاوت است. ممکن است برنامه در هر دقیقه فقط ۳۰ درخواست ارسال کند، اما اگر هر درخواست ۶۰ ثانیه طول بکشد، تعداد عملیات هم‌زمان بالا می‌رود.

این موضوع در مدل‌های زیر اهمیت بیشتری دارد:

  • تولید ویدئو
  • تولید تصویر
  • پردازش اسناد طولانی
  • مدل‌های Reasoning
  • تبدیل گفتار
  • Batch Processing
  • Agentهای چندمرحله‌ای

Burst Limit چیست؟

Burst به ارسال تعداد زیادی درخواست در مدت بسیار کوتاه گفته می‌شود.

برای مثال، یک Job زمان‌بندی‌شده ممکن است رأس ساعت، ۵۰۰ درخواست را هم‌زمان ارسال کند. حتی اگر ظرفیت ساعتی کافی باشد، این افزایش ناگهانی می‌تواند Rate Limit را فعال کند.

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

  • Queue
  • Token Bucket
  • Leaky Bucket
  • محدودکردن Concurrency
  • توزیع تصادفی زمان شروع
  • پردازش تدریجی
  • Batch API

تفاوت Rate Limit و Quota چیست؟

این دو مفهوم نزدیک‌اند، اما یکسان نیستند.

ویژگیRate LimitQuota
هدفکنترل سرعت مصرفکنترل حجم کل مصرف
بازه معمولثانیه یا دقیقهروز، ماه یا دوره مالی
راه‌حل رایجکاهش سرعت یا Retryافزایش سهمیه یا انتظار تا بازنشانی
نمونه۶۰ درخواست در دقیقه۱۰۰ هزار درخواست در ماه
خطای محتمل429 موقت429 یا خطای سهمیه

برنامه باید میان خطای موقت نرخ و پایان سهمیه تفاوت قائل شود.

الگوریتم‌های رایج Rate Limiting

Fixed Window

در این روش، زمان به بازه‌های ثابت تقسیم می‌شود؛ برای مثال هر دقیقه یک Window مستقل است.

مزیت:

  • پیاده‌سازی ساده
  • مصرف حافظه کم

محدودیت:

  • امکان Burst در مرز دو Window

برای مثال، کاربر می‌تواند ۱۰۰ درخواست در ثانیه پایانی دقیقه اول و ۱۰۰ درخواست در ثانیه ابتدایی دقیقه دوم ارسال کند.

Sliding Window Log

زمان هر درخواست ذخیره می‌شود و فقط درخواست‌های داخل بازه جاری محاسبه می‌شوند.

مزیت:

  • دقت زیاد

محدودیت:

  • مصرف حافظه و پردازش بیشتر

Sliding Window Counter

ترکیبی تقریبی از Fixed Window و Sliding Window است که با شمارنده‌های بازه‌ای، نرخ را هموارتر محاسبه می‌کند.

Token Bucket

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

مزایا:

  • پشتیبانی از Burst کنترل‌شده
  • مناسب برای APIهای عمومی
  • امکان وزن‌دهی متفاوت به عملیات

برای مثال، درخواست متنی ساده می‌تواند یک Token و تولید ویدئو ۲۰ Token از مخزن مصرف کند.

Leaky Bucket

درخواست‌ها وارد یک صف می‌شوند و با نرخ ثابت از آن خارج می‌شوند.

مزایا:

  • هموارکردن ترافیک
  • مناسب برای کاهش Burst

محدودیت:

  • در صورت پرشدن صف، درخواست جدید رد می‌شود یا باید منتظر بماند.

Headerهای مربوط به Rate Limit

بعضی APIها Headerهایی برای نمایش وضعیت محدودیت برمی‌گردانند:

x-ratelimit-limit-requests: 600
x-ratelimit-remaining-requests: 124
x-ratelimit-reset-requests: 8s
x-ratelimit-limit-tokens: 1000000
x-ratelimit-remaining-tokens: 245000
x-ratelimit-reset-tokens: 3s
retry-after: 8

نام و معنای Headerها میان ارائه‌دهندگان متفاوت است. برنامه نباید بدون بررسی مستندات، وجود یک Header مشخص را فرض کند.

اگر Retry-After موجود است، معمولاً باید حداقل به‌اندازه مقدار آن منتظر بمانید. مستندات فعلی OpenAI نیز توصیه می‌کند هنگام وجود این Header، زمان اعلام‌شده رعایت شود.

Exponential Backoff چیست؟

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

نمونه ساده:

تلاش اول: ۱ ثانیه
تلاش دوم: ۲ ثانیه
تلاش سوم: ۴ ثانیه
تلاش چهارم: ۸ ثانیه
تلاش پنجم: ۱۶ ثانیه

فرمول ساده:

delay = base_delay × 2^attempt

این روش باعث می‌شود Client هنگام اختلال موقت، فشار بیشتری به سرویس وارد نکند.

Jitter چیست؟

اگر هزار Client هم‌زمان خطا بگیرند و همگی دقیقاً پس از چهار ثانیه Retry کنند، یک موج جدید از درخواست‌ها ایجاد می‌شود. به این پدیده Thundering Herd گفته می‌شود.

Jitter مقدار تصادفی کوچکی به زمان انتظار اضافه می‌کند تا Retryها در طول زمان پخش شوند.

نمونه Full Jitter:

maximum_delay = min(cap, base × 2^attempt)
delay = random(0, maximum_delay)

AWS استفاده از Exponential Backoff همراه با Jitter را برای کاهش هم‌زمانی Retryها و فشار مجدد روی سرویس توضیح داده است.

Retry نامحدود چرا خطرناک است؟

Retry نامحدود می‌تواند مشکلات زیر را ایجاد کند:

  • افزایش هزینه
  • افزایش تأخیر کاربر
  • تکمیل Thread یا Connection Pool
  • بزرگ‌شدن صف
  • افزایش فشار روی سرویس
  • تکرار عملیات غیرقابل‌بازگشت
  • ایجاد Retry Storm
  • پنهان‌شدن خطای اصلی

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

  • حداکثر تعداد تلاش
  • حداکثر زمان کل
  • فهرست خطاهای قابل Retry
  • Backoff
  • Jitter
  • Timeout
  • امکان لغو درخواست
  • ثبت تعداد Retry

چه خطاهایی را Retry کنیم؟

خطاRetryتوضیح
408معمولاً بلهپایان زمان انتظار درخواست
409وابسته به عملیاتممکن است تعارض موقت باشد
429 موقتبلهبا رعایت Retry-After
500معمولاً بلهخطای داخلی موقت
502معمولاً بلهخطای Gateway
503معمولاً بلهعدم دسترسی موقت
504معمولاً بلهTimeout در Gateway
400خیردرخواست باید اصلاح شود
401خیرکلید یا احراز هویت باید اصلاح شود
403معمولاً خیردسترسی کافی نیست
404 مدلخیرشناسه مدل یا مسیر اشتباه است
پایان سهمیهخیرنیازمند افزایش یا بازنشانی سهمیه

Retry داخلی SDK و خطر Double Retry

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

برای مثال:

  • SDK: سه تلاش
  • لایه برنامه: سه تلاش
  • Worker: سه تلاش

حداکثر تلاش واقعی:

3 × 3 × 3 = 27

کتابخانه رسمی Python برای برخی خطاها، از جمله 429 و خطاهای 500، Retry پیش‌فرض دارد و گزینه max_retries برای تنظیم آن ارائه می‌کند.

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

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

آدرس پایه API درواره:

https://api.darvareh.ir/v1

نصب کتابخانه:

pip install openai

متغیرهای محیطی:

export DARVAREH_API_KEY="YOUR_API_KEY"
export DARVAREH_MODEL="YOUR_MODEL_ID"

Client پایه:

import os

from openai import OpenAI


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

در این حالت SDK می‌تواند Retryهای پشتیبانی‌شده خود را انجام دهد. اگر به سیاست کاملاً اختصاصی نیاز دارید، Retry داخلی را غیرفعال کنید:

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

پیاده‌سازی Backoff و Jitter در Python

import asyncio
import email.utils
import os
import random
from datetime import datetime, timezone

from openai import (
    APIConnectionError,
    APIStatusError,
    APITimeoutError,
    AsyncOpenAI,
    RateLimitError,
)


client = AsyncOpenAI(
    api_key=os.environ["DARVAREH_API_KEY"],
    base_url="https://api.darvareh.ir/v1",
    timeout=30.0,
    max_retries=0,
)

MODEL_ID = os.environ["DARVAREH_MODEL"]


def parse_retry_after(error: APIStatusError) -> float | None:
    response = getattr(error, "response", None)

    if response is None:
        return None

    value = response.headers.get("retry-after")

    if not value:
        return None

    try:
        return max(0.0, float(value))
    except ValueError:
        pass

    try:
        retry_date = email.utils.parsedate_to_datetime(value)

        if retry_date.tzinfo is None:
            retry_date = retry_date.replace(tzinfo=timezone.utc)

        now = datetime.now(timezone.utc)

        return max(0.0, (retry_date - now).total_seconds())
    except (TypeError, ValueError):
        return None


def calculate_delay(
    attempt: int,
    error: APIStatusError | None,
    base_delay: float = 1.0,
    maximum_delay: float = 30.0,
) -> float:
    if error is not None:
        retry_after = parse_retry_after(error)

        if retry_after is not None:
            return min(retry_after, maximum_delay)

    exponential_cap = min(
        maximum_delay,
        base_delay * (2 ** attempt),
    )

    return random.uniform(0, exponential_cap)


async def generate_text(
    prompt: str,
    maximum_attempts: int = 4,
) -> str:
    last_error: Exception | None = None

    for attempt in range(maximum_attempts):
        try:
            completion = await client.chat.completions.create(
                model=MODEL_ID,
                temperature=0.2,
                messages=[
                    {
                        "role": "user",
                        "content": prompt,
                    }
                ],
            )

            content = completion.choices[0].message.content

            if not content:
                raise ValueError("پاسخ متنی دریافت نشد.")

            return content

        except RateLimitError as error:
            last_error = error

            if attempt == maximum_attempts - 1:
                break

            delay = calculate_delay(attempt, error)
            await asyncio.sleep(delay)

        except (APITimeoutError, APIConnectionError) as error:
            last_error = error

            if attempt == maximum_attempts - 1:
                break

            delay = calculate_delay(attempt, None)
            await asyncio.sleep(delay)

        except APIStatusError as error:
            last_error = error

            if error.status_code < 500:
                raise

            if attempt == maximum_attempts - 1:
                break

            delay = calculate_delay(attempt, error)
            await asyncio.sleep(delay)

    raise RuntimeError(
        "درخواست پس از چند تلاش ناموفق بود."
    ) from last_error

ویژگی‌های این پیاده‌سازی:

  • Retry داخلی SDK غیرفعال شده است.
  • تعداد تلاش محدود است.
  • Retry-After در اولویت قرار دارد.
  • برای خطاهای موقت از Full Jitter استفاده می‌شود.
  • خطاهای 4xx غیرقابل Retry دوباره ارسال نمی‌شوند.
  • Timeout و خطای اتصال جداگانه مدیریت می‌شوند.

کنترل هم‌زمانی در Python

Retry به‌تنهایی Rate Limit را حل نمی‌کند. اگر صدها Coroutine هم‌زمان درخواست بفرستند، همه آن‌ها ممکن است 429 دریافت کنند.

با Semaphore می‌توان تعداد درخواست‌های هم‌زمان را محدود کرد:

import asyncio


CONCURRENCY_LIMIT = 8
semaphore = asyncio.Semaphore(CONCURRENCY_LIMIT)


async def limited_generate(prompt: str) -> str:
    async with semaphore:
        return await generate_text(prompt)

پردازش گروهی:

async def process_prompts(
    prompts: list[str],
) -> list[str | Exception]:
    tasks = [
        limited_generate(prompt)
        for prompt in prompts
    ]

    return await asyncio.gather(
        *tasks,
        return_exceptions=True,
    )

Semaphore فقط هم‌زمانی داخل همان Process را کنترل می‌کند. اگر برنامه روی چند سرور یا چند Worker اجرا شود، به کنترل توزیع‌شده نیاز دارید.

Rate Limiter توزیع‌شده با Redis

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

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

  • کلید جداگانه برای هر مدل
  • شمارنده درخواست
  • شمارنده توکن
  • زمان انقضای Window
  • عملیات Atomic
  • Lua Script
  • Token Bucket مشترک
  • سهمیه جداگانه برای Tenantها

نمونه کلیدها:

rate:tenant:42:model:text:rpm
rate:tenant:42:model:text:tpm
rate:global:provider:concurrency

عملیات بررسی و کاهش ظرفیت باید Atomic باشد. اجرای جداگانه GET و SET می‌تواند در شرایط هم‌زمانی Race Condition ایجاد کند.

کنترل نرخ با Queue و Worker

برای پردازش‌های غیرتعاملی، Queue معمولاً بهتر از Retry مستقیم است.

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

  1. API درخواست کاربر را دریافت می‌کند.
  2. Job در صف ذخیره می‌شود.
  3. Worker ظرفیت مجاز را بررسی می‌کند.
  4. درخواست با نرخ کنترل‌شده ارسال می‌شود.
  5. نتیجه ذخیره می‌شود.
  6. وضعیت Job به کاربر اعلام می‌شود.

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

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

نمونه وضعیت Job

پاسخ اولیه:

{
  "job_id": "job_8c2f91",
  "status": "queued"
}

وضعیت در حال پردازش:

{
  "job_id": "job_8c2f91",
  "status": "processing",
  "attempt": 2
}

نتیجه نهایی:

{
  "job_id": "job_8c2f91",
  "status": "completed",
  "result": {
    "summary": "خلاصه تولیدشده"
  }
}

پیاده‌سازی در Node.js و TypeScript

نصب SDK:

npm install openai

ساخت Client:

import OpenAI from "openai";

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

تابع Sleep:

function sleep(milliseconds: number): Promise<void> {
  return new Promise((resolve) => {
    setTimeout(resolve, milliseconds);
  });
}

محاسبه Full Jitter:

function calculateJitterDelay(
  attempt: number,
  baseDelayMs = 1_000,
  maximumDelayMs = 30_000,
): number {
  const cap = Math.min(
    maximumDelayMs,
    baseDelayMs * 2 ** attempt,
  );

  return Math.floor(Math.random() * cap);
}

خواندن Retry-After:

function parseRetryAfter(
  value: string | null | undefined,
): number | null {
  if (!value) {
    return null;
  }

  const seconds = Number(value);

  if (Number.isFinite(seconds)) {
    return Math.max(0, seconds * 1_000);
  }

  const date = Date.parse(value);

  if (Number.isNaN(date)) {
    return null;
  }

  return Math.max(0, date - Date.now());
}

ارسال درخواست همراه با Retry:

async function generateText(
  prompt: string,
  maximumAttempts = 4,
): Promise<string> {
  const model = process.env.DARVAREH_MODEL;

  if (!model) {
    throw new Error("DARVAREH_MODEL is not configured.");
  }

  let lastError: unknown;

  for (let attempt = 0; attempt < maximumAttempts; attempt += 1) {
    try {
      const completion =
        await client.chat.completions.create({
          model,
          temperature: 0.2,
          messages: [
            {
              role: "user",
              content: prompt,
            },
          ],
        });

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

      if (!content) {
        throw new Error("Empty model response.");
      }

      return content;
    } catch (error) {
      lastError = error;

      const isRateLimit =
        error instanceof OpenAI.RateLimitError;

      const isTimeout =
        error instanceof OpenAI.APIConnectionTimeoutError;

      const isConnection =
        error instanceof OpenAI.APIConnectionError;

      const isServerError =
        error instanceof OpenAI.APIError &&
        typeof error.status === "number" &&
        error.status >= 500;

      const retryable =
        isRateLimit ||
        isTimeout ||
        isConnection ||
        isServerError;

      if (!retryable || attempt === maximumAttempts - 1) {
        throw error;
      }

      let delay = calculateJitterDelay(attempt);

      if (error instanceof OpenAI.APIError) {
        const retryAfter = parseRetryAfter(
          error.headers?.get("retry-after"),
        );

        if (retryAfter !== null) {
          delay = Math.min(retryAfter, 30_000);
        }
      }

      await sleep(delay);
    }
  }

  throw lastError;
}

کلاس‌ها و جزئیات Error ممکن است میان نسخه‌های SDK تغییر کنند؛ بنابراین نسخه Package را قفل کرده و کد را با مستندات همان نسخه تطبیق دهید.

محدودکردن Concurrency در Node.js

یک پیاده‌سازی ساده Worker Pool:

async function mapWithConcurrency<T, R>(
  items: T[],
  concurrency: number,
  handler: (item: T) => Promise<R>,
): Promise<R[]> {
  const results = new Array<R>(items.length);
  let nextIndex = 0;

  async function worker(): Promise<void> {
    while (true) {
      const currentIndex = nextIndex;
      nextIndex += 1;

      if (currentIndex >= items.length) {
        return;
      }

      results[currentIndex] = await handler(
        items[currentIndex],
      );
    }
  }

  const workers = Array.from(
    {
      length: Math.min(concurrency, items.length),
    },
    () => worker(),
  );

  await Promise.all(workers);

  return results;
}

استفاده:

const results = await mapWithConcurrency(
  prompts,
  8,
  generateText,
);

این روش نیز فقط در یک Process کار می‌کند و جایگزین Rate Limiter توزیع‌شده نیست.

محاسبه ظرفیت موردنیاز

فرض کنید در زمان اوج:

  • ۲۰ درخواست در ثانیه دریافت می‌شود.
  • هر درخواست به‌طور متوسط ۱۵۰۰ توکن مصرف می‌کند.

RPM موردنیاز:

20 × 60 = 1,200 RPM

TPM موردنیاز:

20 × 1,500 × 60 = 1,800,000 TPM

این اعداد حداقل نظری هستند. برای نوسان طول ورودی، Retry و Burst باید حاشیه ظرفیت در نظر گرفت.

فرمول عمومی:

Required RPM = Peak RPS × 60

Required TPM =
Peak RPS × Average Tokens Per Request × 60

اگر حاشیه ظرفیت ۴۰ درصد باشد:

Provisioned TPM =
Required TPM × 1.4

در مثال بالا:

1,800,000 × 1.4 = 2,520,000 TPM

آیا کاهش max_tokens به مدیریت Rate Limit کمک می‌کند؟

ممکن است. بعضی ارائه‌دهندگان ظرفیت را براساس توکن واقعی و برخی براساس سقف خروجی رزروشده یا برآورد درخواست محاسبه می‌کنند.

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

راهکارها:

  • خروجی طبقه‌بندی را کوتاه نگه دارید.
  • از JSON ساختاریافته استفاده کنید.
  • سقف خروجی را متناسب با وظیفه تنظیم کنید.
  • تاریخچه غیرضروری را حذف کنید.
  • اسناد را قبل از ارسال بازیابی و فیلتر کنید.
  • برای وظایف ساده از مدل مناسب‌تر استفاده کنید.

Model Routing چگونه به Rate Limit کمک می‌کند؟

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

برای مثال:

  • طبقه‌بندی → مدل سریع و اقتصادی
  • استخراج اطلاعات → مدل کوچک با خروجی ساختاریافته
  • تحلیل پیچیده → مدل Reasoning
  • پاسخ پشتیبانی → مدل عمومی
  • ورودی طولانی → مدل دارای Context مناسب

این توزیع بار باعث می‌شود محدودیت یک مدل خاص به گلوگاه کل محصول تبدیل نشود.

Routing نباید فقط هنگام خطا انجام شود. بهتر است براساس نوع وظیفه، کیفیت موردنیاز، هزینه و ظرفیت برنامه‌ریزی شود.

Fallback هنگام Rate Limit

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

پیش از Fallback بررسی کنید:

  • مدل جایگزین قابلیت موردنیاز را دارد.
  • ساختار خروجی یکسان است.
  • کیفیت آن قابل قبول است.
  • هزینه آن از سقف مجاز عبور نمی‌کند.
  • Context کافی دارد.
  • Tool Calling یا ورودی تصویر را پشتیبانی می‌کند.
  • داده ورودی با شرایط مسیر جایگزین سازگار است.

Fallback کورکورانه ممکن است خطای 429 را به هزینه یا افت کیفیت تبدیل کند.

Circuit Breaker چیست؟

Circuit Breaker زمانی استفاده می‌شود که یک مدل یا ارائه‌دهنده برای مدتی خطای زیادی تولید می‌کند.

سه حالت رایج:

Closed

درخواست‌ها عادی ارسال می‌شوند.

Open

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

Half-Open

تعداد محدودی درخواست آزمایشی ارسال می‌شود تا بازیابی سرویس بررسی شود.

Circuit Breaker مانع از آن می‌شود که برنامه هنگام اختلال، درخواست‌های بی‌نتیجه و Retryهای زیاد تولید کند.

مدیریت Rate Limit در سیستم چندمستاجری

در یک SaaS، فقط محدودیت ارائه‌دهنده کافی نیست. باید سهمیه داخلی برای هر Tenant یا کاربر نیز تعریف شود.

نمونه سیاست:

ظرفیت کل مدل: 1000 RPM

سهمیه سازمان A: 400 RPM
سهمیه سازمان B: 300 RPM
سهمیه کاربران عمومی: 200 RPM
ظرفیت رزرو اضطراری: 100 RPM

قابلیت‌های پیشنهادی:

  • سهمیه براساس Plan
  • محدودیت Burst
  • سقف مصرف روزانه
  • محدودیت هم‌زمانی
  • اولویت سازمانی
  • ظرفیت رزروشده
  • Queue جداگانه
  • Fair Scheduling
  • گزارش مصرف
  • امکان افزایش موقت ظرفیت

اولویت‌بندی صف

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

سطوح پیشنهادی:

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

از یک صف واحد FIFO برای تمام کاربردها استفاده نکنید؛ زیرا Jobهای سنگین می‌توانند درخواست‌های تعاملی را مسدود کنند.

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

شاخص‌های درخواست

  • تعداد درخواست در ثانیه
  • تعداد درخواست در دقیقه
  • تعداد درخواست موفق
  • تعداد خطای 429
  • نرخ خطای 5xx
  • تعداد Retry
  • زمان انتظار Retry
  • تعداد Timeout

شاخص‌های توکن

  • توکن ورودی
  • توکن خروجی
  • TPM هر مدل
  • میانگین توکن هر درخواست
  • صدک ۹۵ طول ورودی
  • توکن مصرف‌شده در Retry

شاخص‌های صف

  • طول صف
  • قدیمی‌ترین Job
  • زمان انتظار
  • نرخ ورود Job
  • نرخ پردازش
  • تعداد Dead Letter
  • تعداد تلاش هر Job

شاخص‌های تجربه کاربر

  • زمان دریافت اولین توکن
  • زمان کامل پاسخ
  • درصد درخواست‌های معطل
  • درصد Fallback
  • درصد خطا پس از Retry
  • درصد لغو توسط کاربر

هشدارهای عملیاتی پیشنهادی

برای این شرایط Alert تعریف کنید:

  • افزایش ناگهانی خطای 429
  • باقی‌ماندن ظرفیت زیر آستانه
  • افزایش تعداد Retry
  • عبور زمان انتظار صف از SLA
  • رشد غیرطبیعی توکن هر درخواست
  • افزایش Concurrency
  • فعال‌شدن Circuit Breaker
  • افزایش استفاده از Fallback
  • کاهش نرخ تکمیل موفق
  • مصرف سریع‌تر از Budget روزانه

اشتباهات رایج در مدیریت Rate Limit

Retry فوری

ارسال مجدد درخواست بدون تأخیر معمولاً باعث دریافت خطای 429 دیگری می‌شود.

Retry هم‌زمان همه درخواست‌ها

این رفتار موج جدیدی از ترافیک ایجاد می‌کند. از Jitter استفاده کنید.

نادیده‌گرفتن Retry-After

اگر سرور زمان انتظار مشخص کرده است، Retry زودتر از آن منطقی نیست.

استفاده از Sleep در Thread تعاملی

درخواست کاربر را برای مدت طولانی باز نگه ندارید. برای انتظار طولانی، Job را به صف منتقل کنید.

نداشتن سقف تلاش

هر درخواست باید Retry Budget مشخصی داشته باشد.

Double Retry

Retry داخلی SDK، لایه سرویس و Worker را بدون هماهنگی فعال نکنید.

استفاده از Semaphore فقط در یک Instance

در معماری چندسروری، Semaphore محلی مصرف کل سیستم را کنترل نمی‌کند.

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

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

نادیده‌گرفتن TPM

ممکن است تعداد درخواست‌ها کم باشد، اما ورودی‌های طولانی محدودیت توکن را تکمیل کنند.

اشتباه‌گرفتن Quota با Rate Limit

پایان اعتبار یا سهمیه با چند ثانیه انتظار حل نمی‌شود.

Log نکردن Headerها و شناسه درخواست

برای عیب‌یابی باید Status Code، زمان، مدل، تعداد تلاش و شناسه درخواست ثبت شوند؛ اما کلید API و محتوای حساس نباید وارد Log شوند.

معماری پیشنهادی برای API هوش مصنوعی در Production

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

  1. API Gateway
  2. احراز هویت و سهمیه کاربر
  3. Rate Limiter داخلی
  4. طبقه‌بندی درخواست
  5. Model Router
  6. Queue برای عملیات غیرتعاملی
  7. Concurrency Controller
  8. SDK Client
  9. Retry Policy
  10. Circuit Breaker
  11. Fallback
  12. Usage Metering
  13. Logging و Tracing
  14. Alerting

قواعد مهم:

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

استفاده از API درواره

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

آدرس پایه:

https://api.darvareh.ir/v1

برای مدیریت صحیح محدودیت نرخ در یک پروژه مبتنی بر درواره:

  • شناسه مدل را در تنظیمات قرار دهید.
  • Client را در Backend ایجاد کنید.
  • Timeout مشخص داشته باشید.
  • Retry SDK را آگاهانه تنظیم کنید.
  • خطاهای 429 را ثبت کنید.
  • درخواست‌های گروهی را در Queue قرار دهید.
  • تعداد عملیات هم‌زمان را محدود کنید.
  • میزان توکن و هزینه هر قابلیت را جداگانه ثبت کنید.
  • برای مدل‌ها و مسیرهای مختلف تست ظرفیت انجام دهید.
  • مقادیر و Headerهای واقعی پاسخ را مبنای پیاده‌سازی قرار دهید.

محدودیت و ظرفیت می‌تواند براساس مدل، مسیر و شرایط ارائه تغییر کند؛ بنابراین از قراردادن یک مقدار ثابت برای تمام مدل‌ها خودداری کنید.

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

Rate Limit چیست؟

Rate Limit محدودیتی است که تعداد یا حجم درخواست‌های قابل ارسال به API را در یک بازه زمانی کنترل می‌کند.

خطای 429 چیست؟

خطای 429 Too Many Requests معمولاً نشان می‌دهد سرعت یا حجم درخواست‌ها از محدودیت مجاز عبور کرده است.

چگونه خطای 429 را رفع کنیم؟

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

RPM چیست؟

RPM تعداد درخواست قابل ارسال در هر دقیقه است.

TPM چیست؟

TPM تعداد توکن قابل پردازش در هر دقیقه است. شیوه دقیق محاسبه آن به سرویس و مدل بستگی دارد.

Retry-After چیست؟

Retry-After یک Header HTTP است که حداقل زمان پیشنهادی برای تلاش مجدد را مشخص می‌کند.

Exponential Backoff چیست؟

روشی است که فاصله Retryها را پس از هر شکست به‌صورت نمایی افزایش می‌دهد.

Jitter چه کاربردی دارد؟

Jitter زمان Retry را کمی تصادفی می‌کند تا تعداد زیادی Client دقیقاً در یک لحظه دوباره درخواست نفرستند.

آیا همه خطاهای 429 باید Retry شوند؟

خیر. اگر 429 ناشی از پایان سهمیه، اعتبار یا محدودیت ثابت حساب باشد، Retry کوتاه‌مدت فایده‌ای ندارد.

تفاوت Rate Limit و Concurrency چیست؟

Rate Limit سرعت یا حجم مصرف در یک بازه را کنترل می‌کند؛ Concurrency تعداد عملیات فعال هم‌زمان را محدود می‌کند.

آیا SDK خودش Retry انجام می‌دهد؟

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

Queue چه زمانی لازم است؟

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

آیا می‌توان هنگام Rate Limit مدل را تغییر داد؟

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

جمع‌بندی

Rate Limit فقط یک خطای 429 نیست؛ بخشی اساسی از معماری یک محصول مبتنی بر API است. در سرویس‌های هوش مصنوعی باید هم‌زمان تعداد درخواست، مصرف توکن، Burst، Concurrency، ظرفیت صف و محدودیت مدل را مدیریت کرد.

برای مدیریت درست Rate Limit:

  1. میان RPM، TPM، Quota و Concurrency تفاوت قائل شوید.
  2. Headerهای پاسخ و Retry-After را بررسی کنید.
  3. Retry را فقط برای خطاهای موقت انجام دهید.
  4. تعداد تلاش‌ها و زمان کل Retry را محدود کنید.
  5. از Exponential Backoff همراه با Jitter استفاده کنید.
  6. Retry داخلی SDK را با Retry برنامه هماهنگ کنید.
  7. هم‌زمانی را با Semaphore یا Rate Limiter کنترل کنید.
  8. در معماری چندسروری از Redis یا محدودکننده توزیع‌شده استفاده کنید.
  9. پردازش‌های سنگین را به Queue منتقل کنید.
  10. مصرف هر مدل و Tenant را جداگانه مانیتور کنید.
  11. برای اختلال‌های طولانی Circuit Breaker داشته باشید.
  12. Fallback را براساس کیفیت، هزینه و قابلیت مدل طراحی کنید.

برای اتصال برنامه‌های Python، Node.js و سایر نرم‌افزارها به مدل‌های مختلف هوش مصنوعی می‌توانید از API سازگار با OpenAI درواره استفاده کنید.

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

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

مقالات مرتبط

منابع

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

Read more