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.
Install
Section titled “Install”You’ll need Python 3.10 or newer. Run this in your terminal:
python3 -m pip install lm15The Python core has no required third-party dependencies: it uses Python’s standard library. This installs LM15 2.0.0, the version this guide uses.
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:
python3 -m venv .venvActivate it on macOS or Linux:
source .venv/bin/activateOr in Windows PowerShell:
.venv\\Scripts\\Activate.ps1Then install inside it:
python -m pip install lm15You’ll need Node.js 22.18 or newer. In a new folder, run this in your terminal:
npm init -ynpm pkg set type=modulenpm install @lm15/lm15LM15 for TypeScript has no runtime dependencies. This installs the current version, 2.0.0, the version this guide uses. Save each example in a file ending in .ts, such as first.ts, and run it with node first.ts: Node runs TypeScript files directly.
Adding LM15 to a project you already have?
Run only the last command, npm install @lm15/lm15. The examples use await at the top level of the file, which needs "type": "module" in your package.json (that’s what the second command sets) or a file ending in .mts.
In a web page, import @lm15/lm15/browser instead; see the TypeScript SDK guide.
You’ll need a recent Rust toolchain. Run this in your terminal:
cargo new first-requestcd first-requestcargo add lm15cargo add tokio --features macros,rt-multi-threadThis installs the current version, 2.0.0, the version this guide uses. Tokio runs the examples’ async code. Put each example in src/main.rs and run it with cargo run.
An example needs another crate?
A few later examples use more crates, or more Tokio features. Each example’s first comment names them, for example // Dependencies: lm15, tokio (macros, rt-multi-thread, time). Add what’s missing the same way:
cargo add tokio --features timeYou’ll need Go 1.26.2 or newer. Run this in your terminal:
mkdir first-request && cd first-requestgo mod init example.com/first-requestgo get github.com/lm15-dev/lm15-go/v2LM15 for Go uses only Go’s standard library. This installs the current version, v2.0.0, the version this guide uses. Go puts the major version in the import path, so programs importgithub.com/lm15-dev/lm15-go/v2. Put each example in main.go and run it with go run .
You’ll need Julia 1.10 or newer. In a new folder, run this in your terminal:
julia --project=. -e 'using Pkg; Pkg.add(url="https://github.com/lm15-dev/LanguageModel15.jl")'LM15 for Julia isn’t in Julia’s package registry yet, so this installs it from GitHub into the folder’s own environment. In Julia the package is namedLanguageModel15 (the registry asks for longer names), so programs start with using LanguageModel15. Save each example as first.jl and run it with julia --project=. first.jl. The first run compiles LM15, which takes a minute.
Set an API key
Section titled “Set an API key”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.
- Anthropic API keys —
ANTHROPIC_API_KEY - DeepInfra API keys —
DEEPINFRA_API_KEY - DeepSeek API keys —
DEEPSEEK_API_KEY - Fireworks AI API keys —
FIREWORKS_API_KEY - Google Gemini API keys —
GEMINI_API_KEY - Groq API keys —
GROQ_API_KEY - Meta API keys —
META_API_KEY - Moonshot / Kimi API keys —
MOONSHOTAI_API_KEY - OpenAI API keys —
OPENAI_API_KEY - OpenRouter API keys —
OPENROUTER_API_KEY - Parasail API keys —
PARASAIL_API_KEY - Together AI API keys —
TOGETHER_API_KEY - xAI API keys —
XAI_API_KEY - Z.AI API keys —
ZAI_API_KEY
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-keyThese 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.
Forgot to set the key?
Python reports which provider needs a key and which variable to set. For your selected provider, the message begins:
Set the named variable, then run your code again from the same terminal.
Ask a question
Section titled “Ask a question”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:
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:
- You described what you want: a request, with a model and one message.
- 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.
- 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.
Read the whole response
Section titled “Read the whole response”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)- finish reason
- stop
- input tokens
- 30
- output tokens
- 581
- The finish reason says why the model stopped.
stopmeans it finished on its own. You’ll also meetlength(it reached a limit you set) andtool_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.
Give it instructions
Section titled “Give it instructions”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?"
)],
)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.
Ask a follow-up
Section titled “Ask a follow-up”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)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)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.
Watch it arrive
Section titled “Watch it arrive”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")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.
Use another provider
Section titled “Use another provider”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.
When something goes wrong
Section titled “When something goes wrong”- 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:
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.
Try it yourself
Section titled “Try it yourself”Predict the answer before you run each one.
- Run the program from Ask a question twice. Do you get the same words?
- Remove “Answer in two sentences.” from the instructions. What happens to the output tokens?
- 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
- No. The same request gave two differently worded answers above, under Give it instructions and Watch it arrive.
- They grow back towards the length of the first, list-shaped answer.
- 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.