hands-on labIDE in your browserfree

Your First AI Call: Talk to a Language Model from Python

Send your first prompt to a real language model from Python, then learn the five things every AI feature is built on: reading the reply, reading the token receipt and the time it took, what temperature does to the answers, giving the model a role with a system message, and keeping your program alive when the call fails.

time
40 min
checked steps
5
level
Beginner
setup
None

Freeone of 170+ labs in Pro

free · no account needed to start

Lab cockpit40 min · 5 stepsSession running
4 / 5 steps passingWhen it fails: keep the program alive · step 5 of 5
ai.py▶ Run✓ Check
FALLBACK = "Sorry, the assistant is unavailable right now. Please try again in a minute."  def safe_ask(prompt, client=None, model=None):    """Like ask(), but a failed call returns FALLBACK instead of crashing."""    client = client or CLIENT         
TerminalOutput
step 5 of 5, replayed: When it fails: keep the program alive. The code, the output and the check are the lab's own.

The job

Brightline Books is a small shop that wants an assistant on its website. Before anyone designs that assistant, someone has to make one call to a language model work from code and understand what comes back. That is your job today. You write five short Python functions in ai.py, one per step, and run each one against a real hosted model. Every check tries your function twice: once against a stand-in model that records exactly what you sent, and once against the real one.

5 steps, each checked when you finish it

A check runs your work at the end of every step. Hints and the full solution are there if you get stuck.

  1. 1

    Say hello: send a prompt, get a reply

    A language model answers messages.

    you writeask()read this step below ↓

  2. 2

    Read the receipt: tokens and time

    Every reply comes with a receipt.

    you writeask_with_stats()

  3. 3

    Temperature: why the same question gets different answers

    You saw in Step 1 that the same question can get a different answer each time.

    you writesample()

  4. 4

    Give it a role: the system message

    So far you have sent one message, from the user.

    you writeask_with_role()

  5. 5

    When it fails: keep the program alive

    A model call goes over the network to someone else's service, so sometimes it fails: the network drops, the service is overloaded, the key is wrong, or the request names a model that does not exist.

    you writesafe_ask()

The lab, step by step

The free lab’s own text, every step of it. Hints and solutions stay inside the lab.

Step 1: Say hello: send a prompt, get a reply

A language model answers messages. Your program sends it a list of messages; the model sends back one new message. That round trip is the whole interface, and every AI feature you will ever build starts with it.

Open ai.py. The top of the file is already done:

  • from openai import OpenAI loads the client library. Most hosted models, not only OpenAI's, accept requests in this format.
  • CLIENT = OpenAI() creates the connection. The lab already holds the address and the key, so it needs no arguments here.
  • MODEL is the name of the model you will talk to: Llama 3.1 8B, a small open model.

Each function takes client=None and starts with client = client or CLIENT. That line means "use the real connection unless someone hands me another one". The checker hands in a stand-in that records what you sent, so it can tell you exactly what was wrong.

do this

1. Write ask(prompt). Replace the TODO with two statements:

response = client.chat.completions.create(
    model=MODEL,
    messages=[{"role": "user", "content": prompt}],
)
return response.choices[0].message.content

messages is a Python list (square brackets) holding one dictionary (curly braces). The dictionary says who is speaking ("user", that is, you) and what they said. The reply comes back inside response: a list called choices (you asked for one answer, so take the first, [0]), its message, and that message's content.

2. Click Run. It calls ask() with a real question and prints the answer. Run it twice. The wording will probably change.

3. Click Check. The checker sends ask() a question it can verify: it asks the real model a question with one right answer.

ai.py, the file you edit50 lines
"""Your first AI calls, from Python.

The lab's environment already knows where the model lives and holds the key,
so `OpenAI()` needs no arguments here. On your own machine you would set
OPENAI_API_KEY (and OPENAI_BASE_URL for a non-OpenAI provider) first.
"""
import time

import openai
from openai import OpenAI

CLIENT = OpenAI()
MODEL = "meta/llama-3.1-8b-instruct"


