Skip to content

Enter a model ID

GitHub

Connect a provider

There are dozens of companies that will run a language model for you, and they all want something slightly different: their own address, their own way of sending a key, their own shape for a request. Most of the time you don’t want to think about any of that. You want to pick a model, and perhaps try another one tomorrow. In LM15, the provider is part of the model’s name, so moving from one provider to another means changing a single string.

On this page, you’ll learn how LM15 finds the provider and the key for a request. We’ll start by sending one question to four providers. Then we’ll look at model names and what happens when one is wrong, and at the different ways to give LM15 your API key. We’ll finish with models that don’t belong to a provider at all: one running on your own computer, and one on a server you run yourself.

To run the examples, you’ll need LM15 installed; see Make your first request. Each section stands on its own.

The examples in these guides follow one small program: an assistant for a wildlife research station. Here is its first question, sent to four different providers:

question = (
    "What might be eating the acorns under our oak trees at night?"
)

router = LMRouter()
for model in [
    "openai:gpt-5-mini",
    "anthropic:claude-haiku-4-5",
    "gemini:gemini-2.5-flash",
    "deepseek:deepseek-chat",
]:
    request = Request(
        model=model,
        system=(
            "You are the field assistant for a wildlife research "
            "station. Answer in two sentences."
        ),
        messages=[Message.user(question)],
    )
    print(model, "->", router.complete(request).text)

The only thing that changes from one request to the next is the model’s name. The router reads the part before the colon, openai or anthropic, and uses it to decide where to send the request, which key to use, and how to write the request in that provider’s own format. The answer comes back in the same shape every time. Here is what the four providers said:

What came back gpt-5-mini through OpenAI, September 23, 2026
At night acorns are most often eaten by small rodents (mice, voles, packrats/woodrats, and in some places flying squirrels), and by larger nocturnal foragers such as raccoons, opossums, porcupines, foxes, wild pigs or deer—and in bear country bears will take them too. To narrow it down set a motion-activated camera and check for telltale signs (tiny gnaw marks and runways for rodents, half-shell piles and clawed tears for raccoons/possums, large crushed shells and hoof or pig tracks for deer/pigs, or larger scat/tracks for bears).
finish reason
stop
input tokens
40
output tokens
625
What came back claude-haiku-4-5 through Anthropic, September 23, 2026
Based on nocturnal acorn foraging, you're likely dealing with deer, raccoons, or wild boars, though smaller culprits like squirrels, mice, and voles are also possibilities. To narrow it down, look for tracks around the feeding area in soft soil or mud, and check whether the acorns are scattered (suggesting deer) or neatly gnawed (suggesting rodents).
finish reason
stop
input tokens
38
output tokens
89
What came back gemini-2.5-flash through Google Gemini, September 23, 2026
Deer are frequent visitors to oak trees at night, diligently foraging for fallen acorns. You might also find raccoons, opossums, or various rodent species like mice enjoying this abundant food source under the cover of darkness.
finish reason
stop
input tokens
30
output tokens
43
What came back deepseek-chat through DeepSeek, September 23, 2026
The most likely nighttime acorn thieves are deer mice and white-footed mice, along with gray squirrels and flying squirrels that cache acorns after dark. Larger nocturnal visitors like raccoons, opossums, and wild turkeys may also forage beneath the oaks, so look for tracks, droppings, or gnawed shell halves to narrow it down.
finish reason
stop
input tokens
35
output tokens
73

Four models, four answers, all following the same instructions. Look at the input tokens, though: the same question counted as 40, 38, 30, 35 tokens. Each provider splits text into tokens its own way, and adds its own wrapping around your messages, so the same request can cost a different amount depending on where you send it.

To run this loop yourself, you’ll need a key for each of the four providers. If you have just one, shorten the list.

A model name in LM15 has two parts: the provider, then the model’s name as that provider knows it. anthropic:claude-haiku-4-5 means “Claude Haiku 4.5, from Anthropic”. The router removes the prefix before it sends the request, so the provider only ever sees its own name for the model.

The router can often work out the provider from a well-known name on its own (claude-haiku-4-5 goes to Anthropic), but writing the prefix makes it certain, and it’s the only way to reach a model that several providers offer.

Each provider has its own list of models, and a name only means something to the provider that uses it. Ask OpenAI for Anthropic’s model, and OpenAI answers that it doesn’t have it:

What LM15 raised claude-haiku-4-5 through OpenAI, September 24, 2026
UnsupportedModelError: The model `claude-haiku-4-5` does not exist or you do not have access to it. (openai, HTTP 404, request req_286b59262e024b629039e5238064b8bc)

A typo in the model’s name gets the same kind of answer from the provider:

What LM15 raised claude-haiku-9 through Anthropic, September 24, 2026
UnsupportedModelError: model: claude-haiku-9 (anthropic, HTTP 404, request req_011CfNPwggDqv4r9gicYFLj9)

