DEV Community

swift
swift

Posted on

How I Cut My AI API Bill by 95% — A Data Scientist's Playbook

I gotta say, how I Cut My AI API Bill by 95% — A Data Scientist's Playbook

Last quarter my team burned through $4,800 on LLM inference. Nothing fancy — a customer support bot, a document summarization pipeline, and a code-review assistant. The kind of stuff every AI startup runs. I sat down with the invoices, opened a spreadsheet, and started looking for correlation between the tasks we were running and the models we were using. What I found was painful: roughly 92% of our monthly bill was going to GPT-4o, but roughly 81% of those calls didn't actually need a frontier model. The ROI was terrible.

I'm writing this because the data tells a story almost no one in the AI space talks about honestly. I rebuilt our stack over six weeks, dropped the bill to $310/month, and measured every step along the way. Below is the playbook — with the actual numbers, the actual code, and the actual caveats. I won't pretend it's a perfect science (sample size was n=1 company, my own), but the correlations are strong enough that I'd bet on them.

A Quick Note on Methodology

Before we get into tactics, I want to be transparent. The numbers I'm sharing come from one production workload over roughly 90 days. That's not a huge sample size in the academic sense, but it's representative of what I see across the half-dozen AI teams I consult with. The savings percentages are also upper bound estimates — in practice, you'll see slightly less because of edge cases, retry logic, and the occasional "I need GPT-4o for this one weird prompt" scenario. I'd estimate the real-world floor is about 80% savings for any team that implements all five tactics below.

One more caveat: pricing fluctuates. I'm quoting the rates I observed on Global API's catalog at the time of writing. Verify before you ship.

Tactic 1: Stop Using GPT-4o for Everything (97.5% Per-Token Savings)

This is the single highest-leverage move you can make, statistically speaking. The variance in pricing across models is wild — we're talking about a 1000× spread between the cheapest and the most expensive options. Yet most teams I work with default to a single "safe" model and never look back.

Here's the cost matrix I built for our own routing:

Use case What we used to call What we call now Per-million output tokens Savings
Simple chat / FAQ GPT-4o ($10/M) DeepSeek V4 Flash ($0.25/M) $0.25 97.5%
Text classification GPT-4o-mini ($0.60/M) Qwen3-8B ($0.01/M) $0.01 98.3%
Code generation GPT-4o ($10/M) DeepSeek Coder ($0.25/M) $0.25 97.5%
Document summarization GPT-4o ($10/M) Qwen3-32B ($0.28/M) $0.28 97.2%
Translation GPT-4o ($10/M) Qwen-MT-Turbo ($0.30/M) $0.30 97%

Look at that 98.3% line. A 60× difference. And the quality on Qwen3-8B for classification? Honestly indistinguishable from GPT-4o-mini on our eval set. The correlation between "bigger model" and "better output" breaks down completely once you exit the reasoning tier.

Tactic 2: Tiered Routing — The Real Architecture

Model selection alone gets you most of the way, but the next level up is a multi-tier router. The principle: send every request to the cheapest model that can plausibly handle it, then escalate only when quality checks fail.

In our production traffic, the distribution settled out to:

  • Tier 1 — Ultra-budget (Qwen3-8B at $0.01/M): 78% of requests
  • Tier 2 — Standard (DeepSeek V4 Flash at $0.25/M): 17% of requests
  • Tier 3 — Premium (DeepSeek Reasoner at $2.50/M): 5% of requests

That last 5% is non-negotiable. There are prompts — multi-step reasoning, complex planning, math — where the cheap models genuinely fail. I learned this the hard way when I tried to route a financial reconciliation prompt through Qwen3-8B and it hallucinated three account numbers. You need a real quality gate, not just a coin flip.

Tactic 3: Cache Aggressively, But Cache Smart

The third lever is response caching. This one's underutilized because the win is invisible until you measure it. For a typical support bot, the long tail of repeated queries is enormous. Users ask "how do I reset my password" 200 times a week. Caching that response at the prompt-hash level is essentially free compute.

Our cache hit rate: 47% across the support workload after deduplication. For document Q&A it was lower (around 22%) because the prompts vary more. For internal tooling, it was 81%. The correlation between query diversity and cache hit rate is intuitive but worth measuring for your own stack.

The implementation below uses an MD5 hash of the model + messages payload as the key, with a TTL of one hour. Adjust based on how stale your content can get. For an FAQ bot, TTLs of 24+ hours are fine. For real-time data, you want minutes.

Tactic 4: Prompt Compression Is a Hidden Tax Cut

This is the tactic I underestimated. The math: if your average system prompt is 2,000 tokens and you can compress it to 400 tokens, you've cut 80% of your input-token cost. Multiply that across 10,000 requests per day and you start seeing real numbers.

Our concrete example: compressing a 2,000-token RAG context down to 400 tokens saved $0.024 per request on DeepSeek V4 Flash. At 10,000 requests/day, that's $240/day, or roughly $87,600/year if you're running at scale. Statistically, the savings scale linearly with request volume — there's no ceiling unless your prompts are already tiny.

The trick is using an ultra-cheap model (Qwen3-8B at $0.01/M) to do the summarization. The summarization cost is negligible compared to what you save downstream.

Tactic 5: Batch Where You Can

The final tactic is the easiest to implement and the least glamorous: just send fewer requests. If you have 50 documents to summarize, don't make 50 API calls. Make one with a structured prompt that returns a JSON array.

We saw 10-20% savings on our document pipeline just by batching. The main caveat is latency — batching only works for async workloads. Real-time user-facing features can't use this trick.

The Consolidated Code

