A semantic LLM router for OpenAI-compatible chat completions.
Project description
route67
route67 is a LLM router for OpenAI-compatible chat
completions format. It uses a user-defined routing table for user defined question-model routing via semantic similarity, as a fallback a weak model answer or explicitly escalate to a strong model.
How it works
flowchart LR
Q["User request"] --> R{"Semantic route match?"}
R -- Yes --> M["Configured weak or strong model"]
R -- No --> W["Weak model gate<br/>usage notes + strong-route examples"]
W -- Answers --> O["Response"]
W -- ESCALATE --> S["Strong model"]
M --> O
S --> O
Install
route67 requires Python 3.10 or newer. Choose either the standard Python workflow
or the uv workflow.
Using python -m venv
Create and activate a virtual environment:
python -m venv .venv
# Windows PowerShell
.\.venv\Scripts\Activate.ps1
# macOS/Linux
source .venv/bin/activate
Then install route67 and its dependencies:
python -m pip install --upgrade pip
python -m pip install -e .
To also install the test dependencies, use python -m pip install -e ".[test]".
Using uv
With uv installed, create the environment and
install the project from the lockfile:
uv sync
Run commands inside the environment with uv run, for example
uv run python example.py. To include test dependencies, use
uv sync --extra test.
Get started
Set an OpenAI API key in your environment:
# Windows PowerShell
$env:OPENAI_API_KEY = "your-api-key"
# macOS/Linux
export OPENAI_API_KEY="your-api-key"
Create example.py:
from llm_router import Controller, ModelSpec, RouterConfig, RoutingTableEntry
config = RouterConfig(
routing_table=[
RoutingTableEntry(
"Prove this theorem",
"strong_model",
notes="Requires a rigorous multi-step proof.",
),
RoutingTableEntry("Rewrite this paragraph", "weak_model"),
],
weak_model=ModelSpec(
"gpt-5-mini",
usage_notes="Avoid difficult multi-step proofs.",
),
strong_model=ModelSpec(
"gpt-5",
usage_notes="Use for rigorous proofs and difficult reasoning.",
),
embedding_cache_path=".cache/routes",
log_path=".cache/routing.jsonl",
)
client = Controller(config)
response = client.chat.completions.create(
messages=[{"role": "user", "content": "Prove that sqrt(2) is irrational."}]
)
print(response.choices[0].message.content)
Run it with the activated standard virtual environment:
python example.py
Or with uv:
uv run python example.py
OpenAI-compatible providers
route67 can use any provider exposed through an OpenAI-compatible client. Create the provider's client normally and inject it into the controller. Model names in the routing configuration are passed to that provider unchanged.
For example, with OpenRouter:
import os
from openai import OpenAI
from llm_router import Controller, ModelSpec, RouterConfig, RoutingTableEntry
openrouter = OpenAI(
base_url="https://openrouter.ai/api/v1",
api_key=os.environ["OPENROUTER_API_KEY"],
)
config = RouterConfig(
routing_table=[
RoutingTableEntry(
"Answer questions about a country",
"weak_model",
),
RoutingTableEntry(
"Solve a difficult reasoning or math problem",
"strong_model",
notes="Requires careful multi-step reasoning.",
),
],
weak_model=ModelSpec(
"openai/gpt-4.1-mini",
usage_notes="Best for straightforward factual and writing questions.",
),
strong_model=ModelSpec(
"deepseek/deepseek-v4-flash",
usage_notes="Use for difficult reasoning, mathematics, and verification.",
),
)
client = Controller(config, openai_client=openrouter)
response = client.chat.completions.create(
messages=[
{
"role": "user",
"content": "How many r's are in the word 'strawberry'?",
}
],
extra_body={"reasoning": {"enabled": True}},
)
Provider-specific request options such as extra_body and extra_headers are
forwarded unchanged. Provider-specific response fields, including
reasoning_details, are also preserved. To continue a provider's reasoning,
pass its assistant message fields back unmodified in the next request.
Routing table entries target only "weak_model" or "strong_model". Provider
model names live in ModelSpec, so switching models or providers does not
require rewriting the routing table.
ModelSpec.usage_notes are added to the weak model's escalation system prompt.
The prompt also includes up to five routing-table entries targeting
"strong_model" as examples of requests that should be escalated. Add concise
notes to those entries when the reason for escalation is useful context.
Your first request will download the minishlab/potion-base-8M from HuggingFace. The model is lazy-loaded,
so constructing a controller with an empty routing table does not download it.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file route67-0.1.0.tar.gz.
File metadata
- Download URL: route67-0.1.0.tar.gz
- Upload date:
- Size: 80.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8669ee902558482764383af6f77dc0bbc5b1683f65dec013954c95b3da1f0ec5
|
|
| MD5 |
3002c386197c87764ca2dee6947cbe9b
|
|
| BLAKE2b-256 |
98e408fd2f32582efdeaf420963837a9b8858abbd8034e65a95b04355d8b92f4
|
File details
Details for the file route67-0.1.0-py3-none-any.whl.
File metadata
- Download URL: route67-0.1.0-py3-none-any.whl
- Upload date:
- Size: 12.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ccee9652df8170cd0e38fa0866bdf658a2075d5f2dd1e6f53913c0e002d58369
|
|
| MD5 |
6b12a33bf1531e782f56390f1df7f537
|
|
| BLAKE2b-256 |
6ad622923b41e2262fd09ef70db55418e1df4e20e972677c1b812d14b33ec6dc
|