Tokens and cost
Send a real request to a model from the provider of your choice, then make the surrounding program trustworthy: constrain the output, validate it, handle streaming and failures, and keep cost bounded.
THE IDEA
Tokens in, tokens out, two prices
Providers meter text in tokens (word pieces, roughly four characters of English each) and bill input and output separately, usually per million tokens. Output is often priced higher than input. The cost of one call is
cost = (input_tokens * price_in + output_tokens * price_out) / 1,000,000The response reports the exact counts, so you never need to guess after the fact. A batch multiplies the one-call cost by the number of calls.
The default prices (2 and 6) are invented arithmetic inputs. Read real prices from your provider's pricing page.
| One call | $0.0048 |
| 10,000 calls at the typical reply | $48.00 |
| Worst case if every reply reaches the cap of 800 | $72.00 |
Expected attempts per task: 1.24 · batch cost with re-asks: $59.52. Every reply that is generated and then thrown away is still paid for.
Bound the worst case with the output cap
Input size is under your control before you send. Output size is not, except through the cap. If every reply ran to the cap, the output side of a batch costs calls x cap x price_out / 1,000,000. That number is the most the output side can cost, so it is the right one to compare with a budget.
Predict first. With 1,000 input and 500 output tokens, do equal prices per million for input and output mean equal spend on each side?
Chapter context
A model API turns input messages into generated output, but the program around it stays responsible for validation, retries and side effects. Every major provider (Anthropic, OpenAI, Google, Mistral, Groq, OpenRouter and local OpenAI-compatible servers) exposes the same few ideas under different field names: a model id, a list of messages with optional system instructions, an output-token limit, and a response carrying the text, a reason it stopped, and token usage. The labs in this chapter run against whichever provider you choose with your own key, so you see real behavior rather than a mock.
TRY IT YOURSELF
Part 1. Price a batch and cap the worst case
Look up two prices for any model on its pricing page (or invent two for practice) and enter them. This lab uses your last real request as its usage if you made one, and a sample otherwise.
TRY IT YOURSELF
Price a call, then cap the worst case
Take a usage report (yours from the last real request, or a sample) and turn it into money with prices from your provider's pricing page. Then choose an output cap that bounds a whole batch.
Part 1: the cost of 10,000 identical calls. Part 2: an output cap that keeps the worst-case total within the budget shown.
Usage to price: 1200 input + 400 output tokens per call (sample usage. Make a real request in an earlier lesson to use your own).
The calculation as code
PRICE_IN, PRICE_OUT = <in>, <out> # USD per million tokens (Price table)
def cost(input_tokens, output_tokens):
return (input_tokens * PRICE_IN + output_tokens * PRICE_OUT) / 1_000_000
per_call = cost(1200, 400) # usage from the response
batch = per_call * 10000 # Part 1
worst_case = cost(1200, 400) * 10000 # Part 2: every reply hits the output capUpdate the controls above and this code changes with them. Run it yourself with the key in an environment variable.Part 2 goal: keep the worst case (every reply hits the cap) at or under the budget (enter prices first).
Part 2. Code: price a batch and bound the worst case
- Prices are USD per million tokens. Write
cost(input_tokens, output_tokens, price_in, price_out)for one call. worst_case(calls, input_tokens, max_output, price_in, price_out)is the batch total if every reply runs all the way to the output cap.max_calls(budget, input_tokens, output_tokens, price_in, price_out)is how many whole calls fit in a budget.max_output_cap(budget, calls, input_tokens, price_in, price_out)is the largest whole-number output cap whose worst case stays within the budget, or 0 if even the input alone does not fit.
Both parts must be solved to finish this lesson.
Stuck, or something not working? You can come back to it any time.
CHECK YOUR UNDERSTANDING
With 1,000 input and 500 output tokens, do equal prices per million for input and output mean equal spend on each side?
Chapter reference: math, worked example & code
This section belongs to the whole chapter, so it is the same on every lesson in it and may cover a different lesson's topic. The code for the experiment you just ran is inside the experiment itself.
Math and notation
Four small rules recur through the chapter.
- Complete or cut off (F8.1): treat the reply as an answer only when the provider's stop reason means a natural stop (
end_turn,stop,STOP), not a limit (max_tokens,length,MAX_TOKENS). - Validation order (F8.2-F8.4): parse, then check fields, then check domain rules. Passing one stage says nothing about the next.
- Backoff (F8.5): wait before retry , where is the base delay and the jitter factor is a random number in (or without jitter). Retry only temporary errors, and stop after a fixed number of retries.
- Cost (F8.6): for prices and per million input and output tokens, and token counts and :
With an output cap , the worst-case output side for calls is . The prices below are invented arithmetic inputs, not a product recommendation; use the provider's current price list for real budgeting.
Worked example
One small helper per idea, all checked offline. A reply stopped with end_turn is usable but one stopped with max_tokens is not. The text {"sentiment": "neutral", "confidence": 0.7} passes all three validation stages, while a confidence of 7 or a reply with chatty text around the JSON is rejected. A 429 is retried but a 401 is not, and the delays for retries 0, 1 and 2 at a 0.5 s base are 0.5, 1.0 and 2.0 seconds. At illustrative prices of 6 per million tokens, a call with 1,000 input and 500 output tokens costs (1000×2 + 500×6)/1,000,000 = $0.005.
Reference code
The following example uses the Python standard library and does not call a remote service. It is the same logic the labs let you try against a real model.
import json
from math import isclose
def usable(stop_reason): # F8.1: a limit means the text may be cut off
return stop_reason in {"end_turn", "stop", "STOP"}
def validate(text): # F8.3: syntax, then fields, then rules
data = json.loads(text) # raises ValueError on bad syntax
if data.get("sentiment") not in {"positive", "negative", "neutral"}:
raise ValueError("bad sentiment")
if not 0 <= data.get("confidence", -1) <= 1:
raise ValueError("confidence out of range")
return data
def should_retry(status, attempt, max_retries=3): # F8.5: temporary errors only, bounded
return status in {429, 529} and attempt < max_retries
def delay(attempt, base=0.5, jitter=1.0): # F8.5: exponential backoff
return base * 2 ** attempt * jitter
def cost(n_in, n_out, p_in=2, p_out=6): # F8.6: illustrative prices
return (n_in * p_in + n_out * p_out) / 1_000_000
assert usable("end_turn") and not usable("max_tokens") and not usable("length")
assert validate('{"sentiment": "neutral", "confidence": 0.7}')["confidence"] == 0.7
for bad in ['{"sentiment": "neutral", "confidence": 7}', 'Sure! {"sentiment": "neutral"}']:
try:
validate(bad)
except ValueError:
pass
else:
raise AssertionError("accepted bad output")
assert should_retry(429, 0) and not should_retry(401, 0) and not should_retry(429, 3)
assert [delay(a) for a in range(3)] == [0.5, 1.0, 2.0]
assert isclose(cost(1000, 500), 0.005)
assert isclose(cost(1000, 500) * 10_000, 50)
Python examples are provided for study and local execution; this page does not execute Python.
Common mistakes
JSON output can still be false or out of range. A model call is not required for every rule-based task. Never place a reusable private API credential in a public browser bundle: the labs here use a key you paste yourself, kept in your browser and sent only to the provider, and a shipped product would call the provider from a server instead. Reasoning models can spend part of the output limit on hidden reasoning, so a tiny limit may return empty text.