AI 功能的帳單可能埋下成本隱患:上線前 6 項防護措施
Your AI Feature's Bill Is a Bug Waiting to Happen: 6 Guardrails Before You Ship
文章建議 AI 功能上線前加入 6 項成本防護,避免用戶可影響的按用量計費請求造成意外支出。措施包括限制輸入和輸出、縮短重送的對話歷史、按用戶限制請求頻率、設定每日用量預算,以及只快取適合共用的相同答案。文章亦建議選用足以完成任務的較小模型、限制重試次數,並記錄用量及設定每日支出提示。
You ship an AI feature on Friday. It works. People like it.
On Monday you open the billing page and the number is ten times what you expected. Nobody attacked you. No server was hacked. One user pasted a book into the input box, another found a button that could be clicked forty times a minute, and a retry loop in your own code asked the same expensive question all weekend.
AI features are different from normal features in one way that matters here: every request has a variable, usage-based cost, and the user controls part of it.
How the Cost Actually Works
Most AI APIs charge by tokens, small chunks of text (roughly parts of words). You pay for tokens going in and tokens coming out, and the exact prices depend on your provider and model, so check their current pricing page.
Three things surprise people:
- Input is under the user's control. A longer message costs more.
- Chat history is sent again on every turn. Message 20 of a conversation carries messages 1 to 19 with it, so costs grow faster than the conversation does.
- Output can be long. Unless you limit it, the model decides how much to write, and you pay for it.
None of this is a problem at ten users. It is a problem when someone, deliberately or not, pushes on it.
The Starting Point
// app/api/chat/route.ts
export async function POST(req: Request) {
const { messages } = await req.json();
const result = await model.generate({ messages });
return Response.json({ text: result.text });
}
(model is a placeholder for your provider's SDK. The guardrails are the same for all of them.)
This has no limit on input size, history length, output length, request rate or total spend. Every one of those is a way to lose money.
Guardrail 1: Cap the Input
Reject oversized input before it reaches the model:
const MAX_CHARS = 4000;
const last = messages.at(-1)?.content ?? '';
if (typeof last !== 'string' || last.length > MAX_CHARS) {
return Response.json({ error: 'Message too long' }, { status: 413 });
}
Choose a limit that fits your real use case, not the biggest the model accepts.
Guardrail 2: Cap the Output
Almost every API lets you set a maximum for generated tokens. Set it:
const result = await model.generate({
messages: trimmed,
maxOutputTokens: 500, // parameter name varies by provider
});
A feature that summarizes a ticket does not need 4,000 tokens of reply. This is the cheapest guardrail there is.
Guardrail 3: Limit the History You Resend
Do not send the whole conversation forever. Keep the last few turns, or summarize the older ones:
const trimmed = messages.slice(-10); // last 10 messages only
For longer chats, replace old turns with a short summary. Either way, the cost of turn 50 stops being fifty times the cost of turn 1.
Guardrail 4: Rate-Limit Per User
Cost control starts with knowing who is asking. Require login for anything that spends money, then limit by user, not just by IP:
const key = `ai:${session.userId}`;
const used = await redis.incr(key);
if (used === 1) await redis.expire(key, 60);
if (used > 10) {
return Response.json({ error: 'Too many requests' }, { status: 429 });
}
This allows 10 requests a minute per user. The numbers are examples. Any shared store with atomic counters works, and you should pick limits that match real usage.
Guardrail 5: Set a Daily Budget Per User
Rate limits stop bursts. A budget stops slow drains. Track how many tokens each user has consumed today, and stop when they pass a limit:
const usage = await getTodayUsage(session.userId);
if (usage.tokens > DAILY_TOKEN_LIMIT) {
return Response.json({ error: 'Daily limit reached' }, { status: 429 });
}
const result = await model.generate({ messages: trimmed, maxOutputTokens: 500 });
await addUsage(session.userId, result.usage.totalTokens);
Most providers return usage numbers with each response, so you do not have to estimate. Also set an overall spending limit in your provider's dashboard if it offers one, as a last line of defense.
Guardrail 6: Do Not Pay Twice for the Same Answer
If many users ask the same question, such as "summarize this public page", cache the result:
const cacheKey = `summary:${hash(pageText)}`;
const cached = await redis.get(cacheKey);
if (cached) return Response.json({ text: cached });
const result = await model.generate({ /* ... */ });
await redis.set(cacheKey, result.text, { ex: 86400 });
Only cache answers that are the same for everyone. Never cache a response that includes one user's private data and then serve it to another.
Two More Habits That Save Money
Use the smallest model that does the job. Classification, extraction and short rewrites often work on a smaller, cheaper model. Save the largest one for tasks that need it, and test to be sure.
Be careful with retries. A retry loop that fires on every error multiplies your cost, and an error caused by a bad prompt will fail the same way every time. Limit retries to two or three, with a delay between them, and do not retry errors that are your fault.
Watch It Like Any Other Cost
Log, per request: user ID, model, input tokens, output tokens and feature name. A small table or a dashboard is enough. You are looking for two things: which feature costs the most, and which users cost the most. The first tells you where to optimize. The second tells you where someone is pushing on your limits.
Set an alert for daily spend, so a surprise reaches you as a message on Tuesday morning, not as an invoice at the end of the month.
The Rule
Treat every AI call as spending money that a user can influence. Cap the input, cap the output, trim the history, limit each user, set a budget and cache what is safe to cache. Do it before launch, not after the first bill.
Quick audit: open your AI endpoint and check six things: input limit, output limit, history trimming, per-user rate limit, daily budget and whether it needs login at all. Every "no" is a way to lose money.
I build SaaS products with Next.js, and limits like these sit in the route handler, next to authentication. If you want a typed starting point to build on, you can browse my templates on pixelanas.com.
Get the templates: https://pixelanas.gumroad.com
Have you ever been surprised by an AI bill? Tell me in the comments what caused it.
Anas, full-stack Next.js developer building SaaS products and premium templates. X: @ASheikh69751
來源:DEV Community · dev.to