Learning / Agentic Operations / Lesson 0005

Agentic Operations · Lesson 0005 · Level 1 · Foundations · Hands-on lab

Tokens & Cost

The unit everything is denominated in: watch your words become tokens, and read real dollars off a real API response.

Your win for this lesson: you'll tokenize real text, predict what a call will cost, and check the prediction against the usage numbers on a live response. Prerequisite: Lesson 0001.

Lesson 0001 established that the context window is the model's entire world. This lesson prices that world. Models don't read words — they read tokens, chunks of a few characters each, and every API bill, rate limit, and context-window ceiling you will ever meet is denominated in them. Work in a terminal at the repo root; the snippets below run in uv run python.

Checkpoint 0 · Bring your own key

This course runs on your own Anthropic API key — it stays on your machine, is never committed, and never reaches anyone else. Copy the template, then paste your key into the new .env:

cp .env.example .env      # then edit .env:  ANTHROPIC_API_KEY=sk-ant-...

Create a key at the Anthropic Console if you don't have one. .env is gitignored, so the key can't be committed by accident. Verify it's set without printing the secret:

uv run python -c "from dotenv import dotenv_values; print('ANTHROPIC_API_KEY' in dotenv_values('.env'))"
Pass: prints True. The whole Level 1 hands-on track runs on well under a dollar of your own credit.

Stage 1 · Count tokens without spending anything

The API has a counting endpoint that tokenizes your input without running the model. Point it at three different kinds of text and compare:

import anthropic
client = anthropic.Anthropic()

samples = {
    "prose":   "The context window is the model's entire world. If a fact "
               "is not in the window, it does not exist for this call.",
    "code":    "def total(xs):\n    return sum(x.amount for x in xs if x.ok)",
    "numbers": "3.14159 2.71828 1.41421 0.57721 6.02214",
}
for name, text in samples.items():
    n = client.messages.count_tokens(
        model="claude-opus-4-8",
        messages=[{"role": "user", "content": text}],
    ).input_tokens
    print(f"{name}: {len(text)} chars -> {n} tokens")
Pass: you can state roughly how many characters one token buys in English prose (about four), and explain why code and bare numbers do worse. Then try your own: paste a paragraph from Lesson 0001 and predict its count before running.

Stage 2 · Predict, then spend

This course's model, claude-opus-4-8, is priced at $5 per million input tokens and $25 per million output tokens. Estimate before you spend: count the input, guess the output length, do the arithmetic — then make the call and read the real numbers off response.usage.

QUESTION = ("Explain in about 200 words why LLM APIs price input and "
            "output tokens differently.")

n_in = client.messages.count_tokens(
    model="claude-opus-4-8",
    messages=[{"role": "user", "content": QUESTION}],
).input_tokens
guess_out = 300  # ~200 words of output
estimate = n_in / 1e6 * 5.00 + guess_out / 1e6 * 25.00
print(f"predicted: {n_in} in, ~{guess_out} out -> ${estimate:.5f}")

response = client.messages.create(
    model="claude-opus-4-8", max_tokens=2000,
    messages=[{"role": "user", "content": QUESTION}],
)
u = response.usage
actual = u.input_tokens / 1e6 * 5.00 + u.output_tokens / 1e6 * 25.00
print(f"actual:    {u.input_tokens} in, {u.output_tokens} out -> ${actual:.5f}")
Pass: your prediction lands within about 2× of the actual, and you can point at every number: which one you controlled (max_tokens is a ceiling, not a target), which one the model chose, and which one the counting endpoint got exactly right.

Stage 3 · Watch history get priced

The model is stateless — every turn re-sends the whole conversation (Lesson 0001). That sentence has a price. Run a three-turn conversation and watch input tokens climb even though each new question is short:

messages = []
for question in [
    "Name three moons of Jupiter.",
    "Which of those is the largest?",
    "How was it discovered?",
]:
    messages.append({"role": "user", "content": question})
    response = client.messages.create(
        model="claude-opus-4-8", max_tokens=2000, messages=messages,
    )
    messages.append({"role": "assistant", "content": response.content})
    print(f"turn {len(messages) // 2}: input_tokens={response.usage.input_tokens}")
Pass: input tokens grow every turn, and you can explain why turn 3 pays for turns 1 and 2 all over again. That growth curve is statelessness, priced — you'll watch it again in Lesson 0007 when you log an agent's full context every turn.
The load-bearing sentence: tokens are the currency of this whole field — every design decision in this course (what goes in context, when to retrieve, when to summarize, when to spawn a subagent) eventually shows up as a token line-item on somebody's bill.

Self-grade

You own this lesson when all three are true:

Next

Lesson 0006 — Anatomy of a Tool Call: one complete tool round-trip by hand — you'll see that a "tool call" is just structured text the model proposes, and that you are the one who makes it real.