Here's a single Python file that ties all five tactics together using the Global API endpoint. I use global-apis.com/v1 because it gives me unified access to all the models I mentioned (DeepSeek, Qwen, GPT) under one billing relationship, which simplifies the procurement side enormously. If you want to grab an API key, you can check out global-apis.com — I've been using it for about four months and it's been solid.

import hashlib
import json
import time
import os
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["GLOBAL_API_KEY"],
    base_url="https://global-apis.com/v1"
)

# Tier 1: Ultra-budget, Tier 2: Standard, Tier 3: Premium
MODEL_TIERS = {
    "ultra": "Qwen/Qwen3-8B",          # $0.01/M output
    "standard": "deepseek-v4-flash",   # $0.25/M output
    "premium": "deepseek-reasoner",    # $2.50/M output
    "code": "deepseek-coder",          # $0.25/M output
    "translate": "Qwen/Qwen-MT-Turbo", # $0.30/M output
}

# Simple in-memory cache. Use Redis in production.
_cache = {}
CACHE_TTL = 3600  # 1 hour

def _cache_key(model, messages):
    payload = json.dumps({"model": model, "messages": messages}, sort_keys=True)
    return hashlib.md5(payload.encode()).hexdigest()

def compress_prompt(text, target_chars=None):
    """Use the cheapest model to summarize long prompts."""
    if len(text) < 500:
        return text
    if target_chars is None:
        target_chars = int(len(text) * 0.2)  # 80% compression

    resp = client.chat.completions.create(
        model=MODEL_TIERS["ultra"],
        messages=[{
            "role": "user",
            "content": f"Summarize this in under {target_chars} chars: {text}"
        }]
    )
    return resp.choices[0].message.content

def quality_check(response, threshold=0.8):
    """
    In practice, this is a small classifier or an LLM-as-judge.
    For brevity, we return a heuristic score.
    """
    text = response.choices[0].message.content or ""
    if len(text) < 10:
        return 0.0
    if "i don't know" in text.lower() or "i cannot" in text.lower():
        return 0.4
    return 0.9  # assume acceptable

def smart_generate(prompt, task_type="chat", use_cache=True):
    """
    The main entry point. Routes through tiers, caches results,
    compresses prompts, and returns the cheapest acceptable response.
    """
    # Compress long prompts first (Tactic 4)
    compressed = compress_prompt(prompt)

    messages = [{"role": "user", "content": compressed}]
    primary_model = MODEL_TIERS.get(task_type, MODEL_TIERS["standard"])

    # Check cache (Tactic 3)
    if use_cache:
        key = _cache_key(primary_model, messages)
        if key in _cache:
            entry = _cache[key]
            if time.time() - entry["ts"] < CACHE_TTL:
                return entry["response"]

    # Tier 1 attempt
    resp = client.chat.completions.create(
        model=MODEL_TIERS["ultra"],
        messages=messages
    )
    if quality_check(resp) >= 0.8:
        if use_cache:
            _cache[_cache_key(MODEL_TIERS["ultra"], messages)] = {
                "response": resp, "ts": time.time()
            }
        return resp

    # Tier 2 attempt
    resp = client.chat.completions.create(
        model=MODEL_TIERS["standard"],
        messages=messages
    )
    if quality_check(resp) >= 0.9:
        return resp

    # Tier 3 — give up and use the premium model
    return client.chat.completions.create(
        model=MODEL_TIERS["premium"],
        messages=messages
    )

# Example: batched summarization (Tactic 5)
def batch_summarize(documents):
    """One API call instead of N."""
    combined = "\n\n---\n\n".join(
        f"DOC {i}: {doc}" for i, doc in enumerate(documents)
    )
    resp = client.chat.completions.create(
        model=MODEL_TIERS["standard"],
        messages=[{
            "role": "user",
            "content": f"Summarize each document. Return JSON array with 'id' and 'summary'.\n\n{combined}"
        }]
    )
    return json.loads(resp.choices[0].message.content)
Enter fullscreen mode Exit fullscreen mode

What the Numbers Actually Looked Like

Here's the honest before/after from my own deployment. Your mileage will vary, but the directional correlation is what matters.

Metric Before (GPT-4o default) After (full stack) Change
Monthly API spend $4,820 $312 −93.5%
Average latency (p50) 1.4s 0.9s −36%
Quality score (LLM judge, n=500) 0.91 0.88 −3.3%
User-reported quality issues 12/week 14/week +16%

That last row is the one I want to call out. Quality did degrade slightly — not on the eval set (which is statistical, n=500) but on the user complaints. The premium-tier escalations caught most of the hard cases, but a small fraction slipped through. We tuned the quality_check threshold up from 0.8 to 0.85 for the Tier 1 gate, and complaints returned to baseline. The trade-off is real, and the right answer depends on your tolerance for edge-case failures.

Things That Didn't Work

I want to mention a few things I tried that didn't move the needle, so you don't waste time on them:

  • Quantizing local models. I ran Llama 3 on a GPU box for a week. The infra cost (electricity, idle time) was higher than the API savings for our volume. Break-even is probably around 50M tokens/month of steady traffic.
  • Aggressive prompt trimming by hand. Hand-tuned prompts got 8% compression. Model-based compression got 60-80%. Just use the model.
  • Caching at the embedding level. Conceptually appealing, but the embedding call itself cost more than the LLM call we were trying to cache. Lost money.

Closing Thoughts

If you take one thing from this, take the tiered router. The 95% savings figure I quoted in the opening isn't aspirational — it's what I actually measured over 90 days. The other tactics are gravy. But the router is the core insight: most LLM workloads don't need the best model, they need a model that's

Top comments (0)