def ask(prompt, client=None):
    """Send one question to the model and return its reply as a string."""
    client = client or CLIENT
    # TODO (Step 1): ask the model and return its reply text.
    #   1. response = client.chat.completions.create(model=MODEL, messages=[...])
    #      messages is a list with ONE dict: {"role": "user", "content": prompt}
    #   2. return response.choices[0].message.content
    raise NotImplementedError("Step 1: write ask()")


def ask_with_stats(prompt, client=None):
    """Ask, and also report what the call cost in tokens and time."""
    client = client or CLIENT
    raise NotImplementedError("ask_with_stats() arrives in Step 2")


def sample(prompt, temperature, n=5, client=None):
    """Ask the same question n times at one temperature; return the n replies."""
    client = client or CLIENT
    raise NotImplementedError("sample() arrives in Step 3")


def ask_with_role(system, prompt, client=None):
    """Give the model standing instructions (a system message), then ask."""
    client = client or CLIENT
    raise NotImplementedError("ask_with_role() arrives in Step 4")


FALLBACK = "Sorry, the assistant is unavailable right now. Please try again in a minute."


def safe_ask(prompt, client=None, model=None):
    """Like ask(), but a failed call returns FALLBACK instead of crashing."""
    client = client or CLIENT
    raise NotImplementedError("safe_ask() arrives in Step 5")

provided for you:try_it.py

Step 2: Read the receipt: tokens and time

Every reply comes with a receipt. response.usage counts tokens: the chunks of text (roughly three quarters of a word each) that the model read and wrote. Providers bill per token, and a model can only read so many at once, so these two numbers decide both the price and the limits of everything you build later.

  • prompt_tokens: what the model read (your messages).
  • completion_tokens: what it wrote (the reply).

The other number that matters to a person waiting for an answer is time.

do this

1. Write ask_with_stats(prompt). Make the same call as ask(), but time it and return a dictionary instead of just the text:

start = time.perf_counter()
response = client.chat.completions.create(...)   # same arguments as ask()
seconds = time.perf_counter() - start
return {
    "text": response.choices[0].message.content,
    "prompt_tokens": response.usage.prompt_tokens,
    "completion_tokens": response.usage.completion_tokens,
    "seconds": round(seconds, 3),
}

time.perf_counter() is a stopwatch reading; the difference between two readings is the time in between.

2. Run. It asks a two-word question and a three-sentence question. Compare the receipts: which number grew, and did the time grow with it? Writing takes the model far longer than reading, so the long answer costs more time than the long question.

ai.py, the file you edit56 lines
"""Your first AI calls, from Python.

The lab's environment already knows where the model lives and holds the key,
so `OpenAI()` needs no arguments here. On your own machine you would set
OPENAI_API_KEY (and OPENAI_BASE_URL for a non-OpenAI provider) first.
"""
import time

import openai
from openai import OpenAI

CLIENT = OpenAI()
MODEL = "meta/llama-3.1-8b-instruct"


def ask(prompt, client=None):
    """Send one question to the model and return its reply as a string."""
    client = client or CLIENT
    response = client.chat.completions.create(
        model=MODEL,
        messages=[{"role": "user", "content": prompt}],
    )
    return response.choices[0].message.content


def ask_with_stats(prompt, client=None):
    """Ask, and also report what the call cost in tokens and time."""
    client = client or CLIENT
    # TODO (Step 2): time the call and return a dict with four keys:
    #   "text"               the reply, as in ask()
    #   "prompt_tokens"      response.usage.prompt_tokens
    #   "completion_tokens"  response.usage.completion_tokens
    #   "seconds"            how long the call took: start = time.perf_counter() before,
    #                        time.perf_counter() - start after (round it to 3 places)
    raise NotImplementedError("Step 2: write ask_with_stats()")


def sample(prompt, temperature, n=5, client=None):
    """Ask the same question n times at one temperature; return the n replies."""
    client = client or CLIENT
    raise NotImplementedError("sample() arrives in Step 3")


def ask_with_role(system, prompt, client=None):
    """Give the model standing instructions (a system message), then ask."""
    client = client or CLIENT
    raise NotImplementedError("ask_with_role() arrives in Step 4")


FALLBACK = "Sorry, the assistant is unavailable right now. Please try again in a minute."