In both cases the request reached the provider, and the message is the provider’s own, passed on by LM15. The kind of error is the same, an UnsupportedModelError, whichever provider answers, so your program can catch it in one place; Handle errors and retries shows how. Model names also change over time, as providers release new models and retire old ones. Each provider’s website lists the models it serves today.

Every provider needs to know who is asking, and that’s what an API key is for. Unless you say otherwise, the router looks for the key in an environment variable named after the provider: OPENAI_API_KEY, ANTHROPIC_API_KEY, GEMINI_API_KEY, and so on. Make your first request shows how to set one. If the variable isn’t set, the router stops before sending anything, and tells you which variable it looked for:

What LM15 raised claude-haiku-4-5 through Anthropic, September 23, 2026
MissingCredentialError: no API key found for provider 'anthropic'. Set ANTHROPIC_API_KEY in the environment, or pass RouterConfig(api_keys={'anthropic': "..."}).

  To fix:
    - Pass the key explicitly (api_key, or RouterConfig api_keys), or on a host with an environment set ANTHROPIC_API_KEY=...
    - Configure credentials for anthropic

Environment variables are a good default, because they keep the key out of your code. Sometimes, though, the key lives somewhere else: in your own settings, a secrets manager, or a variable with a different name. You can then give it to the router yourself, one key per provider:

router = LMRouter(RouterConfig(
    api_keys={"provider": os.environ["STATION_API_KEY"]},
))

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

A key given this way takes priority over the environment. Whatever you do, don’t write the key itself into your code: code gets shared, copied, and committed, and a key in it is a key anyone can use.

Some models are small enough to run on your own computer, with no provider, no key, and no cost per request. Ollama is one of the easiest ways to do that. Once it’s installed, download a model:

Terminal window
ollama pull llama3.2:3b

Ollama then serves it at a local address, and LM15 knows where to find it. The model’s name starts with ollama, and no key is needed:

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

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

What came back llama3.2:3b through Ollama, September 23, 2026
There are several possibilities for what could be eating the acorns under your oak trees at night, depending on the location and time of year. Here are a few potential suspects:

1. **Raccoons**: These clever critters are known to raid oak trees for acorns, especially during the fall. They are nocturnal, so they would likely be active at night.
2. **Chipmunks**: These small rodents are also known to store acorns in their burrows and feed on them throughout the year. They are nocturnal and can be a significant threat to oak acorn crops.
3. **Squirrels**: Gray squirrels, in particular, are notorious for raiding oak trees for acorns. They are active at night and may store some acorns in their nests, but may also feed on them directly.
4. **Deer**: In some areas, deer may visit oak trees at night to eat acorns, especially during the fall when they are more abundant.
5. **Acorn beetles**: These small insects can be a significant threat to oak acorns. They can infest the acorns and feed on the nuts, especially in the early fall.
6. **Birds**: Some bird species, like cedar waxwings, blue jays, and black-capped chickadees, may visit oak trees at night to eat acorns.

It's worth noting that the type of creature eating the acorns could depend on the specific conditions and location. If you're concerned about the acorn loss, it's a good idea to observe the area at night or take a closer look during the day to see what's causing the problem.

Would you like to know more about any of these potential suspects?
finish reason
stop
input tokens
39
output tokens
352

The request never left the computer. That makes local models a good choice for private data, for working offline, and for trying things without paying for each call. The answer also shows their limits: this small model says chipmunks are nocturnal, and that squirrels are active at night, and both are wrong. A model small enough to run on a laptop knows less, and makes more mistakes, than the large models providers run. Check its answers with that in mind.

The same idea works for a model you run on a bigger machine, with a server such as vLLM. LM15 assumes each kind of server is on your own computer, at its usual address. When it isn’t, give the router the address:

router = LMRouter(RouterConfig(
    base_urls={"vllm": "http://gpu-box:8000/v1"},
))

request = Request(
    model="vllm:Qwen/Qwen3-8B",
    messages=[Message.user(
        "What might be eating the acorns under our oak trees at "
        "night?"
    )],
)
response = router.complete(request)
print(response.text)

The request is written the same way as before. Only the router’s idea of where vllm lives has changed, and the model’s name is whatever name your server gives it.

Predict what will happen before you run each one.

  1. Unset ANTHROPIC_API_KEY, and run the loop from One question, four providers. Which answers will you get?
  2. Send openai:gpt-5-mini with the key from GEMINI_API_KEY given explicitly for openai. What will happen?
  3. Ask the local model the station’s follow-up question from Make your first request, without the conversation. Will it do better or worse than the large models?
What to expect
  1. The loop stops at Anthropic, with the missing-key error above: the first answer prints, and the rest never run. To keep going past a provider that fails, catch the error inside the loop.
  2. The router sends the Gemini key to OpenAI, because you told it to. OpenAI refuses it, and LM15 reports an authentication error from OpenAI.
  3. Probably worse. Without the conversation it has no idea which animals you mean, like every model, and a small model is also more likely to guess.

Every provider works the same way from here on: the rest of these guides use the provider and model you choose at the top of the page. To keep a conversation going across several requests, see Keep a conversation.