Jarvis · Chapter 3

Hello, Jarvis 👋

Eleven lines of Python that talk to a language model. We will write them, run them, and then take every one apart, because the whole rest of the build is variations on this.

Goal

Make your first API call and understand every single line of it.

about 25 minutes

The whole program

Create hello.py:

import anthropic

client = anthropic.Anthropic()

response = client.messages.create(
    model="claude-haiku-4-5",
    max_tokens=300,
    messages=[
        {"role": "user", "content": "In one sentence, what is Python?"}
    ],
)

for block in response.content:
    if block.type == "text":
        print(block.text)

Run it:

python3 hello.py

After a second or two you get a sentence about Python. You just paid about a fifth of a cent. Now let us make sure you know exactly what happened.

Line by line

import anthropic

Pulls in the library you installed. It is ordinary Python; you could read its source. All it really does is send HTTP requests and parse the replies, which you already learned about in Lesson 43.

client = anthropic.Anthropic()

Makes the object that knows how to talk to the API. Note the empty brackets: as chapter 2 explained, it finds ANTHROPIC_API_KEY in your environment on its own. Make it once and reuse it; it holds a connection pool.

client.messages.create(...)

The actual request. Everything else in this build is this call with more arguments. It sends your message off and waits for the whole reply to come back.

model="claude-haiku-4-5"

Which model answers. One string, and the only thing you change to trade cost for capability. Chapter 1 has the table.

max_tokens=300

The ceiling on the reply length, in tokens (about three quarters of a word each). This is a safety limit, not a target: the model stops when it has finished, and you only pay for what it actually writes. Set it too low and answers get cut off mid-sentence.

messages=[...]

The conversation, as a list. Each entry is a dictionary with a role and some content. "user" is you. Right now there is exactly one message, which is why the model has no idea who you are or what you asked before.

The loop at the end

This is the part that surprises people. response.content is a list, not a string, because a reply can contain several kinds of block: text, a request to use a tool (chapter 8), and others. So you check each block's type before reading .text off it.

🧱 Why not just print(response.content[0].text)?

You will see that shortcut everywhere and it works right up until it does not. The moment you add tools, block zero may be a tool request with no .text at all, and your program dies with an AttributeError you will not enjoy debugging. Checking the type costs one line and never breaks.

Seeing the shape of a reply

The response object has more on it than the text. This prints the useful parts:

import anthropic

client = anthropic.Anthropic()

response = client.messages.create(
    model="claude-haiku-4-5",
    max_tokens=300,
    messages=[{"role": "user", "content": "Say hello in exactly three words."}],
)

print("model used :", response.model)
print("why stopped:", response.stop_reason)
print("tokens in  :", response.usage.input_tokens)
print("tokens out :", response.usage.output_tokens)
print("blocks     :", [b.type for b in response.content])

for block in response.content:
    if block.type == "text":
        print("text       :", block.text)

Output looks roughly like this (your numbers will differ slightly):

model used : claude-haiku-4-5
why stopped: end_turn
tokens in  : 16
tokens out : 8
blocks     : ['text']
text       : Hello there, friend.

stop_reason is worth knowing now because you will meet it properly in chapter 8:

stop_reasonWhat it means
end_turnIt finished naturally. The normal case.
max_tokensIt hit your ceiling and got cut off. Raise max_tokens.
tool_useIt wants to use a tool you gave it. All of chapter 8.
refusalIt declined on safety grounds.

The cost, for real this time

You now have real token counts, so you can compute what that call actually cost. This function is one you will reuse in chapter 12:

def cost_of(tokens_in, tokens_out, price_in=1.00, price_out=5.00):
    """Dollar cost of one call. Prices are per million tokens."""
    return (tokens_in / 1_000_000) * price_in + (tokens_out / 1_000_000) * price_out


# the numbers printed above
print(f"that call cost ${cost_of(16, 8):.6f}")
print(f"a thousand like it: ${cost_of(16, 8) * 1000:.4f}")
that call cost $0.000056
a thousand like it: $0.0560

Five and a half cents for a thousand short exchanges. This is why the honest answer to "can I afford to learn this?" is yes.

If it went wrong

Chapter 12 turns every one of those into a friendly sentence instead of a stack trace.

✅ Checkpoint

python3 hello.py prints a sentence written by a language model, and you can point at any line in the file and say what it does.

+150 XP