Skip to content

Enter a model ID

GitHub

Make your first request

The goal of this page is to get a real answer from a model as quickly as possible. You’ll install LM15, set an API key, and ask a question. Then we’ll change one thing at a time, and look at what changes. The Overview explains the ideas behind each part; here we’ll skip the theory and focus on the practice.

On this page, you’ll learn how to:

  • ask a question and read everything that comes back;
  • give the model instructions;
  • carry a conversation into a follow-up question;
  • watch an answer arrive as it’s written.

The code follows the language, provider, and model you picked at the top left of the page. The answers shown are real: we ran each step with gpt-5.6-sol through OpenAI Codex. Yours will be worded differently, and that’s normal.

You’ll need Python 3.10 or newer. Run this in your terminal:

Terminal window
python3 -m pip install --pre lm15

The Python core has no required third-party dependencies: it uses Python’s standard library. The --pre flag includes prereleases, so you get the API used in this guide rather than the older stable release.

Using Windows, or keeping this project separate?

On Windows, use py instead of python3.

A virtual environment keeps this project’s packages separate from your other Python work. Create one before installing LM15:

Terminal window
python3 -m venv .venv

Activate it on macOS or Linux:

Terminal window
source .venv/bin/activate

Or in Windows PowerShell:

Terminal window
.venv\\Scripts\\Activate.ps1

Then install inside it:

Terminal window
python -m pip install --pre lm15

You only need a key from one provider. Choose your provider at the top left of the page, then open its key page below. You may need to create an account and add credit.

On macOS or Linux, run the command below in the terminal where you’ll run your code. Replace your-api-key with your own key.

Choose a provider above to fill in the variable name below.

export YOUR_PROVIDER_API_KEY="your-api-key"
First time setting an environment variable?

An environment variable is a named value your terminal passes to the programs it starts. LM15’s router reads the key from that value, so you don’t need to put it in your code.

The command above works in Bash and Zsh, the usual shells on macOS and Linux. In Windows PowerShell, use:

$env:YOUR_PROVIDER_API_KEY = "your-api-key"

In Windows Command Prompt, use:

set YOUR_PROVIDER_API_KEY=your-api-key

These commands apply to the current terminal. Run your example from that same terminal; set the key again when you open a new one. An already-open notebook or editor won’t automatically pick it up.

Keep the key private. Don’t include it in shared code, screenshots, or version control, and don’t print it to check whether setup worked.

Running the example sends a real request to your chosen provider and may cost money. Choose a model your account can access.

Using local Ollama instead? The Python router acceptsollama:your-model without an API key. Ollama must be running with that model installed; edit the model name in your copied code.

For explicit keys, rotating credentials, or local Claude and Codex logins, see Python authentication.

The examples in these guides follow one small program: an assistant for a wildlife research station. The station has a mystery: acorns under the oak trees vanish overnight. Let’s ask a model.

from lm15 import LMRouter, Message, Request

request = Request(
    model="provider:model",
    messages=[Message.user(
        "What might be eating the acorns under our oak trees at "
        "night?"
    )],
)

router = LMRouter()
response = router.complete(request)
print(response.text)

Run it. Here is what came back when we did:

What came back gpt-5.6-sol through OpenAI Codex, September 23, 2026
Common nighttime acorn eaters include:

- **Deer** — often swallow acorns whole; look for hoofprints and pellet-like droppings.
- **Mice, rats, voles, and woodrats** — leave neatly gnawed shells, small tooth marks, or piles near cover.
- **Flying squirrels** — nocturnal and especially likely near mature oaks; may leave opened shells around tree bases.
- **Raccoons and opossums** — may eat acorns along with other foods; identifiable by hand-like raccoon tracks or opossum tracks.
- **Wild pigs or bears**, where present — usually leave obvious signs such as rooted-up soil, large tracks, or scat.

Day-active **tree squirrels, chipmunks, jays, and turkeys** may also remove or cache acorns, so disappearance noticed in the morning does not necessarily mean it happened overnight. Acorns with a small round hole may instead contain—or have contained—an **acorn-weevil larva**.

A motion-activated trail camera aimed at an undisturbed patch beneath the trees is the easiest way to identify the visitor.

Three things happened:

  1. You described what you want: a request, with a model and one message.
  2. The router read the model name. Its first part names the provider, so it knew whom to call and which API key to look for in your environment.
  3. The provider’s answer came back as a response. Its text is the text of the message the model wrote.

The answer is long, and it’s written as a list. We’ll come back to that when we give the model instructions.

The text is only part of what comes back. Replace the lines after the request with these:

router = LMRouter()
response = router.complete(request)
print(response.text)
print(response.finish_reason)  # "stop", "length", "tool_call"…
print(response.usage.input_tokens, response.usage.output_tokens)

The rest of that answer gpt-5.6-sol through OpenAI Codex, September 23, 2026
finish reason
stop
input tokens
30
output tokens
581
  • The finish reason says why the model stopped. stop means it finished on its own. You’ll also meet length (it reached a limit you set) and tool_call (it is waiting for your program to run a tool).
  • Token counts are what the provider counted, and what it bills: here 30 tokens in and 581 out. A count the provider didn’t report is empty, not zero.

