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.
"Easy or hard" is decided by Leanroute's trained router, trained on 109k real prompts whose cheap-model answers GPT-4 graded: at the default setting about half of traffic goes to the cheap model and 94% of those answers were good enough (how we measured). Streaming works with the wrapper, and streamed answers are costed correctly.
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. - Streaming works as usual; the cost is recorded when the stream ends:
stream = client.chat.completions.create(model="auto", messages=messages, stream=True)
for chunk in stream:
print(chunk.choices[0].delta.content or "", end="")
print(stream.leanroute.route)
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", router="leanroute", router_threshold=0.107))
router_threshold is the savings dial: lower = more cautious. On held-out data, 0.067 sent ~30% of traffic to the cheap model (96% of those answers good enough), 0.107 ~50% (94%), 0.165 ~70% (92%). router="laya" uses Laya's difficulty score instead.
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.2.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.2.0.tar.gz | 21.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| leanroute-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 41.2 kB
Release files / leanroute-0.2.0.tar.gz
| Download URL | leanroute-0.2.0.tar.gz |
|---|---|
| Size | 21.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
461eee397d436bb6e2be225e008f569e5d379ec01b68d803ef7c539be0fcd590
|
|
BLAKE2b-256 checksum How to use checksums |
0a4ed03be36e6c28bfdd47f4bcfef557d266972f8b051d6663edb168ee269a24
|
| 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.2.0-py3-none-any.whl
| Download URL | leanroute-0.2.0-py3-none-any.whl |
|---|---|
| Size | 20.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
cfda802dd505043246b950f08761df9530b9021febe499756633e10197d38fa6
|
|
BLAKE2b-256 checksum How to use checksums |
3cf996a4378df044b30ba485b9613a92d84199ddcd7eeca7d9985e98fc7914e8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.9.6
|