Knaph

Your First Call2 of 376 min read

Reading the Reply

Leila's hello program works. Out of curiosity she changes the last lines to print(reply), and instead of a greeting she gets a screenful: an id, the model's name, something called content with two items in it, a stop_reason, and a usage with four numbers. She only asked for one sentence. What is all the rest?


Leila I expected a string. Why is content a list?

Omid What else, apart from the words of the answer, have you seen a model write?

Leila Thinking, before it answers. And in an agent, requests to use tools.

Omid So if the reply were one string, how would your program tell those apart?

Leila It couldn't, not reliably. It would have to guess where the thinking ends and the tool request begins.

Omid That's why the reply comes in labelled pieces. Each piece is a block with a type, and your program picks out the kinds it wants.

Leila And the numbers at the bottom?

Omid That's the bill. Every reply tells you exactly what you were charged for.


Content blocks

reply.content is a list of content blocks. Each one has a type, and the rest of its fields depend on the type:

  • "text": the words of the answer, in block.text. There can be more than one text block in a reply.
  • "thinking": the model's working-out before it answers. Opus 5.5 always thinks, so its replies usually start with one of these. Its text is left out by default (you can ask for a summary of it), but you still pay for the thinking.
  • "tool_use": a request to run a tool, with the tool's name and its input. You'll meet these when you give mini tools, two modules from now.

The reply object. It holds response.content, a list of blocks: a thinking block, hidden by default, and a text block with the answer. Beside it, stop_reason is end_turn, and usage says 12 input tokens and 80 output tokens were used.

That's why the hello program loops over the blocks and keeps only the text ones. A shorter way to write the same thing, which mini uses throughout:

answer = "".join(b.text for b in reply.content if b.type == "text")
Python

Notice that it never assumes the text is the first block, or that there's only one. Code that does reply.content[0].text works until the day the first block is a thinking block, and then fails.

Why it stopped

reply.stop_reason says why the model stopped writing. Four values matter for an agent:

  • "end_turn": the model finished its answer. This is the normal one.
  • "max_tokens": the reply hit the max_tokens limit and was cut off, possibly mid-sentence.
  • "tool_use": the model stopped to wait for a tool to be run. The loop in a later module runs on this.
  • "refusal": the model declined to answer, for safety reasons.

A careful program checks stop_reason before it trusts the content. A reply cut off by max_tokens can look finished, and code that treats it as finished will act on half an answer.

What it cost

reply.usage holds the counts the bill is made from:

  • input_tokens: tokens you sent that were processed at the full input price.
  • cache_read_input_tokens and cache_creation_input_tokens: tokens read from, or written to, the prompt cache. For now they're 0; a later module turns caching on and makes them large.
  • output_tokens: everything the model wrote, thinking included.

Each kind has its own price per million tokens. In late 2026, for Opus 5.5: 4 dollars for input, 20 for output, 0.20 for reading from the cache (a twentieth of the input price) and 5 for writing to it. Sonnet 5.5 costs 2 and 10 for input and output; Haiku 5.5, 0.10 and 0.50.

From usage to dollars. Each line of usage is multiplied by its own price per million tokens: 2,000 input tokens at 4 dollars, 80,000 cache-read tokens at 0.20 dollars, 0 cache-write tokens at 5 dollars, and 1,500 output tokens at 20 dollars. The total is about 0.054 dollars.

In code, that's a table and a sum:

PRICES = {"input": 4.00, "output": 20.00, "cache_read": 0.20, "cache_write": 5.00}
 
def cost(usage):
    u = usage
    dollars = (u.input_tokens * PRICES["input"]
               + (u.cache_read_input_tokens or 0) * PRICES["cache_read"]
               + (u.cache_creation_input_tokens or 0) * PRICES["cache_write"]
               + u.output_tokens * PRICES["output"])
    return dollars / 1_000_000
 
print(f"{cost(reply.usage):.6f} dollars")
Python

The or 0 is there because a count can be None when there was nothing to count. The picture's example is a typical step of an agent session: most of the input comes from the cache and costs little, while the 1,500 output tokens are more than half the bill. Output is the expensive kind, and thinking is output.


Leila So my agent can keep its own bill as it goes.

Omid It should. Add up cost() after every call and you'll always know what a task cost, and which part of it was expensive.

Leila And if a reply has a huge output_tokens but a short answer?

Omid Then most of it was thinking. That's worth knowing too: it tells you the task was hard for the model, or that the effort was set higher than it needed.


Try it

With a key, add print(reply.stop_reason, reply.usage) to your hello program. Run it once with "Say hello" and once with a small puzzle ("A bat and a ball cost 1.10 in total; the bat costs 1.00 more than the ball. How much is the ball?"). Compare the output_tokens: the difference is thinking. Then set max_tokens=20 and ask for a long story, and look at the stop_reason. Without a key, the exercise after this lesson does the same arithmetic on real-looking replies.

mini so far

At the end of every module, here's the whole of mini as it stands, so you can always copy a working version. After this module it's one call, with the reply read properly:

mini.py after "Your First Call"
import anthropic
 
MODEL = "claude-opus-5-5"  # claude-haiku-5-5 is about 40x cheaper for practice
client = anthropic.Anthropic()  # reads ANTHROPIC_API_KEY
 
reply = client.messages.create(
    model=MODEL,
    max_tokens=1000,
    messages=[{"role": "user", "content": "Say hello in one short sentence."}],
)
print("".join(b.text for b in reply.content if b.type == "text"))
print(reply.stop_reason, reply.usage)
Python

Using it well

  1. Pick blocks by type, never by position. if b.type == "text" works whatever order the blocks come in; content[0] breaks on the first thinking block.
  2. Check stop_reason before acting on a reply. Treat max_tokens as unfinished and refusal as no answer, not as a normal reply.
  3. Log the usage of every call. It's free information, and when a session costs more than you expected, the usage shows where it went.
  4. Count missing numbers as zero. Write (u.field or 0) so that a reply without cache activity can't crash your cost code.

Words to know

  • content block - one labelled piece of a reply, such as text, thinking or a tool call, told apart by its type
  • stop reason - why the model stopped writing: finished, out of tokens, waiting for a tool, or refused
  • usage - the token counts of one reply (input, cache reads and writes, output), which its cost is made from

Big idea

A reply is labelled pieces, a reason for stopping, and a bill. A harness reads all three: the blocks to know what the model said or asked for, the stop reason to know whether it's finished, and the usage to know what it cost.

Test your understanding

Sign in to test your understanding of this lesson and track your progress.