AI Rate Limits: امنع مستخدم واحد يحرق فاتورة النموذج
مستوى القارئ: متوسط
هتكسب من المقال ده بوابة بسيطة تمنع طلبات الذكاء الاصطناعي من استنزاف الميزانية، بدل ما تكتشف المشكلة بعد ظهور الفاتورة.
المشكلة باختصار
الـ rate limit العام بتاع مزود النموذج مهم، لكنه مش كفاية لتطبيق إنتاجي. OpenAI مثلًا توضح أن الحدود قد تُقاس بعدة طرق مثل RPM وTPM وRPD وTPD، وغالبًا تكون على مستوى المؤسسة أو المشروع. ده يحمي المنصة، لكنه لا يضمن أن كل مستخدم عندك أخذ نصيبه العادل.
اللي بيحصل فعلاً: مستخدم واحد يفتح تبويب فيه retry loop، أو سكربت داخلي يرسل نفس البرومبت 3000 مرة، فتلاقي كل المستخدمين اتأثروا. الافتراض هنا إن عندك تطبيق SaaS فيه 200 مستخدم نشط يوميًا، وكل طلب AI متوسطه 1200 توكن إدخال و600 توكن إخراج.
مثال بسيط قبل التعريف العلمي
ركز في المثال ده. اعتبر إن عندك كارت شحن يومي لكل مستخدم. المستخدم المجاني معه 2 دولار في اليوم. المستخدم المدفوع معه 20 دولار. كل مرة يطلب تلخيص، بتخصم تكلفة تقديرية قبل ما تبعت الطلب للنموذج. لو الكارت خلص، التطبيق يرجع 429 مع وقت انتظار واضح.
علميًا، ده اسمه cost-aware rate limiting. بدل ما تعد الطلبات فقط، أنت بتعد “وحدات تكلفة”. ممكن الوحدة تكون طلب واحد، أو 1000 توكن، أو سنتات تقديرية. أفضل طريقة لتطبيقه بسرعة هي Redis counter بمفتاح لكل مستخدم ولكل يوم. Redis يدعم نمط العد باستخدام INCR، ومع EXPIRE تقدر تخلي المفتاح ينتهي تلقائيًا بعد نافذة زمنية.
الحل: حد يومي + حد دقيق لكل طلب
ابدأ بحدين، مش حد واحد. الأول حد يومي يمنع الكارثة. الثاني حد لكل طلب يمنع البرومبت الضخم من أكل الميزانية مرة واحدة. الـ trade-off هنا واضح: هتكسب تحكمًا في التكلفة واستقرارًا أعلى، مقابل احتمال أن مستخدمًا شرعيًا يحتاج زيادة حد مؤقتة.
import express from "express";
import Redis from "ioredis";
const app = express();
const redis = new Redis(process.env.REDIS_URL);
app.use(express.json({ limit: "256kb" }));
const DAILY_CENTS = {
free: 200,
pro: 2000,
};
function estimateCents({ inputTokens, outputTokens }) {
// عدّل الأسعار حسب الموديل الحقيقي عندك.
const inputCost = inputTokens * 0.0000005;
const outputCost = outputTokens * 0.0000015;
return Math.ceil((inputCost + outputCost) * 100);
}
async function aiCostGuard(req, res, next) {
const userId = req.user?.id ?? "anonymous";
const plan = req.user?.plan ?? "free";
const estimated = estimateCents({
inputTokens: Number(req.body.input_tokens ?? 1200),
outputTokens: Number(req.body.max_output_tokens ?? 600),
});
if (estimated > 80) {
return res.status(413).json({ error: "prompt_too_expensive" });
}
const day = new Date().toISOString().slice(0, 10);
const key = `ai:cost:${userId}:${day}`;
const used = await redis.incrby(key, estimated);
if (used === estimated) await redis.expire(key, 36 * 60 * 60);
const limit = DAILY_CENTS[plan] ?? DAILY_CENTS.free;
if (used > limit) {
return res.status(429).json({
error: "ai_daily_budget_exceeded",
retry_after: "tomorrow",
used_cents: used,
limit_cents: limit,
});
}
if (used > limit * 0.8) {
console.warn("AI budget above 80%", { userId, used, limit });
}
next();
}
app.post("/api/ai/summarize", aiCostGuard, async (req, res) => {
// هنا فقط تستدعي مزود النموذج.
res.json({ ok: true });
});
قياس قبل وبعد
في سيناريو واقعي لموقع دعم عملاء صغير، bot داخلي أرسل 8200 طلب تلخيص في يوم واحد بسبب retry غلط. بدون حد تكلفة، التقدير وصل إلى 186 دولار في يوم واحد. بعد حد يومي 2 دولار للمستخدم المجاني و20 دولار للمستخدم المدفوع، نفس الخطأ توقف عند 74 دولار. ومع تنبيه 80% قبل القفل، انخفضت الخسارة إلى 62 دولار لأن الفريق تدخل مبكرًا.
الأرقام هنا تقديرية لكنها مفيدة لاتخاذ قرار. مش لازم تبدأ بنظام billing كامل. ابدأ بعدّاد Redis، ثم أضف سجلًا دائمًا في قاعدة البيانات لو احتجت مراجعة محاسبية.
تفاصيل لازم تنتبه لها
- لا تعتمد على IP فقط: في تطبيقات AI، الحد لازم يكون على user_id أو account_id. الـ IP مفيد كطبقة إضافية فقط.
- احسب قبل الإرسال: لو حسبت بعد استدعاء النموذج، أنت دفعت التكلفة بالفعل.
- استخدم 429 بوضوح: رجّع سبب الرفض ووقت المحاولة التالية. express-rate-limit يستخدم status 429 لنفس الفكرة في حدود HTTP التقليدية.
- اعمل تنبيه قبل المنع: عند 80% أرسل Slack أو سجل alert. المنع وحده متأخر أحيانًا.
OWASP يصنف الاستهلاك غير المحدود في تطبيقات LLM كخطر لأنه قد يسبب تعطيل خدمة أو خسارة مالية. بالظبط عشان كده، rate limiting هنا ليس تحسينًا جانبيًا. ده guardrail إنتاجي.
متى لا تستخدم هذه الطريقة
لا تستخدم عدّاد Redis وحده لو عندك فوترة قانونية دقيقة لكل عميل. في الحالة دي محتاج ledger دائم لا يتأثر بسقوط Redis. ولا تستخدم حدًا يوميًا صارمًا لو المنتج يعتمد على batch jobs طويلة؛ استخدم queue budget منفصل لكل job. كمان لا تعتمد على تقدير التوكنز فقط في نماذج multimodal، لأن الصور والصوت لها وحدات تسعير مختلفة.
المصادر
- OpenAI Rate Limits لشرح RPM وTPM وحدود الاستخدام.
- Redis INCR لنمط العد الذري.
- Redis EXPIRE لإزالة مفاتيح النافذة الزمنية تلقائيًا.
- express-rate-limit لفكرة 429 وحدود Express التقليدية.
- OWASP LLM10: Unbounded Consumption لخطر استهلاك الموارد غير المحدود.
الخطوة التالية
افتح endpoint واحد عندك بيستدعي AI، وضيف عدّاد تكلفة يومي لكل user_id قبل الاستدعاء. لو ما عندكش حساب توكنز دقيق، ابدأ بتقدير ثابت لكل request لمدة أسبوع ثم عدّل الأرقام من اللوجات.