Picking the Right Model: A Practical Decision Framework
Hands-on techniques for testing and comparing Claude models against your use case, so you can make a confident call each time a new release ships.
This lesson is original educational writing based on this video by Anthropic (published May 21, 2026). All credit for the original content goes to the creators.
1. The model selection problem
Every time Anthropic ships a new model, developers face the same question: which one should I use? The instinct is often to reach for the most capable model β but that instinct is expensive, and frequently unnecessary.
The right framing is not βwhich model is best?β but βwhich model is best for this task?β. That distinction changes everything, because the answer depends entirely on:
- What quality looks like for your specific output
- How many tokens you process per day
- How much latency your users will tolerate
- What budget you have for inference
A model that is overkill for a classification task is exactly right for a multi-step reasoning task. A model fast enough for an interactive chatbot may be too slow for a synchronous API response in a checkout flow. Picking well means understanding both the task and the model family.
The Claude model family
As of mid-2026, Claude is organised into three capability tiers, each available as a point
release (e.g., claude-haiku-4-5, claude-sonnet-4-6, claude-opus-4):
- Haiku β fastest, cheapest. Optimised for high-volume, latency-sensitive work where the task is well-defined: classification, extraction, routing, summarisation of short texts.
- Sonnet β balanced. The daily-driver for the majority of production workloads: code generation, multi-turn chat, document analysis, moderate-length reasoning.
- Opus β most capable, highest cost, higher latency. Reserved for tasks where quality is the only variable that matters: complex research, long-horizon agentic work, tasks that require deep multi-step reasoning.
2. What actually drives model choice
The selector between model tiers is not βdifficultyβ in the abstract β it is a set of measurable task properties. Learn to read these signals in your own workload:
Signal 1: Output sensitivity
Ask: βHow bad is a wrong answer?β For a spam classifier, a wrong label costs almost nothing β you review flagged items anyway. For a contract clause analyzer, a wrong interpretation could carry real risk. High-sensitivity outputs push toward stronger models and always require an evaluation harness.
Signal 2: Reasoning depth
Count the number of independent reasoning steps the task requires. A sentiment label is one step. A strategy recommendation that must weigh competing constraints across a long document is ten. Tasks that require composing many sub-conclusions into a final judgment benefit most from Opusβs deeper reasoning.
Signal 3: Context length and coherence
Long-context tasks β summarising a 200-page report, reviewing a large codebase, multi-session research β stress working memory. At long contexts, quality gaps between model tiers widen. If your 90th-percentile request sends 50K+ tokens, test all tiers at that length, not at 2K.
Signal 4: Throughput and budget
Multiply your expected daily request volume by your average prompt + completion tokens, then by the per-token price for each tier. For high-volume pipelines, the cost difference between Haiku and Sonnet can be 10β20Γ. That spread often funds significant engineering work to make a smaller model perform adequately.
Signal 5: Latency budget
Streaming helps time-to-first-token feel fast, but total generation time matters for synchronous calls. Haiku generates tokens at roughly 3β5Γ the speed of Opus in practice. If users are waiting for a UI to unblock, measure end-to-end latency for your 95th-percentile token count on each tier.
Check your understanding
2 questions Β· your answers are saved in this browser only
-
1. A pipeline classifies incoming support tickets into one of 8 categories and routes them to a queue. It processes 200,000 tickets per day. Which model tier is the best starting point?
-
2. Which task characteristic most strongly justifies using Opus over Sonnet?
3. Building a model comparison harness
The only way to make a confident model selection is to measure, not guess. You need a comparison harness: a script that runs the same inputs through multiple model tiers and produces comparable outputs you can score.
The three-layer evaluation stack
A minimal harness has three layers:
-
Golden dataset β 20β50 representative inputs sampled from your real distribution. Include edge cases and the hardest examples you know about. Do not cherry-pick easy inputs; you need your worst-case to be covered.
-
Automated metrics β whatever you can compute without human review: exact-match on classification labels, ROUGE or embedding similarity for summarization, JSON schema validation for structured outputs, unit tests for generated code. These give you fast signal on most failures.
-
Human review sample β 10β20 outputs per model tier, reviewed by someone who knows what βgoodβ looks like. Focus reviewers on the cases where automated metrics diverge between tiers.
Running the comparison
import anthropic
import json
client = anthropic.Anthropic()
MODELS = {
"haiku": "claude-haiku-4-5",
"sonnet": "claude-sonnet-4-6",
"opus": "claude-opus-4",
}
def run_on_model(model_id: str, system: str, user: str) -> dict:
resp = client.messages.create(
model=model_id,
max_tokens=1024,
system=system,
messages=[{"role": "user", "content": user}],
)
return {
"model": model_id,
"output": resp.content[0].text,
"input_tokens": resp.usage.input_tokens,
"output_tokens": resp.usage.output_tokens,
}
# Run every golden-set example across all tiers
results = []
for example in golden_dataset:
for tier, model_id in MODELS.items():
result = run_on_model(model_id, SYSTEM_PROMPT, example["input"])
result["tier"] = tier
result["example_id"] = example["id"]
results.append(result)
with open("comparison_results.json", "w") as f:
json.dump(results, f, indent=2)
Once you have the results file, score each output against your golden answers using your automated metrics, compute per-tier aggregate scores, and calculate the cost for each tier at your production volume. The decision matrix usually becomes obvious.
4. Reading the cost model
After your comparison run, you have three numbers per tier: quality score, average latency, and token usage. Combine them with pricing to build a decision matrix:
| Tier | Quality score | p95 latency | Daily cost (est.) |
|---|---|---|---|
| Haiku | 87% | 0.8 s | $12 |
| Sonnet | 94% | 1.9 s | $85 |
| Opus | 97% | 4.1 s | $420 |
In this fictional example, Sonnet buys a 7-point quality gain over Haiku for 7Γ the cost, while Opus buys only 3 more points for 5Γ the cost again. The marginal return on Opus is poor β unless those 3 points are the difference between acceptable and unacceptable for your use case.
The upgrade threshold
Use this rule of thumb: upgrade when the quality gap between tiers is larger than the noise in your evaluation. If your eval has Β±3% variance (typical for a 20-example golden set), a 2-point quality difference between tiers is not a real signal β the difference could flip on the next run. A 10-point difference is real and worth paying for.
Check your understanding
2 questions Β· your answers are saved in this browser only
-
1. Your eval shows Sonnet scores 91% and Opus scores 93% on your golden dataset of 20 examples. What should you do?
-
2. When building a model comparison harness, what is the purpose of including "edge cases and the hardest examples" in your golden dataset?
5. Model selection in agentic systems
Single-call tasks are the easy case. Agentic systems β where a model runs for many steps, calls tools, and accumulates a long context β add two complications:
Error propagation
In a multi-step agent, an early reasoning mistake compounds. A misread of step 2 leads to a wrong tool call at step 4, which surfaces as a completely wrong final answer. Stronger models make fewer early errors, so quality gaps are amplified in agentic pipelines compared to single-call tasks. This is why agentic orchestrators tend to use Sonnet or Opus even when individual steps seem simple.
Cost per task, not cost per call
A 10-step agentic task that calls the model 10 times with accumulating context costs 10Γ per run. At Opus prices, a task that feels inexpensive in a demo can become very expensive at scale. Common patterns:
- Orchestrator / subagent split: use Sonnet or Opus for the orchestrator (complex planning), Haiku for cheap subagents (web search parsing, data extraction, formatting).
- Speculative Haiku: try Haiku first; if the output fails a validation step, retry with Sonnet. This wins when most inputs are easy but a long tail is genuinely hard.
- Extended thinking on hard steps only: enable extended thinking for the one or two steps that require deep reasoning; use standard generation for the rest.
Check your understanding
1 question Β· your answers are saved in this browser only
-
1. Why do quality gaps between model tiers tend to be larger in agentic pipelines than in single-call tasks?
6. Staying current as models evolve
Anthropic ships model updates regularly. A selection you made six months ago may not be optimal today β in either direction. A newer Sonnet release might outperform the older Opus on your workload; a new Haiku might close the gap on tasks where you previously needed Sonnet.
When to re-evaluate
Trigger a re-evaluation whenever:
- A new point release ships for any tier you use
- Your workload composition changes significantly (e.g., average prompt length doubles)
- Your quality requirements change (e.g., you expand to a new use case)
- Your cost constraints change (e.g., you scale to 10Γ volume)
Keep your eval harness alive
The comparison harness you built in Chapter 3 is not a one-time script β it is a test suite for your model selection. Run it against new model versions the same way you run unit tests against new library versions. A harness that takes 5 minutes to run and costs $2 in API credits will save you far more in avoided mistakes.
Build it yourself
Follow these exact steps to reproduce it yourself Β· estimated time: ~15 min
Prerequisites
- Python 3.10+
- Anthropic API key (console.anthropic.com)
- A task you want to optimise β even a toy classification or summarization job works
You will build a minimal model selection harness: a golden dataset, a runner, a cost calculator, and a decision matrix. By the end you can make a data-driven model choice for any workload.
Step 1 β Set up the environment
mkdir model-selector && cd model-selector
python3 -m venv .venv && source .venv/bin/activate
pip install anthropic
export ANTHROPIC_API_KEY="sk-ant-..."Step 2 β Create your golden dataset
Create golden.json with 20 examples. Each entry needs an id, an input, and an
expected answer (the ground truth you will score against):
[
{
"id": "ex-001",
"input": "Classify: 'I love this product, works perfectly!'",
"expected": "positive"
},
{
"id": "ex-002",
"input": "Classify: 'Broke after two days, total waste of money.'",
"expected": "negative"
}
]Add at least 5 genuinely ambiguous or hard examples β these are the ones that will distinguish the model tiers.
Step 3 β Write the comparison runner
Create compare.py:
import json, time, anthropic
client = anthropic.Anthropic()
MODELS = {
"haiku": "claude-haiku-4-5",
"sonnet": "claude-sonnet-4-6",
"opus": "claude-opus-4",
}
SYSTEM = "Classify the sentiment of the following text. Reply with exactly one word: positive, negative, or neutral."
with open("golden.json") as f:
golden = json.load(f)
results = []
for example in golden:
for tier, model_id in MODELS.items():
t0 = time.time()
resp = client.messages.create(
model=model_id,
max_tokens=10,
system=SYSTEM,
messages=[{"role": "user", "content": example["input"]}],
)
latency = time.time() - t0
output = resp.content[0].text.strip().lower()
results.append({
"id": example["id"],
"tier": tier,
"model": model_id,
"output": output,
"expected": example["expected"],
"correct": output == example["expected"],
"latency_s": round(latency, 2),
"input_tokens": resp.usage.input_tokens,
"output_tokens": resp.usage.output_tokens,
})
print(f"{tier:8s} {example['id']} β {output!r:12s} {'β' if output == example['expected'] else 'β'}")
with open("results.json", "w") as f:
json.dump(results, f, indent=2)
print("Saved results.json")Step 4 β Score and cost
Create score.py to aggregate results and estimate daily cost:
import json
from collections import defaultdict
# Pricing in USD per million tokens (adjust to current rates)
PRICING = {
"haiku": {"input": 0.80, "output": 4.00},
"sonnet": {"input": 3.00, "output": 15.00},
"opus": {"input": 15.00, "output": 75.00},
}
DAILY_REQUESTS = 10_000 # your expected daily volume
with open("results.json") as f:
results = json.load(f)
by_tier = defaultdict(list)
for r in results:
by_tier[r["tier"]].append(r)
print(f"\n{'Tier':8s} {'Accuracy':>10s} {'Avg latency':>13s} {'Est. daily cost':>17s}")
print("-" * 54)
for tier in ["haiku", "sonnet", "opus"]:
rows = by_tier[tier]
accuracy = sum(r["correct"] for r in rows) / len(rows) * 100
avg_latency = sum(r["latency_s"] for r in rows) / len(rows)
avg_in = sum(r["input_tokens"] for r in rows) / len(rows)
avg_out = sum(r["output_tokens"] for r in rows) / len(rows)
p = PRICING[tier]
daily_cost = DAILY_REQUESTS * (avg_in * p["input"] + avg_out * p["output"]) / 1_000_000
print(f"{tier:8s} {accuracy:>9.1f}% {avg_latency:>11.2f}s ${daily_cost:>15.2f}")Step 5 β Make your decision
Look at the accuracy column first. If Haiku meets your quality bar, youβre done β pick Haiku and save the money. If Haiku falls short but Sonnet doesnβt, pick Sonnet. Move to Opus only if the quality gap between Sonnet and Opus is larger than your eval noise and the extra cost is justified by your business impact.
Document the winning model, the date, and the accuracy you measured β youβll need this baseline when the next model release ships.
Step 6 β Schedule a re-evaluation
Create a calendar reminder or CI cron job to re-run compare.py whenever Anthropic ships a new
model version. The harness takes minutes to run and keeps your model selection current without
manual research.
# Example: add to CI as a monthly scheduled job
# on: schedule: - cron: '0 9 1 * *'
python3 compare.py && python3 score.py