From the previous lesson you already know what an LLM, a token and a context window are. The catch is that a model like that does not live on your laptop - it sits on a server run by the provider. To use it from Python you have to send an HTTP request, sign it with a key and unpack the answer. By hand that is an hour of fiddling; with the official library it is four lines.
This lesson is about those four steps: install, import, call, read the result. Tracking a model across the network is not that different from tracking game on the savanna - what counts is walking the right path, not how fast you run.
Python libraries are installed with the pip package manager. That is the first trap, because the internet is full of JavaScript examples where the same package is installed with
npm install openai. npm is the Node.js package manager - in Python it installs nothing, and at best it tells you it has never heard of that command.The second trap is the package name. The company is called OpenAI, the product ChatGPT, and the model family GPT - but in the PyPI repository the package is simply named
openai. The commands pip install chatgpt and pip install gpt4 will not install the official library, because OpenAI publishes nothing under those names. The Anthropic package follows the same rule: it is called anthropic, not claude.1pip install openai
2pip install anthropicAfter those two commands you have two independent packages in your environment. Neither has sent a single request yet and neither knows your API key - you supply the key only when you create the client.
The order in the import line is always the same:
from → openai → import → OpenAI. First from points at the package we are taking from, and only then import names the thing we want to pull out of it. Watch the letter case: the package is lowercase (openai), the client class is capitalized (OpenAI). Swapping the two around is the most common mistake in this single line.1from openai import OpenAI
2
3client = OpenAI(api_key="your-api-key")
4
5response = client.chat.completions.create(
6 model="gpt-4",
7 messages=[
8 {"role": "system", "content": "You are a safari guide named Darwin."},
9 {"role": "user", "content": "Which savanna animal is the fastest?"}
10 ],
11 temperature=0.7,
12 max_tokens=500
13)The call itself reads like a path:
client → .chat → .completions → .create(. The client groups API families, chat is the family of conversations, completions is a resource inside that family, and create is the action you perform on it. Together they build one chat completion - the API name for a single turn of the conversation. The order is not a convention you can bend, because client.completions.chat does not exist: completions lives inside chat, not the other way round. Broken into single pieces the same path looks like this: client, ., chat, ., completions, ., create.The model name is an ordinary string. Here we passed
"gpt-4", but "gpt-5" could sit there just as well - the library checks nothing locally, it only forwards the name. A typo ends in an error from the server, and that is actually good news.max_tokens is trickier. It caps the length of the answer, not the length of the question, and when the limit runs out the model simply stops writing - mid sentence, with no exception raised. The code runs, the answer is there, it is just cut off. If your results regularly end in the middle of a thought, @name, the model is usually not at fault - max_tokens is set too low. temperature, in turn, controls randomness: 0 gives answers that are as repeatable as possible, 1 gives more varied ones.There is no text in the
response variable. There is an object full of metadata, and the actual content sits four steps deeper.1answer = response.choices[0].message.content
2print(answer)The path is
response → .choices[0] → .message → .content, in exactly that order. choices is a list, because the API can return several variants of an answer at once, which is why [0] picks the first one. message is the message object, carrying among other things a role and content, and content is finally the plain text. Skipping [0] hands you a list instead of a message, and stopping at .message hands you an object whose printout is an unreadable dump of fields - no error, just not what you wanted.With the Claude model it looks very similar, only the names differ. The import keeps the same order:
from → anthropic → import → Anthropic. Again the package is lowercase, the class capitalized. You will also meet the import anthropic variant, where the client is created with anthropic.Anthropic(...) - it works the same way, but I recommend the version with from, because a class name pulled out once is shorter to use afterwards.1from anthropic import Anthropic
2
3client = Anthropic(api_key="your-api-key")
4
5message = client.messages.create(
6 model="claude-sonnet-4-6",
7 max_tokens=1024,
8 system="You are an expert in the Python language.",
9 messages=[
10 {"role": "user", "content": "How do I write a sorting function?"}
11 ]
12)
13
14print(message.content[0].text)Three differences are worth remembering. The call path is shorter -
client.messages.create(, with no chat and no completions. The system instruction is a separate system parameter rather than an entry in messages, so putting a "system" role in there ends with an error from the API. And max_tokens is mandatory here: without it the request will not go through at all. You read the answer through message.content[0].text, not through choices - a distinction that is easy to blur when one project talks to both providers.By default you wait for the whole answer and only then see anything. With a longer text that is a dozen seconds of silence. The
stream=True parameter changes one thing: the response arrives in pieces, token by token, so you can print it in real time.1stream = client.chat.completions.create(
2 model="gpt-4",
3 messages=[{"role": "user", "content": "Tell me a story about the savanna"}],
4 stream=True
5)
6
7for chunk in stream:
8 content = chunk.choices[0].delta.content
9 if content:
10 print(content, end="", flush=True)Instead of a single object you get a stream of chunks that you walk through with a loop. In streaming mode there is no
.message - a chunk carries .delta, the increment of content. The first and last chunk have delta.content equal to None, and that is exactly why if content: is standing there. Without that check Python prints the word "None" in the middle of your text and raises no error at all.It is worth knowing what
stream=True does not do. It is not for encrypting communication - HTTPS handles encryption and works identically in both modes. It is not for data compression either - compression belongs to HTTP transport and the library arranges it regardless of this parameter. And it is not for saving the response to a file - if you want a record on disk, you have to open the file yourself and append the chunks as they arrive. The number of tokens spent and the cost stay exactly the same as without the stream. The only thing that changes is the moment the text appears on screen.pip install openai. The form npm install openai belongs to the Node.js world and does nothing in Python.pip install gpt4 and pip install chatgpt do not install the official library - the package name on PyPI is openai.from → openai → import → OpenAI, package lowercase, class capitalized.from → anthropic → import → Anthropic, following exactly the same pattern.client → .chat → .completions → .create(, piece by piece client . chat . completions . create.response → .choices[0] → .message → .content.client.messages.create(...), max_tokens is mandatory, the instruction goes into the system parameter, and you read the text from message.content[0].text.stream=True is for receiving the response in real time, token by token. It does not encrypt communication, does not compress data and does not save anything to a file."gpt-4", "gpt-5" or "claude-sonnet-4-6".In the next lesson we turn to the content of the messages themselves - prompt engineering will show you how to phrase an instruction so the model gets it right the first time. For now remember one thing: the whole API comes down to four moves - install the package, import the client class, call
create and pluck the text from the end of the path.