You shipped a support chatbot. It works. Users love it. Then the invoice lands and your CFO wants a meeting.
This is the pattern we see constantly. Teams obsess over model quality and prompt engineering, then treat token spend as an afterthought. Six months later they're burning thousands a month with no ceiling and no visibility into who or what is driving the cost.
Token costs don't kill your ROI in one dramatic moment. They erode it quietly, one unmetered conversation at a time.
Why token spend gets out of control
Three things compound against you:
Context bloat. Every turn in a conversation resends the full history plus system prompt plus retrieved documents. A 20-message support thread can hit tens of thousands of tokens per request because you're paying to reprocess everything each time.
Freeloaders and abuse. Public-facing bots attract scrapers, prompt-injection probes, and people using your GPT-4 endpoint as a free coding assistant. If there's no auth or rate limit, you're subsidizing strangers.
Model overkill. Routing a "what are your hours?" question to your most expensive model is like renting a crane to hang a picture frame.
Step 1: Measure before you optimize
You cannot govern what you don't log. Every LLM call should record tokens in, tokens out, model, user or session ID, and estimated cost. Do this at the middleware layer, not scattered across your app.
import time
PRICING = {
"gpt-4o": {"in": 2.50 / 1_000_000, "out": 10.00 / 1_000_000},
"gpt-4o-mini": {"in": 0.15 / 1_000_000, "out": 0.60 / 1_000_000},
}
def log_usage(response, model, user_id):
usage = response.usage
p = PRICING[model]
cost = usage.prompt_tokens * p["in"] + usage.completion_tokens * p["out"]
record = {
"ts": time.time(),
"user_id": user_id,
"model": model,
"prompt_tokens": usage.prompt_tokens,
"completion_tokens": usage.completion_tokens,
"cost_usd": round(cost, 6),
}
# ship to your warehouse / metrics store
emit_metric(record)
return cost
Once you have per-user cost data, patterns jump out fast. Usually a tiny fraction of sessions drive the majority of spend.
Step 2: Kill token freeloading
Rate limiting is your first line of defense. Cap requests and tokens per user per window. A per-token budget matters more than a per-request one, because a single abusive request can be huge.
from collections import defaultdict
import time
class TokenBudget:
def __init__(self, daily_token_limit=50_000):
self.limit = daily_token_limit
self.usage = defaultdict(lambda: {"tokens": 0, "reset": 0})
def check(self, user_id, estimated_tokens):
now = time.time()
u = self.usage[user_id]
if now > u["reset"]:
u["tokens"], u["reset"] = 0, now + 86400
if u["tokens"] + estimated_tokens > self.limit:
raise PermissionError("Daily token budget exceeded")
u["tokens"] += estimated_tokens
Layer on input validation too. Cap max input length, strip obvious injection patterns, and require auth on anything that isn't explicitly meant to be anonymous. If your bot is public, put a lightweight challenge in front of it before a session can hit the model.
Step 3: Route by complexity, not by default
Most support queries are simple. Send them to a cheap model. Escalate to the expensive one only when needed.
A practical router:
- Tier 1 (cheap model): FAQs, status checks, greetings, classification.
- Tier 2 (mid model): multi-step reasoning, document synthesis.
- Tier 3 (premium): edge cases the cheaper tiers flagged as low confidence.
You can gate escalation on a confidence check. If the cheap model returns a low-confidence answer or explicitly signals it can't handle the request, retry on the better model. Most traffic never leaves Tier 1, and your blended cost drops sharply.
Step 4: Trim the context you resend
Context is where the silent spend lives. Tactics that actually move the needle:
- Summarize old turns. After N messages, compress the earlier history into a short summary instead of resending it verbatim.
- Retrieve narrowly. Return the top 3 relevant chunks, not the top 15. More context rarely improves answers and always increases cost.
- Cache system prompts. Providers now offer prompt caching. Structure your calls so the static system prompt and instructions hit the cache instead of being billed fresh every turn.
- Set max_tokens on output. An unbounded response can ramble for 2,000 tokens when 200 would do.
Step 5: Set budgets and alerts
Governance isn't a one-time cleanup. It's a control loop. Define a monthly budget, wire spend into a dashboard, and set alerts at 50%, 80%, and 100%. When you cross a threshold, someone gets paged, not surprised at month end.
Better still, enforce a hard cap. If the org-wide daily budget is blown, degrade gracefully to a cheaper model or a static fallback rather than letting spend run free.
The payoff
None of this is glamorous. It's plumbing. But the teams that treat token spend as a first-class engineering concern run chatbots at a fraction of the cost of teams that don't, with no visible quality drop.
Measure everything. Route by complexity. Trim context aggressively. Cap the freeloaders. Do that, and your chatbot stays an asset instead of quietly becoming a liability line on the P&L.
If you're scaling AI support and want the governance layer built in from day one rather than bolted on after the scary invoice, that's exactly the kind of system we design at Michael AI.
Originally published at getmichaelai.com
Top comments (0)