def safe_ask(prompt, client=None, model=None):
    """Like ask(), but a failed call returns FALLBACK instead of crashing."""
    client = client or CLIENT
    raise NotImplementedError("safe_ask() arrives in Step 5")

provided for you:try_it.py

Step 3: Temperature: why the same question gets different answers

You saw in Step 1 that the same question can get a different answer each time. The model writes one token at a time, and for each position it has a list of likely next tokens with probabilities. Temperature controls how it picks from that list. At 0 it always takes the most likely token, so the same prompt gives the same answer. Higher values let less likely tokens through, so answers vary.

Neither is "better". A shop assistant quoting opening hours wants 0. A brainstorm of names wants variety.

do this

1. Write sample(prompt, temperature, n). Ask the same prompt n times at one temperature and return the list of replies:

replies = []
for _ in range(n):
    response = client.chat.completions.create(
        model=MODEL,
        messages=[{"role": "user", "content": prompt}],
        temperature=temperature,
        max_tokens=20,
    )
    replies.append(response.choices[0].message.content.strip())
return replies

for _ in range(n): repeats the indented block n times. max_tokens=20 caps each reply, which keeps the experiment fast and cheap. .strip() removes spaces and line breaks at the ends so identical names compare equal.

2. Before you run, answer the question below. The run asks the model to invent a bookshop name five times at temperature 0 and five times at temperature 1.0.

3. Run, and compare the two lists. The results are saved to temperature_run.json.

ai.py, the file you edit67 lines
"""Your first AI calls, from Python.

The lab's environment already knows where the model lives and holds the key,
so `OpenAI()` needs no arguments here. On your own machine you would set
OPENAI_API_KEY (and OPENAI_BASE_URL for a non-OpenAI provider) first.
"""
import time

import openai
from openai import OpenAI

CLIENT = OpenAI()
MODEL = "meta/llama-3.1-8b-instruct"


def ask(prompt, client=None):
    """Send one question to the model and return its reply as a string."""
    client = client or CLIENT
    response = client.chat.completions.create(
        model=MODEL,
        messages=[{"role": "user", "content": prompt}],
    )
    return response.choices[0].message.content


def ask_with_stats(prompt, client=None):
    """Ask, and also report what the call cost in tokens and time."""
    client = client or CLIENT
    start = time.perf_counter()
    response = client.chat.completions.create(
        model=MODEL,
        messages=[{"role": "user", "content": prompt}],
    )
    seconds = time.perf_counter() - start
    return {
        "text": response.choices[0].message.content,
        "prompt_tokens": response.usage.prompt_tokens,
        "completion_tokens": response.usage.completion_tokens,
        "seconds": round(seconds, 3),
    }


def sample(prompt, temperature, n=5, client=None):
    """Ask the same question n times at one temperature; return the n replies."""
    client = client or CLIENT
    # TODO (Step 3): call the model n times with the same prompt and collect the replies.
    #   replies = []
    #   a for loop that runs n times; inside it, create(...) exactly as in ask() but also pass
    #       temperature=temperature, max_tokens=20
    #   append response.choices[0].message.content.strip() to replies
    #   return replies
    raise NotImplementedError("Step 3: write sample()")


def ask_with_role(system, prompt, client=None):
    """Give the model standing instructions (a system message), then ask."""
    client = client or CLIENT
    raise NotImplementedError("ask_with_role() arrives in Step 4")


FALLBACK = "Sorry, the assistant is unavailable right now. Please try again in a minute."


def safe_ask(prompt, client=None, model=None):
    """Like ask(), but a failed call returns FALLBACK instead of crashing."""
    client = client or CLIENT
    raise NotImplementedError("safe_ask() arrives in Step 5")

provided for you:try_it.py

Step 4: Give it a role: the system message

So far you have sent one message, from the user. A system message goes first in the list and holds standing instructions: who the model is, what it may talk about, how long and in what style it answers. The user never sees it, and the model gives it more weight than anything the user types. This is how Brightline's assistant will know it works for a bookshop.

do this

1. Write ask_with_role(system, prompt). Send two messages, the system one first:

messages=[
    {"role": "system", "content": system},
    {"role": "user", "content": prompt},
],

Pass temperature=0 as well (Step 3 showed why: the assistant should give the same answer to the same question) and return the reply text.

