Decisions in AI Systems
Suppose you switch an agent to a cheaper model to cut costs, and it gets more expensive. I built the agent and measured when that happens — and the measurement corrected my own explanation.
The number that lies
In an agentic loop you don't pay per token. You pay per completed task, and the two move in opposite directions often enough to be dangerous.
A cheaper model at the orchestrator — the node that plans, picks a tool, reads the result and decides again — takes more turns to get there, and fails more often. Both effects land on the same bill. In the worked example below, a model 4× cheaper per token comes out 16% more expensive per successful task, while a cost dashboard, which counts calls rather than outcomes, reports a 23% saving.
Then I put prompt caching into the cost model, and the answer flipped.
Most APIs now bill re-sent context at a steep discount, and almost everything an agent turn sends was sent the turn before. With cache reads at a tenth of the normal price, the cheaper model turns into a 27% saving per successful task. What doesn't change: it still fails 40% of tasks against 10%, and still makes seven round trips against three.
So the honest claim is narrower than "cheap models cost more", and more useful:
A cheaper model does not automatically reduce the cost of an agentic system, and a more capable one doesn't automatically improve it. Which case you're in depends on turns, success rate, caching and latency — and you only learn those by measuring.
Why the loop punishes a weaker model
Three multipliers sit between the price per token and the price per task.
Turn inflation. A weaker orchestrator picks a plausible but wrong tool, passes an argument in the wrong shape, reads the error and re-plans. Each misstep is at least one more turn.
Context growth. Chat APIs are stateless, so every turn re-sends the whole conversation: system prompt, tool definitions, and every call and result so far. Tokens grow with the square of the turns, and the extra turns are the expensive ones because they come last.
Failure. Cost per successful task is cost per attempt divided by the success rate, so a weaker model's bill gets divided by a smaller number.
Together they give one rule you can apply before committing to a swap. A cheaper model is only cheaper per successful task if price ratio > token ratio × success ratio. The pricing page gives you the first. Only an evaluation gives you the other two.
What an evaluation has to cover
"Run an eval" is easy to say and easy to do badly. A single prompt run once measures one sample of a random process. A benchmark measures someone else's tasks. A cost dashboard measures calls, not outcomes.
An agent conversation can be graded at five levels, and each one answers a question the others can't.
Every level gets measured on the same three axes, always per completed task rather than per call: accuracy, latency and cost. Two of those deserve emphasis, because they're the ones teams skip:
- Reliability, not just success. Models sample, so run each case k times.
pass^k— every one of k runs succeeding — is the number that matters for an agent acting on a user's behalf, because every user gets one run. An agent that's right 90% of the time is right five times running only 59% of the time. - Latency per task and at the tail. Seven fast round trips can lose to three slow ones, and a median hides it. In the experiment below the median said the cheap model was faster while the time per successful task was three times worse.
Then decide with gates, in order: quality, then latency, then cost. A configuration that fails the quality bar is out, however cheap it is. Cost only ranks what survives. A weighted score sounds more balanced, but it lets a precise cost number outvote a noisy quality one.
A test subject, and two runs of 240 measured conversations
To keep this from being an argument about arithmetic, I built the agent it describes and evaluated it: a customer-support agent for a made-up online store, with 8 tools, 4 policy rules, and 24 graded conversations including multi-turn ones where the customer changes their mind. Grading is deterministic, on the store's end state rather than on wording.
The headline comes from the second run. With the graders frozen first, documenting every tool parameter doubled the 3B agent's task success (20% → 41%) and cut the price advantage it needs to break even from 4.1× to 1.5× (95% CI 1.0–2.2×), still above the router's 1.1×. The first run comes first here, because it's what showed where to look.
Every conversation ran five times on each of two sizes of the same open model family, locally: 120 conversations per model, plus a separate 48-message set for the leaf node below. The first run took 56 minutes on one consumer GPU and cost nothing.
In the first run the two models are indistinguishable on the metrics a dashboard shows: 4.3 model calls per conversation each, about 11 seconds of model time each (most of it a fixed ~2 s per call in the harness, which flatters the slower model). Per outcome, the 3B needed 4.1× cheaper tokens just to break even (95% CI 2.4–7.5× over 24 tasks), and it never gets that far, because it fails the quality gate first: 20% of tasks against 62.5%.
Pricing prompt caching into the measured traces doesn't rescue it either — break-even moves to 4.24×, slightly wider. Caching flips the worked example above but not the real one, because the measured gap comes from the success rate, not from context growth.
The mechanism was also not quite the one I predicted, which is the best argument for measuring rather than reasoning. Model calls per conversation were identical. The extra cost came from rejected tool calls — 3.14 per conversation against 0.04 — pushing errors into a context that every later call re-sends, and from the small model giving up rather than looping: answering from a failed lookup, inventing a SKU, or quitting after one error. In 18% of conversations it hit the call cap and never answered at all.
Then the same two models on a separate leaf-node task in the same domain — an intent router: one call, fixed output, no loop:
| Intent router | 3B | 7B |
|---|---|---|
| Accuracy (95% CI) | 87% (80–93%) | 98% (94–100%) |
| Tokens per correct call | 149 | 132 |
| Latency per call (about 2 s fixed overhead) | 2.25 s | 2.37 s |
Break-even collapses from 4.1× to 1.13× (95% CI 1.04–1.24×). Almost the entire price gap passes straight through, and on cost the small model wins comfortably. Whether you'd ship it is a quality question about what happens after a misroute, not a cost one.
Same two models, two kinds of node, opposite answers. Which is the whole argument.
The second run: document the tools
About a third of those rejected calls had a plain cause: only one of the agent's 13 tool parameters had a description, and 117 of the small model's 377 rejected calls copied a parameter's schema into the argument. So the second run described all 13, each with an example value that isn't in the store. It also moved the router to temperature 0 and put it in front of the agent, and logged Ollama's own timings. The two models, the 24 tasks and the seeds stayed the same, and the graders were frozen and committed before the first trace.
| First run | Second run | 95% CI | |
|---|---|---|---|
| 3B task success | 20% | 40.8% | 27–55% |
| 7B task success | 62.5% | 68.3% | 52–83% |
| All 5 runs succeed, 3B / 7B | 4% / 50% | 12% / 54% | |
| Rejected tool calls per conversation, 3B | 3.14 | 1.43 | |
| Calls that copied a schema into the argument, 3B | 117 | 0 | |
| Break-even, orchestrator | 4.10× | 1.47× | 1.01–2.16× |
| Break-even with the re-sent prefix at 10% | 4.24× | 1.74× | 1.26–2.51× |
| Break-even, router | 1.13× | 1.09× | 1.02–1.21× |
The gap narrowed and the ordering held: the orchestrator is still further from parity than the router, and the quality gate still decides first, at 41% against 68%. The whole pipeline, router included, also breaks even at 1.47×, because the router is only about 3% of its tokens.
Several setup changes went in together, so this run doesn't say how much each contributed. The parameter docs are the most likely driver, since the schema-copying calls went to zero, but the router's label entered the context in the same run. Separating them is the next test. The fixed 2 seconds per call also turned out to be the harness: measured against Ollama's own timings, request overhead is 0.03 s.
The same argument as a product decision
The companion piece is the non-quantitative version: setting the bar for a model change on an internal knowledge product, where accuracy gates before cost is considered at all, rather than the two being weighed together.
Weighing them sounds balanced. It's also how teams talk themselves into a worse product, because the cost figure is precise, the quality figure is noisy, and the precise number wins the argument regardless of which one matters.
Reproducing it
Everything is public and runs on a laptop. The cost model needs only the standard library; the experiment needs Ollama and two free local models. Every figure on this page is generated by the same code that produced the numbers, and the raw traces for all 480 conversations across both runs are in the repository.
python cost_model.py # every illustrative number, including the caching case
cd experiment && python analyze.py # recompute the first run from the published traces
EVAL_RESULTS=results-run2 python analyze.py # the same for the second run
Both write-ups describe how decisions were made rather than the systems behind them: no proprietary architecture, no client details, and worked examples that say where they're illustrative.