We use cookies to enhance your experience on the site
CodeWorlds

API Integration - Connecting with AI

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.

Installation: pip, not npm

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 anthropic

After 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 import and the OpenAI 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.

Reading the response

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.

The Anthropic client

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.

Streaming, or stream=True

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.

Summary

  • Python libraries are installed with
    pip install openai
    . The form
    npm install openai
    belongs to the Node.js world and does nothing in Python.
  • The variants
    pip install gpt4
    and
    pip install chatgpt
    do not install the official library - the package name on PyPI is
    openai
    .
  • OpenAI client import:
    from
    openai
    import
    OpenAI
    , package lowercase, class capitalized.
  • Anthropic client import:
    from
    anthropic
    import
    Anthropic
    , following exactly the same pattern.
  • The OpenAI chat call:
    client
    .chat
    .completions
    .create(
    , piece by piece
    client
    .
    chat
    .
    completions
    .
    create
    .
  • Reading the response:
    response
    .choices[0]
    .message
    .content
    .
  • With Anthropic you call
    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.
  • The model name is an ordinary string, for example
    "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.

Go to CodeWorlds