581 tokens is a lot for a quick question. Instructions change that. They go in system, beside the messages rather than inside them, and they apply to the whole request. Change the request to this:

request = Request(
    model="provider:model",
    system=(
        "You are the field assistant for a wildlife research "
        "station. Answer in two sentences."
    ),
    messages=[Message.user(
        "What might be eating the acorns under our oak trees at "
        "night?"
    )],
)

What came back gpt-5.6-sol through OpenAI Codex, September 23, 2026
Likely nighttime acorn eaters include deer, raccoons, opossums, wild pigs, wood mice, rats, and other small rodents; squirrels and many birds usually feed during the day. Check for tracks, scat, gnaw marks, or disturbed leaf litter, and use a motion-triggered trail camera to identify the visitor.
finish reason
stop
input tokens
40
output tokens
94

The question is the same, but the answer is different: it’s two sentences long (94 tokens instead of 581), and it answers as the station’s assistant.

Now a second question: Would the same animals eat hazelnuts? Send it on its own first:

followup = Request(
    model="provider:model",
    system=(
        "You are the field assistant for a wildlife research "
        "station. Answer in two sentences."
    ),
    messages=[Message.user("Would the same animals eat hazelnuts?")],
)
print(router.complete(followup).text)

What came back gpt-5.6-sol through OpenAI Codex, September 23, 2026
Which animals are you referring to? Many rodents, birds, deer, and wild boar eat hazelnuts, but suitability depends on the species.

The model doesn’t know which animals you mean. It remembers nothing between requests: each one must carry the conversation. So send the first question, the model’s answer, and then the new question. Add this after the lines that print the first answer:

followup = Request(
    model="provider:model",
    system=(
        "You are the field assistant for a wildlife research "
        "station. Answer in two sentences."
    ),
    messages=[
        *request.messages,
        response.message,
        Message.user("Would the same animals eat hazelnuts?"),
    ],
)
print(router.complete(followup).text)

What came back gpt-5.6-sol through OpenAI Codex, September 23, 2026
Yes—mice, rats, squirrels, raccoons, deer, and wild pigs may also eat hazelnuts, although opossums are less likely to target hard-shelled nuts. Small, neat tooth marks often indicate rodents, while crushed shells or scattered husks may point to larger mammals.
finish reason
stop
input tokens
149
output tokens
76

This time the model knows which animals you mean. The cost is in the input: 149 tokens, against 35 for the question alone. Each request sends the whole conversation again, and you pay for it again.

Send the model’s whole message, not just its text. A message can carry things the provider needs to continue, such as its reasoning, which the text alone would lose.

A long answer takes a while. Streaming prints it as it’s written. The request is the one you sent in Give it instructions; only the way you receive the answer changes:

stream = ResponseStream(router.stream(request), request)
for text in stream:
    print(text, end="", flush=True)
print()
print(stream.usage.output_tokens, "tokens")

What came back gpt-5.6-sol through OpenAI Codex, September 23, 2026
Likely nighttime acorn eaters include deer, raccoons, opossums, mice, rats, and squirrels caching food around dusk; wild boar may also be responsible where present. Check for tracks, droppings, rooting, gnawed shells, or set up a motion-activated trail camera to identify the visitor.
finish reason
stop
input tokens
40
output tokens
91
pieces streamed
67

The answer arrived in 67 pieces. At the end you get the same kind of complete response as before, with its finish reason and token counts.

Compare it with the answer under Give it instructions: same request, different words. A model doesn’t repeat itself word for word, so don’t write code that expects it to.

To switch, change the model name, and nothing else. Pick another provider at the top of the page and every example above follows. A model name belongs to one provider, so choose a model that provider serves, and set its API key too.

  • No API key found. The error names the environment variable to set; see Set an API key. Set it in the terminal where you run your program.
  • The model name is wrong or your account can’t use it. The provider refuses, and LM15 passes its message on.
  • A setting the provider can’t honour. LM15 refuses before sending, rather than quietly ignore what you asked. When we asked OpenAI Codex, which has no length limit, for a 16-token limit:
What LM15 raised gpt-5.6-sol through OpenAI Codex, September 23, 2026
UnsupportedFeatureError: openai-codex: config.max_tokens: this backend has no output cap; dropping it risks unbounded paid generation

The request was never sent, so it didn’t cost anything.

Predict the answer before you run each one.

  1. Run the program from Ask a question twice. Do you get the same words?
  2. Remove “Answer in two sentences.” from the instructions. What happens to the output tokens?
  3. In the follow-up, keep the first question but leave out the model’s answer. What does the model see, and how might it answer?
What to expect
  1. No. The same request gave two differently worded answers above, under Give it instructions and Watch it arrive.
  2. They grow back towards the length of the first, list-shaped answer.
  3. Two questions in a row, with no answer between them. It may answer both, or only the last; either way, it can’t know what it said before.

You’ve used four of the parts every request has: a model, messages, instructions, and the response. The Overview shows the others, tools and settings, and how LM15 keeps them the same across languages and providers.