کنترل هزینه API هوش مصنوعی؛ سقف خرج و امنیت کلید
اعتبار API را شارژ میکنید، یک هفته همه چیز خوب پیش میرود و بعد یک روز میبینید موجودی تمام شده در حالی که ترافیک محصولتان تغییری نکرده است. تقریباً هیچوقت علت این نیست که «مدل گران بود». علت معمولاً یکی از این سه چیز است: کدی که در حلقه افتاده، کلیدی که جایی بیرون رفته، یا کلیدی که از ابتدا داخل اپلیکیشن موبایل یا فایل جاوااسکریپت سایت قرار گرفته و هر کسی میتواند آن را بخواند. این مقاله دربارهٔ نحوهٔ محاسبهٔ هزینه نیست — آن را در توکن چیست و هزینهٔ API چطور حساب میشود نوشتهایم — اینجا دربارهٔ کنترل خرج و بهداشت کلید حرف میزنیم.
سه راهی که صورتحساب API منفجر میشود
- حلقه و تلاش مجدد بیکنترل. یک تابع که در صورت خطا دوباره درخواست میفرستد، بدون سقف تعداد تلاش و بدون فاصلهٔ فزاینده. یک اختلال کوتاه در سرویس میتواند هزاران درخواست تولید کند.
- ورودی بزرگ و کنترلنشده. کاربری یک فایل چندصد صفحهای میفرستد و شما تمام متن را در هر پیام دوباره به مدل میدهید. هزینهٔ ورودی خطی رشد میکند و کسی متوجه نمیشود.
- کلید لو رفته. بدترین حالت، چون مصرف شما نیست و هیچ رابطهای با ترافیک محصولتان ندارد. تا وقتی کلید باطل نشود، سرعت تخلیهٔ اعتبار فقط به مهاجم بستگی دارد.
هر سه با کنترلهای ساده قابل مهار است، به شرطی که پیش از حادثه گذاشته شده باشند. تفاوت مهم این سه با هم در سرعت است: حلقهٔ بیکنترل معمولاً در چند ساعت خودش را نشان میدهد، ورودی بزرگ در طول هفتهها بیسروصدا رشد میکند، و کلید لو رفته میتواند در یک شب همه چیز را ببرد. به همین دلیل ترتیب کارها اهمیت دارد و اول باید سراغ چیزی بروید که بیشترین خسارت را در کمترین زمان میزند.
یک نکتهٔ مهم دیگر: بدون دیدهبانی هیچکدام از این کنترلها معنا ندارد. اگر ندانید دیروز چند درخواست فرستادهاید و هر کدام چقدر توکن مصرف کرده، در لحظهٔ بروز مشکل فقط یک عدد کلی میبینید و نمیتوانید بگویید از کجا آمده است. ثبت مصرف هر فراخوانی در لاگ، ارزانترین کاری است که میتوانید بکنید و بیشترین کمک را در روز بد میکند.
سقف خرج: اولین کاری که باید انجام دهید
پیش از نوشتن اولین خط کد، سقف بگذارید. سه لایه سقف وجود دارد و هر کدام یک نوع فاجعه را میگیرد:
- سقف اعتبار حساب. سادهترین و مؤثرترین محافظ برای کاربر ایرانی: اعتبار را بهاندازهٔ یک دورهٔ کوتاه شارژ کنید، نه بهاندازهٔ یک سال. اگر اتفاقی بیفتد، حداکثر خسارت همان مبلغ است.
- سقف در سطح کلید یا پروژه. برای هر سرویس و هر محیط (توسعه، آزمایش، عملیاتی) کلید جداگانه بسازید و برای هرکدام بودجهٔ ماهانه تعیین کنید. اینطور یک اشکال در محیط توسعه، بودجهٔ محصول عملیاتی را نمیخورد.
- سقف داخل کد خودتان. لایهای که پیش از فراخوانی مدل، تعداد درخواست هر کاربر و هزینهٔ تجمعی روز را چک میکند. این تنها سقفی است که میتواند رفتار یک کاربر خاص را متوقف کند.
در کنار سقف، سه پارامتر ساده در خود درخواست بیشترین تأثیر را دارند: تعیین max_tokens برای محدود کردن طول پاسخ، انتخاب مدل متناسب با سختی کار بهجای استفادهٔ همیشگی از گرانترین مدل، و کوتاه نگه داشتن تاریخچهٔ گفتگو بهجای فرستادن کل مکالمه در هر پیام.
مورد سوم بیشتر از آنچه فکر میکنید اهمیت دارد. در یک چتبات ساده، اگر تمام تاریخچه را در هر پیام دوباره بفرستید، هزینهٔ پیام بیستم چند برابر پیام اول است، در حالی که کاربر احساس میکند دارد همان کار قبلی را میکند. راهحلهای عملی: فقط چند پیام آخر را نگه دارید، یا هر چند نوبت یک بار خلاصهای از گفتگو بسازید و بهجای متن کامل از همان استفاده کنید. همچنین برای درخواستهای تکراری با ورودی یکسان، پاسخ را در سمت خودتان ذخیره کنید؛ ارزانترین فراخوانی، فراخوانیای است که اصلاً انجام نمیشود.
جدول: هر کنترل چه چیزی را میگیرد
| کنترل | جلوی چه چیزی را میگیرد | هزینهٔ پیادهسازی | اولویت |
|---|---|---|---|
| شارژ اعتبار بهاندازهٔ یک دورهٔ کوتاه | هر فاجعهای؛ سقف خسارت را میبندد | هیچ | بسیار بالا |
| کلید جدا برای هر سرویس و محیط | سرایت مشکل از یک بخش به کل سیستم | کم | بسیار بالا |
| پراکسی بکاند بهجای کلید در سمت کاربر | سوءاستفاده از کلید استخراجشده از اپ یا سایت | متوسط | بسیار بالا |
| محدودیت نرخ درخواست بهازای هر کاربر | سوءاستفاده و حلقهٔ ناخواسته | کم | بالا |
| تعیین max_tokens و انتخاب مدل مناسب | رشد بیسروصدای هزینهٔ خروجی | هیچ | بالا |
| ثبت مصرف هر درخواست در لاگ | کور بودن؛ بدون آن علت را پیدا نمیکنید | کم | بالا |
| هشدار موجودی و مصرف غیرعادی | دیر فهمیدن | کم | متوسط |
| چرخش دورهای کلید | نشتهای قدیمی و کشفنشده | کم | متوسط |
فاجعهای که کسی دربارهاش نمینویسد: کلید داخل اپلیکیشن
این رایجترین اشتباه پرهزینهای است که در پروژههای ایرانی میبینیم. توسعهدهنده کلید API را مستقیم داخل کد اپلیکیشن Flutter یا React Native میگذارد، یا داخل فایل جاوااسکریپت سایت، و فرض میکند چون کد کامپایل یا فشرده شده، کسی آن را نمیبیند. این فرض غلط است. فایل APK یا باندل جاوااسکریپت روی دستگاه کاربر است و استخراج رشتههای متنی از آن کار چند دقیقهای است. متغیرهای محیطی هم نجاتتان نمیدهند: هر مقداری که در زمان ساخت به باندل تزریق شود، در باندل نهایی قابل خواندن است.
نتیجه همیشه یکسان است: کلید در یک مخزن عمومی یا کانال اشتراکی منتشر میشود و اعتبار شما در چند ساعت خالی میشود، در حالی که هیچکدام از آن درخواستها از محصول شما نیامده است.
راهحل یک الگوی ساده است: کلید فقط روی سرور شما میماند و اپلیکیشن با سرور شما حرف میزند، نه با ارائهدهنده. سرور شما درخواست کاربر را میگیرد، اعتبارسنجی و محدودسازی میکند، بعد خودش مدل را صدا میزند.
این الگو فقط مسئلهٔ امنیت را حل نمیکند؛ چند مزیت جدی دیگر هم دارد که به همان اندازه ارزشمندند. میتوانید دستور سیستمی را در یک نقطه نگه دارید و بدون انتشار نسخهٔ جدید اپلیکیشن تغییرش دهید. میتوانید برای هر کاربر سقف مصرف بگذارید و کسی را که رفتار غیرعادی دارد متوقف کنید. میتوانید مدل را عوض کنید بدون اینکه کاربران مجبور به بهروزرسانی شوند. و مهمتر از همه، میتوانید هزینهٔ هر قابلیت محصول را جداگانه ببینید و بفهمید کدام بخش دارد پول میخورد.
هزینهٔ این الگو یک لایهٔ اضافه در معماری و کمی تأخیر بیشتر است. در عمل، این تأخیر در برابر زمان تولید پاسخ مدل ناچیز است و تقریباً هیچوقت دلیل خوبی برای کنار گذاشتن پراکسی نیست.
الگوی پراکسی بکاند با نمونه کد
نمونهٔ کوتاه با Node.js و Express. کلید از متغیر محیطی سرور خوانده میشود و هیچوقت به سمت کاربر نمیرود:
// server.js — npm i express node-fetch@2
const express = require('express');
const fetch = require('node-fetch');
const app = express();
app.use(express.json({ limit: '64kb' }));
const BASE_URL = process.env.AI_BASE_URL; // https://netarz.ir/api/ai/v1
const API_KEY = process.env.AI_API_KEY; // only on the server
const hits = new Map(); // naive per-IP rate limit
app.post('/api/chat', async (req, res) => {
const ip = req.ip;
const now = Date.now();
const list = (hits.get(ip) || []).filter((t) => now - t < 60000);
if (list.length >= 10) {
return res.status(429).json({ error: 'too many requests' });
}
list.push(now);
hits.set(ip, list);
const message = String(req.body.message || '').slice(0, 4000);
if (!message) return res.status(400).json({ error: 'empty message' });
const upstream = await fetch(BASE_URL + '/chat/completions', {
method: 'POST',
headers: {
'Authorization': 'Bearer ' + API_KEY,
'Content-Type': 'application/json',
},
body: JSON.stringify({
model: 'gpt-4o-mini',
max_tokens: 500,
messages: [
{ role: 'system', content: 'Answer briefly in Persian.' },
{ role: 'user', content: message },
],
}),
});
const data = await upstream.json();
console.log('usage', data.usage); // log every call for cost tracking
res.json({ reply: data.choices?.[0]?.message?.content ?? '' });
});
app.listen(3000);
سه نکتهٔ مهم در همین چند خط: کلید فقط روی سرور است، طول ورودی و خروجی محدود شده، و مصرف هر درخواست در لاگ ثبت میشود. همان الگو در پایتون هم به همین سادگی است:
# pip install openai
import os
from openai import OpenAI
client = OpenAI(
base_url=os.environ["AI_BASE_URL"], # https://netarz.ir/api/ai/v1
api_key=os.environ["AI_API_KEY"],
)
def ask(message: str) -> str:
message = message[:4000]
resp = client.chat.completions.create(
model="gpt-4o-mini",
max_tokens=500,
messages=[
{"role": "system", "content": "Answer briefly in Persian."},
{"role": "user", "content": message},
],
)
usage = resp.usage
print("in:", usage.prompt_tokens, "out:", usage.completion_tokens)
return resp.choices[0].message.content
نام مدلهای در دسترس و قیمت هرکدام را در فهرست مدلهای دروازه ببینید و نمونههای کاملتر در مستندات API آمده است. توجه کنید که دروازهٔ نِت اَرز هماکنون با مدلهای OpenAI فعال است و بقیهٔ ارائهدهندگان در حال اتصالاند.
چرخش کلید و ساعت اولی که کلید لو میرود
فرض کنید همین حالا فهمیدید کلیدتان جایی بیرون رفته است. ترتیب کارها اهمیت دارد:
- کلید را باطل کنید، نه اینکه فقط عوضش کنید. تا وقتی کلید قدیمی معتبر است، هر کار دیگری بیفایده است.
- کلید جدید بسازید و در متغیر محیطی سرور بگذارید، نه در کد و نه در فایل تنظیماتی که به مخزن میرود.
- مصرف چند ساعت گذشته را بررسی کنید تا بفهمید چقدر خسارت خوردهاید و از چه زمانی شروع شده است.
- منشأ نشت را پیدا کنید. اگر کلید در مخزن گیت رفته، صرفاً حذف فایل کافی نیست؛ کلید در تاریخچهٔ کامیتها باقی میماند.
- باقی کلیدها را هم بچرخانید اگر همه در یک فایل کنار هم بودهاند.
برای پیشگیری، سه عادت ساده کافی است: کلیدها را در یک پسورد منیجر نگه دارید نه در گروه چت یا فایل متنی؛ فایل تنظیمات محلی را از ابتدا به فهرست نادیدهگرفتههای گیت اضافه کنید؛ و هر چند ماه یک بار کلیدها را بچرخانید حتی اگر مشکلی پیش نیامده است. حواستان به مسیر دیگر نشت هم باشد: صفحات جعلی که بهبهانهٔ «تمدید اعتبار» کلید یا رمز شما را میخواهند؛ نشانههای این دام را در فیشینگ چیست جمع کردهایم.
چکلیست نهایی پیش از عملیاتی شدن
- هیچ کلیدی در کد سمت کاربر، باندل جاوااسکریپت یا اپلیکیشن موبایل نیست.
- هر سرویس و هر محیط کلید جدا و بودجهٔ جدا دارد.
- محدودیت نرخ بهازای هر کاربر و سقف طول ورودی و خروجی گذاشته شده است.
- مصرف هر درخواست ثبت میشود و میتوانید هزینهٔ دیروز را به تفکیک سرویس ببینید.
- هشدار موجودی فعال است تا پیش از صفر شدن اعتبار خبردار شوید.
- اعتبار را بهاندازهٔ یک دورهٔ کوتاه شارژ میکنید، نه یکجا برای ماهها.
اگر هنوز بین اشتراک و API مردد هستید، تفاوت API و اشتراک این تصمیم را با محاسبه روشن میکند و روش پرداخت ریالی در API هوش مصنوعی با پرداخت ریالی توضیح داده شده است. برای شروع، اعتبار را در دروازهٔ API نِت اَرز به تومان شارژ کنید، برای هر سرویس یک کلید جدا بسازید و همان روز اول سقف بگذارید؛ گذاشتن سقف بعد از یک صورتحساب سنگین، دیگر کنترل هزینه نیست، جبران خسارت است.
آموزشهای کاربردی در گیسو
گیسو برند هوش مصنوعی نِت اَرز است، با همین حساب و همین اعتبار. این راهنماها قدمبهقدم نشان میدهند همین کار را چطور با هوش مصنوعی انجام دهید.
پرسشهای پرتکرار
آیا میتوانم کلید API را داخل اپلیکیشن موبایلم بگذارم؟
خیر. فایل نصبی اپلیکیشن روی دستگاه کاربر است و استخراج رشتههای داخل آن کار سادهای است؛ متغیرهای محیطی زمان ساخت هم در باندل نهایی قابل خواندناند. کلید باید فقط روی سرور شما بماند و اپلیکیشن با سرور خودتان حرف بزند.
اگر کلیدم لو رفت اولین کاری که باید بکنم چیست؟
کلید قدیمی را باطل کنید، نه اینکه صرفاً کلید جدید بسازید؛ تا وقتی کلید قبلی معتبر است هر کار دیگری بیاثر است. بعد کلید تازه را در متغیر محیطی سرور بگذارید، مصرف چند ساعت گذشته را بررسی کنید و منشأ نشت را پیدا کنید.
چطور جلوی مصرف ناگهانی و غیرعادی را بگیرم؟
با ترکیب چند لایه: اعتبار را بهاندازهٔ یک دورهٔ کوتاه شارژ کنید، برای هر سرویس کلید و بودجهٔ جداگانه بسازید، محدودیت نرخ درخواست بهازای هر کاربر بگذارید و max_tokens را تعیین کنید. بدون ثبت مصرف در لاگ هم عملاً نمیفهمید مشکل از کجاست.
این مقاله با راهنمای محاسبهٔ توکن چه فرقی دارد؟
آن مقاله توضیح میدهد هزینهٔ هر درخواست چطور محاسبه میشود و به شما کمک میکند بودجه را تخمین بزنید. این مقاله دربارهٔ نگه داشتن آن هزینه در محدودهٔ تعیینشده و محافظت از کلید است؛ دو موضوع مکمل که هرکدام صفحهٔ خودشان را دارند.
نظر خوانندگان
هنوز نظری ثبت نشده. اگر این مقاله پرسشتان را جواب داد یا جای چیزی در آن خالی ماند، همینجا بنویسید.