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, inblock.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'snameand itsinput. You'll meet these when you give mini tools, two modules from now.
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")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 themax_tokenslimit 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_tokensandcache_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.
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")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 theoutput_tokens: the difference is thinking. Then setmax_tokens=20and ask for a long story, and look at thestop_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)Using it well
- 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. - Check
stop_reasonbefore acting on a reply. Treatmax_tokensas unfinished andrefusalas no answer, not as a normal reply. - 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.
- 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.