2. Run. It asks "Do you sell second-hand books?" twice: once with no role, once as Brightline's assistant with a two-sentence limit and a sign-off. Compare the length and tone of the two answers.

3. Check. The checker gives the real model a strict role, "reply with only the number", and asks it a sum. Without the role the model would explain its working; with it, you get just the number.

ai.py, the file you edit74 lines
"""Your first AI calls, from Python.

The lab's environment already knows where the model lives and holds the key,
so `OpenAI()` needs no arguments here. On your own machine you would set
OPENAI_API_KEY (and OPENAI_BASE_URL for a non-OpenAI provider) first.
"""
import time

import openai
from openai import OpenAI

CLIENT = OpenAI()
MODEL = "meta/llama-3.1-8b-instruct"


def ask(prompt, client=None):
    """Send one question to the model and return its reply as a string."""
    client = client or CLIENT
    response = client.chat.completions.create(
        model=MODEL,
        messages=[{"role": "user", "content": prompt}],
    )
    return response.choices[0].message.content


def ask_with_stats(prompt, client=None):
    """Ask, and also report what the call cost in tokens and time."""
    client = client or CLIENT
    start = time.perf_counter()
    response = client.chat.completions.create(
        model=MODEL,
        messages=[{"role": "user", "content": prompt}],
    )
    seconds = time.perf_counter() - start
    return {
        "text": response.choices[0].message.content,
        "prompt_tokens": response.usage.prompt_tokens,
        "completion_tokens": response.usage.completion_tokens,
        "seconds": round(seconds, 3),
    }


def sample(prompt, temperature, n=5, client=None):
    """Ask the same question n times at one temperature; return the n replies."""
    client = client or CLIENT
    replies = []
    for _ in range(n):
        response = client.chat.completions.create(
            model=MODEL,
            messages=[{"role": "user", "content": prompt}],
            temperature=temperature,
            max_tokens=20,
        )
        replies.append(response.choices[0].message.content.strip())
    return replies


def ask_with_role(system, prompt, client=None):
    """Give the model standing instructions (a system message), then ask."""
    client = client or CLIENT
    # TODO (Step 4): send TWO messages, the system message first:
    #   {"role": "system", "content": system}
    #   {"role": "user", "content": prompt}
    # Pass temperature=0 so the answer is repeatable, and return the reply text.
    raise NotImplementedError("Step 4: write ask_with_role()")


FALLBACK = "Sorry, the assistant is unavailable right now. Please try again in a minute."


def safe_ask(prompt, client=None, model=None):
    """Like ask(), but a failed call returns FALLBACK instead of crashing."""
    client = client or CLIENT
    raise NotImplementedError("safe_ask() arrives in Step 5")

provided for you:try_it.py

Step 5: When it fails: keep the program alive

A model call goes over the network to someone else's service, so sometimes it fails: the network drops, the service is overloaded, the key is wrong, or the request names a model that does not exist. If your code does not expect that, the whole program crashes and the shop's visitor sees an error page.

The openai library turns every one of those failures into an exception that belongs to one family, openai.APIError. Catching that family lets you show a polite message instead. Catch only that family: if you catch every exception, a typo in your own code will also turn into "try again later", and you will never find the bug.

do this

1. Write safe_ask(prompt, model=None). Wrap the call in try / except:

try:
    response = client.chat.completions.create(
        model=model or MODEL,
        messages=[{"role": "user", "content": prompt}],
        timeout=20,
    )
    return response.choices[0].message.content
except openai.APIError as err:
    print(f"[safe_ask] model call failed: {type(err).__name__}: {err}")
    return FALLBACK

model or MODEL uses the model the caller asked for, or the default when they asked for none. timeout=20 gives up after 20 seconds instead of waiting forever. The print keeps a note for you, the developer, while the visitor gets FALLBACK.

2. Run. A normal call, then a call to a model name that does not exist. The second prints the real error type the service sent back, then the friendly message.

3. Check. The checker simulates a dropped connection and an overloaded service, then sends a bug of its own through your function to make sure it is not hidden.

