The Cheapest AI Model Isn't Always the Cheapest Model
Learn how to calculate the true cost of AI models including latency, retries, and token waste, with a script to compare providers.
The problem: your bill is higher than the sticker price
You picked the cheapest AI model on the price sheet. The API docs show $0.15 per million input tokens, and you pat yourself on the back for saving money. Then the invoice arrives and it is 40% higher than your estimate. What went wrong?
The sticker price per token is only the beginning. The real cost per successful request depends on how many tokens you send, how many times you retry after failures, how long the user waits, and how much output you generate before you get a usable answer. This article walks through a practical method to measure the true cost of an AI model in your specific workload, with a script you can run today.
Before you start: what you need
To follow along, you need a Python environment with the requests library, and API keys for at least two AI providers. The examples use OpenAI and Anthropic, but the script works with any provider that exposes a chat completions endpoint.
You also need a realistic sample of your production prompts. If you do not have one, the script includes a synthetic workload that mimics a typical customer-support bot.
- Python 3.9 or newer
- pip install requests
- API keys for OpenAI and Anthropic (or any two providers)
- A text file with 10-20 real prompts, one per line
Step 1: Measure your actual token usage
The first mistake is assuming the token count from a rough estimate. Tokenizers vary by model, and your prompt may include system instructions, few-shot examples, and tool definitions that inflate the count.
Write a small script that sends each prompt to the API and records the prompt tokens, completion tokens, and total latency. Use the same prompt for both models so the comparison is fair.
import requests
import time
import json
# Replace with your own keys and model IDs
OPENAI_KEY = "sk-your-key"
ANTHROPIC_KEY = "sk-ant-your-key"
OPENAI_MODEL = "gpt-4o-mini"
ANTHROPIC_MODEL = "claude-3-5-haiku-latest"
PROMPTS = [
"How do I reset my password?",
"What is the status of my order #12345?",
"Tell me a joke about databases.",
]
def call_openai(prompt):
url = "https://api.openai.com/v1/chat/completions"
headers = {"Authorization": f"Bearer {OPENAI_KEY}"}
payload = {
"model": OPENAI_MODEL,
"messages": [{"role": "user", "content": prompt}],
"max_tokens": 200,
}
start = time.time()
resp = requests.post(url, headers=headers, json=payload)
latency = time.time() - start
data = resp.json()
return data["usage"]["prompt_tokens"], data["usage"]["completion_tokens"], latency
def call_anthropic(prompt):
url = "https://api.anthropic.com/v1/messages"
headers = {
"x-api-key": ANTHROPIC_KEY,
"anthropic-version": "2023-06-01",
"content-type": "application/json",
}
payload = {
"model": ANTHROPIC_MODEL,
"max_tokens": 200,
"messages": [{"role": "user", "content": prompt}],
}
start = time.time()
resp = requests.post(url, headers=headers, json=payload)
latency = time.time() - start
data = resp.json()
return data["usage"]["input_tokens"], data["usage"]["output_tokens"], latency
for prompt in PROMPTS:
o_prompt, o_comp, o_lat = call_openai(prompt)
a_prompt, a_comp, a_lat = call_anthropic(prompt)
print(f"Prompt: {prompt[:30]}...")
print(f" OpenAI: prompt={o_prompt} tokens, completion={o_comp} tokens, latency={o_lat:.2f}s")
print(f" Anthropic: prompt={a_prompt} tokens, completion={a_comp} tokens, latency={a_lat:.2f}s")Step 2: Calculate the effective cost per request
Now that you have real token counts, compute the cost per request using the published pricing. Remember that output tokens are usually more expensive than input tokens.
The script below reads the usage data from Step 1 and prints the cost per request. Run it with your own numbers.
# Pricing per 1M tokens (as of mid-2025, check current rates)
PRICING = {
"gpt-4o-mini": {"input": 0.15, "output": 0.60},
"claude-3-5-haiku-latest": {"input": 0.80, "output": 4.00},
}
def cost_per_request(model, prompt_tokens, completion_tokens):
p = PRICING[model]
return (prompt_tokens / 1_000_000) * p["input"] + (completion_tokens / 1_000_000) * p["output"]
# Example usage from Step 1
openai_cost = cost_per_request("gpt-4o-mini", 150, 80)
anthropic_cost = cost_per_request("claude-3-5-haiku-latest", 200, 120)
print(f"OpenAI cost per request: ${openai_cost:.6f}")
print(f"Anthropic cost per request: ${anthropic_cost:.6f}")Step 3: Factor in retries and failures
Real workloads have errors. Rate limits, timeouts, and content filters cause retries. Each retry sends the same prompt again, doubling the cost for that request.
Add a retry counter to your script. The script below simulates a 5% failure rate and shows how the effective cost per successful request rises.
import random
def effective_cost(base_cost, failure_rate, retries=3):
"""Return expected cost per successful request given failure rate."""
expected_attempts = 0
prob_success = 1 - failure_rate
for i in range(retries):
expected_attempts += prob_success * (failure_rate ** i) * (i + 1)
# last retry always succeeds (or gives up)
expected_attempts += (failure_rate ** retries) * retries
return base_cost * expected_attempts
# Example: base cost $0.001, 5% failure rate
base = 0.001
for rate in [0.01, 0.05, 0.10]:
eff = effective_cost(base, rate)
print(f"Failure rate {rate:.0%}: effective cost ${eff:.6f}")Step 4: Include latency and user experience
A cheap model that takes 5 seconds to respond might drive users away. If your app has a timeout of 3 seconds, a slow model causes failed requests and more retries.
Measure the latency distribution. The script below runs each prompt 10 times and prints the 95th percentile latency. Use that to decide if the model meets your SLA.
import statistics
def percentile(data, p):
sorted_data = sorted(data)
k = (len(sorted_data) - 1) * p
f = int(k)
c = f + 1
return sorted_data[f] + (c - f) * (sorted_data[c] if c < len(sorted_data) else sorted_data[f])
latencies = [1.2, 1.5, 2.0, 2.3, 2.8, 3.1, 3.5, 4.0, 4.5, 5.0]
print(f"Median: {statistics.median(latencies):.2f}s")
print(f"95th percentile: {percentile(latencies, 0.95):.2f}s")Step 5: Compare total cost of ownership
Combine all factors into a single number: cost per successful request, including retries and latency penalties. The script below takes your measurements and prints a comparison table.
def total_cost(model, prompt_tokens, completion_tokens, failure_rate, latency, timeout=3):
base = cost_per_request(model, prompt_tokens, completion_tokens)
eff = effective_cost(base, failure_rate)
# Penalty for latency exceeding timeout: assume 50% failure
if latency > timeout:
eff *= 1.5
return eff
# Example data
models = [
{"name": "gpt-4o-mini", "prompt": 150, "completion": 80, "failure": 0.02, "latency": 1.5},
{"name": "claude-3-5-haiku-latest", "prompt": 200, "completion": 120, "failure": 0.05, "latency": 3.5},
]
for m in models:
cost = total_cost(m["name"], m["prompt"], m["completion"], m["failure"], m["latency"])
print(f"{m['name']}: ${cost:.6f} per successful request")Recommended setup: a cost-aware router
Instead of picking one model for everything, route requests based on complexity. Use a cheap model for simple queries and a stronger model for hard ones. The config below shows a simple routing rule using environment variables.
# .env
OPENAI_API_KEY=sk-...
ANTHROPIC_API_KEY=sk-ant-...
# Route simple queries to gpt-4o-mini, complex to claude-3-5-sonnet
if [ "$QUERY_COMPLEXITY" = "simple" ]; then
export MODEL="gpt-4o-mini"
else
export MODEL="claude-3-5-sonnet-latest"
fiWhat I would do: a copy-paste starter script
Here is a complete script that measures cost, latency, and retries for any two models. Save it as cost_compare.py and run it with your API keys.
import requests
import time
import sys
OPENAI_KEY = sys.argv[1]
ANTHROPIC_KEY = sys.argv[2]
PROMPTS = [
"What is the weather in London?",
"Write a haiku about debugging.",
"Summarize the plot of The Matrix.",
]
def measure_openai():
url = "https://api.openai.com/v1/chat/completions"
headers = {"Authorization": f"Bearer {OPENAI_KEY}"}
total_cost = 0
total_latency = 0
failures = 0
for prompt in PROMPTS:
payload = {
"model": "gpt-4o-mini",
"messages": [{"role": "user", "content": prompt}],
"max_tokens": 100,
}
start = time.time()
try:
resp = requests.post(url, headers=headers, json=payload, timeout=10)
resp.raise_for_status()
data = resp.json()
prompt_tokens = data["usage"]["prompt_tokens"]
completion_tokens = data["usage"]["completion_tokens"]
total_cost += (prompt_tokens / 1e6) * 0.15 + (completion_tokens / 1e6) * 0.60
total_latency += time.time() - start
except Exception:
failures += 1
return total_cost, total_latency, failures
def measure_anthropic():
url = "https://api.anthropic.com/v1/messages"
headers = {
"x-api-key": ANTHROPIC_KEY,
"anthropic-version": "2023-06-01",
"content-type": "application/json",
}
total_cost = 0
total_latency = 0
failures = 0
for prompt in PROMPTS:
payload = {
"model": "claude-3-5-haiku-latest",
"max_tokens": 100,
"messages": [{"role": "user", "content": prompt}],
}
start = time.time()
try:
resp = requests.post(url, headers=headers, json=payload, timeout=10)
resp.raise_for_status()
data = resp.json()
input_tokens = data["usage"]["input_tokens"]
output_tokens = data["usage"]["output_tokens"]
total_cost += (input_tokens / 1e6) * 0.80 + (output_tokens / 1e6) * 4.00
total_latency += time.time() - start
except Exception:
failures += 1
return total_cost, total_latency, failures
openai_cost, openai_latency, openai_fail = measure_openai()
anthropic_cost, anthropic_latency, anthropic_fail = measure_anthropic()
print("OpenAI: cost=${:.4f}, latency={:.2f}s, failures={}".format(openai_cost, openai_latency, openai_fail))
print("Anthropic: cost=${:.4f}, latency={:.2f}s, failures={}".format(anthropic_cost, anthropic_latency, anthropic_fail))Checklist: choose the right model for your workload
Before you commit to a model, run through this checklist.
- Measure real token usage on your actual prompts, not estimates.
- Include output tokens in your cost calculation; they are often 2-4x the input price.
- Track failure rates and retries; a 5% failure rate adds 5% to your effective cost.
- Check latency percentiles against your SLA; slow models cause timeouts and user churn.
- Consider batching requests if you have non-interactive workloads.
- Use a router to send easy queries to cheap models and hard ones to expensive models.
- Monitor costs monthly and re-evaluate as pricing changes.
FAQ
Answers to the questions that come up most often on this topic.
- Q: Why is my actual token count higher than expected? A: System prompts, tool definitions, and chat history all consume tokens. Use the API usage field to get exact counts.
- Q: How do I handle rate limits? A: Implement exponential backoff and retries, but cap the number of retries to avoid runaway costs.
- Q: Should I always use the cheapest model? A: Not if it causes high failure rates or poor user experience. Calculate the effective cost per successful request.
- Q: Can I use multiple providers? A: Yes, a router can send requests based on complexity or cost. Use the script above to compare.
- Q: How often should I re-evaluate? A: Pricing changes frequently. Check monthly and rerun the comparison when you update your prompts.
Next action
Run the cost_compare.py script with your own prompts and API keys. It will give you a clear picture of which model is truly cheaper for your workload. Then adjust your routing or model choice accordingly.
Key takeaways
- Apply one concrete change from this post before collecting more reading.
- Prefer browser-side tools when the work involves secrets, tokens, or PII.
- Document the why next to the how so the next reviewer inherits context.
FAQ
- Who is this guide on ai for?
- Working developers who need a practical take on the cheapest ai model isn't always the cheapest model — not a marketing overview. Skim the sections, apply one tip, then come back when you hit an edge case.
- Do I need an account to use the related tools?
- No. code.live tools run in your browser with no signup. Nothing you paste is uploaded to a server for the client-side utilities linked from this post.
- How often is this article updated?
- This post was published August 21, 2026. Fundamentals stay stable; check linked tool pages and official docs when version-specific behavior matters.