leanroute
A toll gate in front of any LLM. Website: leanroute.online · Source: GitHub
Before your app pays an LLM, Leanroute checks three things, locally and for $0 per call:
- Is this an attack? Prompt injections are blocked by an open-source detector before any LLM call.
- How hard is it? Easy → your cheap model. Hard or sensitive → your strong model.
- Anything else you ask, e.g. "is this spam?", "which team?", "hot lead?" → answered by Laya (Apache 2.0) with a probability, no LLM at all.
Version 0.1 note: this package decides "easy or hard" with Laya's difficulty score. The Leanroute server uses a trained router that was measurably better on real graded answers (results); bringing it to this package is planned for 0.2.
your code ─► leanroute ─┬─ blocked ............ $0
├─ cheap model ........ $
└─ strong model ....... $$$
Install
pip install "leanroute[local]" # runs Laya inside your app (downloads the model once)
pip install leanroute # lightweight: talks to a Leanroute server instead
1. Drop-in: one line in front of your existing client
from openai import OpenAI
from leanroute import Leanroute
lr = Leanroute() # or Leanroute(api_url="http://localhost:8000")
client = lr.wrap(OpenAI(), cheap="gpt-4o-mini", strong="gpt-4o")
r = client.chat.completions.create(
model="auto", # Leanroute picks cheap vs strong
messages=[{"role": "user", "content": "Capital of Australia?"}],
)
print(r.choices[0].message.content, r.leanroute.route) # -> "Canberra", "cheap"
Everything else about the client is unchanged. Works with OpenAI, AsyncOpenAI, any OpenAI-compatible SDK (Groq, Together, OpenRouter, Ollama's /v1), and Anthropic (client.messages.create(model="auto", ...)).
model="auto"→ guard + route.- Any real model name → guard only; your model is used ("pinned").
- Blocked prompts raise
leanroute.Blockedbefore any tokens are billed.
2. Any LLM, any framework: ask for a route
d = lr.route(messages, cheap="small-model", strong="big-model")
if d.blocked:
return "Sorry, I can't help with that."
reply = call_my_llm(model=d.model, messages=messages) # LangChain, LiteLLM, raw HTTP, anything
Or just the guardrail:
from leanroute import Blocked
try:
lr.guard(user_input)
except Blocked as e:
print("blocked:", e.decision.reason)
3. Skip the LLM entirely for simple decisions
from leanroute import yes_no, choice, level
lr.check("FREE crypto signals!!!", "Is this spam?") # -> 0.94
a = lr.decide(ticket_text, {
"team": choice("Which team should handle this?", ["billing", "technical", "sales", "other"]),
"urgent": yes_no("Is there a deadline or time pressure?"),
"anger": level("How frustrated is the customer?", ["calm", "annoyed", "furious"]),
})
a["team"].value, a["team"].confidence, a["urgent"].yes
Savings report
lr = Leanroute(prices={"gpt-4o-mini": (0.15, 0.60), "gpt-4o": (2.50, 10.00)}) # USD per 1M tokens in/out
...
lr.stats.summary()
# {'requests': 120, 'routes': {'cheap': 81, 'strong': 37, 'blocked': 2}, 'saved_usd': 1.84, 'saved_pct': 71.3, ...}
Use your provider's current prices; savings are only as accurate as those numbers.
Tuning
from leanroute import Policy
lr = Leanroute(policy=Policy(guard_mode="precise", detector_threshold=0.66, easy_max=1.2))
Guard modes (measured in eval/results.md):
guard_mode |
What blocks a prompt |
|---|---|
precise (default) |
ProtectAI's open-source prompt-injection detector. Fewest normal requests blocked; never blocked code or math in testing |
broad |
The detector, or Laya when its jailbreak and injection scores are both very high. Catches more role-play jailbreaks, blocks some coding requests |
laya |
Laya's jailbreak and injection scores only |
off |
Nothing |
In local mode the detector runs inside your app (downloaded on first use); with api_url it runs on the server.
Fail-open by default: if the decision layer errors (server down, model not loaded), requests go to your strong model so your app keeps working. Set fail_open=False to raise instead.
Honest limits
Laya decides; it doesn't write. It reads short text (about a page), works best with a handful of options per question, and should be fine-tuned and calibrated on your own data before you trust its thresholds in production. Start conservative and check the routes in lr.stats.
License
Apache 2.0. Built on Laya © Convai Innovations (Apache 2.0).
Metadata
Release files for leanroute 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| leanroute-0.1.0.tar.gz | 16.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| leanroute-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 32.7 kB
Release files / leanroute-0.1.0.tar.gz
| Download URL | leanroute-0.1.0.tar.gz |
|---|---|
| Size | 16.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
942e20e30dbcf5bc314807f214990078d3eb96ebdc5323512db8a858cab6be40
|
|
BLAKE2b-256 checksum How to use checksums |
f80faf03d7a8be001576c5edcdda1c7f5666fefe8577439934ee9f833114edc2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.9.6
|
Release files / leanroute-0.1.0-py3-none-any.whl
| Download URL | leanroute-0.1.0-py3-none-any.whl |
|---|---|
| Size | 16.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c65f78acd6f6b6215150dd83d09cc501924b5a97a01e31e505bd9c40d3d323b7
|
|
BLAKE2b-256 checksum How to use checksums |
fa95778d1cd9ff67e60de1fbd6aef0d81956d93ad54ea3f1b8465765799de808
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.9.6
|