ai.py, the file you edit85 lines
"""Your first AI calls, from Python.

The lab's environment already knows where the model lives and holds the key,
so `OpenAI()` needs no arguments here. On your own machine you would set
OPENAI_API_KEY (and OPENAI_BASE_URL for a non-OpenAI provider) first.
"""
import time

import openai
from openai import OpenAI

CLIENT = OpenAI()
MODEL = "meta/llama-3.1-8b-instruct"


def ask(prompt, client=None):
    """Send one question to the model and return its reply as a string."""
    client = client or CLIENT
    response = client.chat.completions.create(
        model=MODEL,
        messages=[{"role": "user", "content": prompt}],
    )
    return response.choices[0].message.content


def ask_with_stats(prompt, client=None):
    """Ask, and also report what the call cost in tokens and time."""
    client = client or CLIENT
    start = time.perf_counter()
    response = client.chat.completions.create(
        model=MODEL,
        messages=[{"role": "user", "content": prompt}],
    )
    seconds = time.perf_counter() - start
    return {
        "text": response.choices[0].message.content,
        "prompt_tokens": response.usage.prompt_tokens,
        "completion_tokens": response.usage.completion_tokens,
        "seconds": round(seconds, 3),
    }


def sample(prompt, temperature, n=5, client=None):
    """Ask the same question n times at one temperature; return the n replies."""
    client = client or CLIENT
    replies = []
    for _ in range(n):
        response = client.chat.completions.create(
            model=MODEL,
            messages=[{"role": "user", "content": prompt}],
            temperature=temperature,
            max_tokens=20,
        )
        replies.append(response.choices[0].message.content.strip())
    return replies


def ask_with_role(system, prompt, client=None):
    """Give the model standing instructions (a system message), then ask."""
    client = client or CLIENT
    response = client.chat.completions.create(
        model=MODEL,
        messages=[
            {"role": "system", "content": system},
            {"role": "user", "content": prompt},
        ],
        temperature=0,
    )
    return response.choices[0].message.content


FALLBACK = "Sorry, the assistant is unavailable right now. Please try again in a minute."


def safe_ask(prompt, client=None, model=None):
    """Like ask(), but a failed call returns FALLBACK instead of crashing."""
    client = client or CLIENT
    # TODO (Step 5): make the call inside try/except.
    #   try:
    #       response = client.chat.completions.create(model=model or MODEL, messages=[...], timeout=20)
    #       return response.choices[0].message.content
    #   except openai.APIError as err:
    #       print a short note that includes the error type, then return FALLBACK
    # Catch openai.APIError only. A bug in your own code (a typo, a wrong key) must still crash.
    raise NotImplementedError("Step 5: write safe_ask()")

provided for you:try_it.py

what you get

  • An editor and terminal in your browser
  • A check at the end of every step
  • Hints and the full solution
  • Nothing to install or configure

before you start

  • None. You can follow along if you have never written Python; each step explains the lines it uses.

Part of the Start with AI track

Lab 1 of 21. Your first AI call, a chatbot, tested prompts and everyday automations. No machine learning background needed.

Questions about this lab

Do I need to know Python for this lab?

No. Each step shows the exact lines you need and explains them. You write about 40 lines in total, and the checker tells you what to fix when something is off.

Do I need my own API key?

No. The lab environment is already connected to a hosted model. The first lines of ai.py explain which two settings you would add on your own computer.

Which model does the lab use?

Llama 3.1 8B Instruct, a small open model, through an OpenAI-compatible endpoint. The same code works with other providers by changing the model name and the base URL.

What is temperature?

A setting that controls how much the model varies its word choices. At 0 it picks the most likely words and repeats itself; at higher values it samples more freely. Step 3 measures the difference on a real prompt.

What happens when a program talks to a language model

A chat assistant on a website is a program that sends text to a language model over the internet and shows what comes back. The request is a list of messages, each with a role and some text. The response carries the reply, a count of the tokens that were read and written, and the name of the model that answered. Everything larger, from a support bot to an agent, is built on that one exchange. This lab makes that exchange concrete in Python with the OpenAI client library, which most hosted models accept. You send a prompt and read the reply, read the token counts that decide the bill, time the call, run the same prompt at two temperatures to see why answers vary, add a system message that changes how the model behaves, and catch the errors a real service throws so your program shows a friendly message instead of a crash.