Topic 16.4
Tokens, Context Windows, Cost and Rate Limits
In one line
Models read and write tokens, not characters or words. A context window caps how many tokens fit in one call, cost is a simple formula of input and output tokens times rates, and rate limits mean you must trim history and retry with backoff.
Think of it like this
A taxi that charges by the kilometre and has a boot of fixed size. You pay for distance travelled (tokens), you cannot load more than the boot holds (context window), and at rush hour the driver sometimes says "come back in a minute" (rate limit). Planning the trip around these three limits is most of the job.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- Token
- A small piece of text the model reads or writes. Roughly four English characters, but it varies.
- Context window
- The most tokens a model can handle in one call, counting input and output together.
- Input / output tokens
- Tokens you send in, and tokens the model generates. They are priced separately.
- Rate limit
- A cap on how many requests or tokens you may use per minute.
- HTTP 429
- The status code for "too many requests, slow down".
- Exponential backoff
- Waiting longer after each failure: 0.5s, 1s, 2s, 4s and so on.
- Jitter
- A random amount added to a wait so many clients do not retry at the same moment.
- Fallback
- A second choice (another model) used when the first fails.
Step by step
01Counting with a toy tokenizer
The toy below splits text into words and punctuation, then cuts long words into pieces of four letters. Real tokenizers are smarter and different, but the habit is the same: count before you send. The first example prints both the characters and the toy tokens.
02Cost as a formula
Write the formula as a function of four numbers: input tokens, output tokens, and the two rates. Because the rates change, keep them in configuration, not scattered in code. The numbers in the example are examples for the lesson, not any provider's prices.
def cost(input_tokens, output_tokens, input_rate, output_rate):
"""Rates are money per 1,000,000 tokens."""
return input_tokens / 1_000_000 * input_rate + output_tokens / 1_000_000 * output_rate03History grows, so trim it
Every turn resends everything. After ten turns most of what you pay for is old text. A basic strategy keeps the system message (always) and the newest messages that fit a token budget, dropping the oldest. The second example does this by walking backwards from the newest message.
Better strategies exist: summarise old turns into one short message, or keep only the turns relevant to the new question. Dropping silently can lose a fact the user gave in turn one, so log what you drop.
04A 429 is not an error to give up on
In the third example a fake model fails with a rate-limit error twice and then works. (We fake the failure because we cannot make a real service rate-limit us on demand, and a test must fail the same way every time.) with_backoff catches the rate-limit error, sleeps for a random time between 0 and an exponentially growing ceiling ("full jitter"), and tries again, up to a retry limit. The sleep function is a parameter so the example does not actually wait; that is also how you keep tests fast.
05Respect Retry-After, and only retry what can succeed
If the server tells you how long to wait (Retry-After), use that instead of guessing. Retry temporary failures: 429, 500, 503, timeouts. Do not retry permanent ones: a 400 (your request is wrong) or 401 (bad key) will fail identically every time and just waste time.
06Fallback and budgets together
After retries run out for one model, move to the next model in a list. The example prints "fallback: moving to the next model". Add a budget guard in front: if the prompt already exceeds the window, fail early with a clear message (the break-it example) instead of paying for a call that will be rejected.
Try it yourself
- 1
Shrink the budget
In the trim example set
budgetto 25. Predict which messages remain, then run. Is the system message ever dropped? - 2
Make a model fail more
In the backoff example set
failures=9for the primary model andretries=2. Predict which lines print, then check.
Code & diagrams
The rates are examples for the lesson only. Real tokenizers split text differently.
Expected output
['Toke', 'niza', 'tion', 'spli', 'ts', 'text', ',', 'not', 'word', 's', '.']
characters: 36 toy tokens: 11
example cost: 0.0304Try budget=30 and budget=100 and see how many messages survive.
Expected output
kept 5 of 13 messages
system | Be brief.
user | question number 5
assistant | answer number 5
user | question number 6
assistant | answer number 6The recorded sleeps are never actually slept, so the example is instant and repeatable.
Expected output
attempt 1 failed (primary: 429 too many requests); waiting 0.07s
attempt 2 failed (primary: 429 too many requests); waiting 0.85s
[primary] answer to 'hi'
sleeps recorded, not slept: 2
attempt 1 failed (big: 429 too many requests); waiting 0.38s
fallback: moving to the next model
[small] answer to 'hi'Always trust the usage numbers a response reports over your own estimate.
response = client.chat.completions.create(model="your-model-name", messages=messages)
usage = response.usage
print(usage.prompt_tokens, usage.completion_tokens) # OpenAI-style field names
# Anthropic Messages: response.usage.input_tokens and response.usage.output_tokensBreak it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Send more than the window allows
Call the model with a prompt larger than its limit.
LIMIT = 20
def call_model(prompt_tokens):
if prompt_tokens > LIMIT:
raise ValueError(f"context window exceeded: {prompt_tokens} tokens > limit {LIMIT}")
return "ok"
print(call_model(15))
print(call_model(52))Myth vs fact
Myth
A token is a word.
Fact
A token is a piece of text chosen by the tokenizer. Common words are often one token, rare words are several, and punctuation and spaces count too.
Myth
You can count tokens by characters divided by four.
Fact
That is a rough guess for English. Code, numbers and other languages differ a lot. Use the real tokenizer or the usage field.
Myth
Retrying immediately is the fastest fix for a 429.
Fact
Immediate retries make the overload worse and get you blocked longer. Back off with jitter and honour Retry-After.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
Cost grows faster than most people expect in chats: turn n resends all earlier turns, so total input across n turns is roughly proportional to n squared. Trimming, summarising and prompt caching (some providers discount repeated prefixes; check your provider's documentation) change that curve.
- ▸
Idempotency matters for retries. A model call has no side effect, so repeating it is safe. A tool call that sends an email does not (Topic 16.10). Retry the model, not the side effects, unless the operation carries an idempotency key.
- ▸
Put rate limiting on your own side too: a semaphore from Topic 12.7 limits concurrent calls, and a per-user daily counter protects your bill. Limits are a product feature as much as a technical one.
- ▸
The output cap counts against the window, and some models spend extra output tokens on hidden reasoning that you are billed for as output. So the budget check is: input tokens + the maximum output you allow must fit the window. Read the usage fields in the response (and your provider's documentation on reasoning and cached tokens) rather than assuming that visible text length equals billed output.
- ▸
Rate limits usually have more than one dimension (requests per minute and tokens per minute are separate caps), so a few huge prompts can hit a limit that many small ones never touch. Client-side, a token bucket sized to your quota smooths bursts better than reacting to 429s after the fact.
Remember this
- 1
A token is the small piece of text a model actually reads: often a whole common word, part of a rare word, a punctuation mark or a space plus a word. As a rough rule of thumb in English, a token is about four characters, or three quarters of a word. This is only an approximation, and it is different for code, other languages and every model family.
- 2
A tokenizer is the program that cuts text into tokens. Real tokenizers (commonly byte-pair encoding, which learns the most common pieces from lots of text) are trained, not hand-written, and each model has its own. The toy tokenizer in this topic cuts words into four-letter pieces to show the idea. Never use a toy count for real billing or limits. Use the provider's tokenizer library or its token-counting endpoint, and rely on the usage numbers returned in each response.
- 3
The context window is the maximum number of tokens the model can handle in one call. Your input (system text, history, retrieved documents: extra text you look up and paste in to help the model, Topic 16.9) and the model's output share it. If the input is too long the API returns an error. If the conversation grows each turn, sooner or later it does not fit, so you must decide what to drop or summarise. Window sizes differ between models and change over time: always read the current documentation for the model you use.
- 4
Cost is a formula. Providers typically charge separately per million input tokens and per million output tokens, with output usually priced higher. So: cost = input_tokens / 1,000,000 × input_rate + output_tokens / 1,000,000 × output_rate. The rates in this topic are made-up examples only. Read your provider's current price page, and remember a chat resends its history. Turn 1 sends one message, turn 2 sends two, turn 10 sends ten. So a long conversation's total input tokens grow roughly with the square of its length.
- 5
Rate limits cap how many requests or tokens you may send per minute. When you exceed them the API answers with HTTP status 429 (Too Many Requests), often with a
Retry-Afterheader (an extra labelled line of information that comes with the response; Topic 15.1). The right reaction is to wait and try again, with exponential backoff (wait 0.5s, 1s, 2s, 4s...) plus jitter (a random part so many clients do not all retry at the same instant). You met this in Topic 15.2; here it guards model calls. - 6
A fallback list adds resilience: if the preferred model is overloaded or failing, try the next model in a list, perhaps a smaller or cheaper one. Combine a retry limit, backoff, a fallback list and a budget guard that refuses to start a call that clearly will not fit. The Hectal tutor uses exactly these pieces: streaming answers, daily limits and model fallbacks.
Explain it without notes
What is a token, and why is counting characters a poor way to budget?
Explain the cost of a model call, and why a long chat gets expensive.
How should a client react to HTTP 429, and what should it not retry?
You have a 10-turn chat and the provider quotes per-million-token rates. How would you estimate its total cost, and name two ways to cut it?
Practice
Write estimate_cost(input_tokens, output_tokens, input_rate, output_rate) for rates quoted per million tokens, rounded to 6 decimals.
Write backoff_delays(retries, base, cap) returning the list of waits (no jitter) for each retry.
Write first_that_works(models, prompt) that tries (name, function) pairs in order and returns the first success with its name, or raises a clear error if all fail.
Trade-offs
- ↔
Trim hard or keep context. Aggressive trimming is cheap and fast but can lose facts. Keeping everything is accurate but costs more and hits the window. Summaries sit in between and cost one extra call.
- ↔
Retry more or fail fast. More retries survive short outages but make users wait. Chat interfaces usually want one or two retries and then a fallback or a clear message.
Done when you can
Done when you can explain tokens, context window and why they are not characters or words.
Done when you can write the cost formula and compute an example with given rates.
Done when you can trim a message list to a budget while keeping the system message.
Done when you can write retry-with-backoff and say which errors to retry.
Done when you know a full Hectal course on LLM engineering is planned to cover production budgets, caching and routing.