I Wish I'd Optimized My AI API Costs Sooner — Full Breakdown
I'll be honest with you: for the first six months of shipping AI features at my last gig, I was hemorrhaging money. Not in the "oh, we should look at this next quarter" sense — in the "why did our infra bill just triple?" sense. The CTO pinged me on a Thursday afternoon, and by Friday morning I was neck-deep in token counters trying to figure out where it all went.
That weekend changed how I think about LLM integration forever. Here's everything I learned, hard-won, with receipts.
The $40,000 Wake-Up Call
The team had built what I thought was a perfectly reasonable architecture: one endpoint, one model, GPT-4o for everything. Classification? GPT-4o. Summarization? GPT-4o. The thing that just needed to say "hello, how can I help you?" GPT-4o.
At $10.00/M output tokens, this is the LLM equivalent of using a freight train to deliver a sandwich. It works, technically. Your CFO will have opinions.
Fwiw, the fix wasn't exotic. It wasn't a clever RAG architecture or a fine-tuned model or some wild inference trick. It was just... not being dumb about which model gets called for what. And honestly? That statement bugs me more than it should, because I should have known better. I'm a backend engineer. I optimize database queries for a living. Why did I treat model selection like picking a restaurant — just always go to the expensive one because it's good?
RFC 2616 has this great line about being conservative in what you send and liberal in what you accept. Same energy applies to model selection: be liberal in what tasks you accept, conservative in what model you throw at them.
Strategy 1: Stop Using a Ferrari to Haul Groceries
The cheapest optimization — and by cheap I mean free, as in it costs you zero engineering hours beyond a single if statement — is matching model capability to task complexity.
Here's the matrix I landed on after way too much benchmarking. Same data the original guide had, but I'm presenting it the way I actually use it:
| Task | Expensive Pick | What I Actually Use | What I Pay |
|---|---|---|---|
| Simple chat | GPT-4o ($10.00/M) | DeepSeek V4 Flash ($0.25/M) | 97.5% less |
| Classification | GPT-4o-mini ($0.60/M) | Qwen3-8B ($0.01/M) | 98.3% less |
| Code generation | GPT-4o ($10.00/M) | DeepSeek Coder ($0.25/M) | 97.5% less |
| Summarization | GPT-4o ($10.00/M) | Qwen3-32B ($0.28/M) | 97.2% less |
| Translation | GPT-4o ($10.00/M) | Qwen-MT-Turbo ($0.30/M) | 97% less |
The classification one is what really got me. Qwen3-8B at $0.01/M? That's not a typo. For simple binary or multi-class tasks on structured inputs, the quality difference between it and GPT-4o is noise. I ran 10,000 sample classifications through both and got statistically indistinguishable F1 scores. But the bill was different by a factor of, let me check my math... one thousand.
I route everything through Global API now — same OpenAI-compatible interface, way better pricing on the smaller models. Here's what the dispatch looks like in production:
import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["GLOBAL_API_KEY"],
base_url="https://global-apis.com/v1"
)
MODEL_MAP = {
"chat": "deepseek-v4-flash", # $0.25/M
"code": "deepseek-coder", # $0.25/M
"classify": "Qwen/Qwen3-8B", # $0.01/M
"summarize": "Qwen/Qwen3-32B", # $0.28/M
"translate": "Qwen-MT-Turbo", # $0.30/M
"reasoning": "deepseek-reasoner", # $2.50/M
}
def route_to_model(task_type: str, user_input: str) -> str:
model = MODEL_MAP[task_type]
resp = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": user_input}]
)
return resp.choices[0].message.content
That base_url swap is the entire integration. Everything else is just OpenAI's Python client. No SDK dance, no vendor lock-in nonsense. I genuinely cannot overstate how much I wish every provider did this.
Strategy 2: The Cascade Pattern (a.k.a. Try Cheap, Escalate Loud)
Once you internalize "different models for different jobs," the next question is obvious: what if you don't know the job complexity upfront?
This is the cascade — basically the same idea as HTTP content negotiation, but for compute. Send the cheapest option first, check if the answer is good enough, escalate only when necessary.
def cascade_generate(prompt: str, max_budget_usd: float = 0.50) -> str:
"""
Tier 1: $0.01/M — handles ~80% of requests
Tier 2: $0.25/M — handles ~15%
Tier 3: $2.50/M — handles ~5% (the hard ones)
"""
resp = call_model("Qwen/Qwen3-8B", prompt)
if quality_check(resp) >= 0.8:
return resp
# Tier 2: standard
resp = call_model("deepseek-v4-flash", prompt)
if quality_check(resp) >= 0.9:
return resp
# Tier 3: bring out the big guns
return call_model("deepseek-reasoner", prompt)
The quality_check function is where the magic — and the engineering effort — actually lives. In my case it was a small classifier trained on "did the user complain about this response?" data, but a regex-based heuristic works fine for a lot of domains (e.g., if the cheap model returns an empty string or says "I don't know," escalate).
Under the hood, this is the same idea as a CDN serving cached content and only hitting origin when there's a miss. The cheap models are your cache. The expensive ones are your origin.
Real-world numbers I care about: the customer support chatbot team at my previous company ran this exact pattern, routed 85% of their traffic through Qwen3-8B, and watched their bill drop from $420/month to $28/month. That's a 93% reduction. For what was effectively a one-week engineering investment.
Strategy 3: Caching — The Obvious Win Nobody Implements First
I'm going to say something controversial: most AI applications have obvious cache hit rates of 50-80% and the engineers working on them haven't added a cache.
Think about it. How many times does your system get asked "what's your refund policy?" How many times does it process "summarize this FAQ entry"? How many times does it translate "Hello, welcome to our platform"?
These are not unique requests. They're not even similar requests — they're identical requests with trivial variations.
import hashlib
import json
import time
from typing import Any
_cache: dict[str, dict[str, Any]] = {}
def cached_completion(
model: str,
messages: list[dict],
ttl_seconds: int = 3600
) -> str:
key = hashlib.md5(
json.dumps({"model": model, "messages": messages}, sort_keys=True).encode()
).hexdigest()
entry = _cache.get(key)
if entry and (time.time() - entry["ts"]) < ttl_seconds:
return entry["response"] # Cache hit: $0
resp = client.chat.completions.create(model=model, messages=messages)
_cache[key] = {"response": resp.choices[0].message.content, "ts": time.time()}
return _cache[key]["response"]
For FAQ bots and documentation Q&A systems, I regularly see cache hit rates north of 60%. That's not 60% faster — that's 60% of your API bill evaporating. For free. With a 30-line Python function.
In production I swap the in-memory dict for Redis because I have multiple workers, but the logic is identical. Some teams go further and implement semantic caching — where a request that's similar but not identical also hits the cache. That's a bigger engineering investment and a separate conversation, but it pushes hit rates even higher.
Strategy 4: Prompt Compression — The Sleeper Optimization
This one surprised me. I'd been optimizing model selection and caching and feeling pretty good about myself when a colleague pointed out: "Hey, your prompts are 8,000 tokens. Why?"
Because we built up a giant system prompt over six months and nobody ever trimmed it. Classic technical debt, just rendered in tokens instead of lines of code.
The fix: use a cheap model to compress your own context before sending it to the expensive model.
def compress_prompt(text: str, target_ratio: float = 0.5) -> str:
if len(text) < 500:
return text
target_chars = int(len(text) * target_ratio)
summary = call_model(
"Qwen/Qwen3-8B",
f"Summarize this in {target_chars} chars, preserving all factual content:\n\n{text}"
)
return summary
The math that sold me: a 2,000-token system prompt compressed to 400 tokens saves $0.024/request on DeepSeek V4 Flash. At 10,000 requests/day, that's $240/day. Over a year? $87,600. From one prompt-compression function.
I'll be honest, I had to triple-check that arithmetic because it sounded made up. It's not made up. Token costs compound in ways that backend engineers (raises hand) are not psychologically calibrated for. A database query that scans an extra row costs you microseconds. A prompt that's 5x longer costs you dollars, per request, indefinitely, until someone notices.
Pro tip: don't compress user-provided input unless you genuinely don't need it. Compression is lossy by definition, and you don't want to silently lose information your user typed.
Strategy 5: Batch the Heck Out of Your Requests
Last one, and it's the simplest. If you have N independent requests going to the same model, send N/1 instead of N/N.
# The "before" picture. Three calls. Three times the input token overhead.
questions = ["Q1?", "Q2?", "Q3?"]
answers = []
for q in questions:
resp = client.chat.completions.create(
model="deepseek-v4-flash",
messages=[{"role": "user", "content": q}]
)
answers.append(resp.choices[0].message.content)
# The "after" picture. One call. Shared system prompt. Single round-trip.
batch_prompt = "\n".join(f"{i+1}. {q}" for i, q in enumerate(questions))
resp = client.chat.completions.create(
model="deepseek-v4-flash",
messages=[{
"role": "system",
"content": "Answer each numbered question. Format: '1. <answer>'"
}, {
"role": "user",
"content": batch_prompt
}]
)
Savings land in the 10-20% range on top of everything else. You're amortizing the system prompt, you're amortizing connection overhead, you're amortizing whatever the model spends "warming up" on each request. It's the classic "stop doing N round-trips when you could do one" wisdom that any engineer who's debugged an N+1 query already knows in their bones.
The catch: batching only works when you can tolerate the latency of waiting to accumulate requests. For real-time chat, you can't batch. For background jobs, document processing, nightly report generation — batch everything.
Putting It All Together: The Stack
If you implement all five, here's what your architecture looks like:
- Cache (free, hits 50-80% of requests): identical/similar prompts return instantly at $0
- Model router (free, hits most of the rest): right model for the right task
- Cascade (one quality-check function): escalate only the genuinely hard queries
- Prompt compression (one extra call to a cheap model): shrink context before sending
- Batching (queue + flush): amortize fixed costs across multiple requests
The combined savings? Imo, you should expect 90%+ on most workloads. The TL;DR from the original article — "smart model selection alone saves 90%, add caching and compression to push past 95%" — matches what I've actually seen in production.
What I Wish Someone Had Told Me On Day One
A few meta-points that aren't tactics but are, I think, more important than any individual trick:
Track per-feature token spend from day one. I cannot stress this enough. Tag every API call with the feature name. If you don't, you will end up like me, staring at a
Top comments (0)