Smart LLM model router - auto-starts proviz-server binary, no Docker required
Project description
ProvizElekto
Smart LLM model router. Picks the best model for each call based on context size, rate limits, and capabilities - before the call happens.
Your app → ProvizElekto.select() → ModelCandidate → LiteLLM → Provider
↑ ↓
report_success / report_rate_limit / report_error
Key difference from LiteLLM fallback: LiteLLM retries after failure. ProvizElekto picks the right model before the call - skipping models that are rate-limited, can't fit the context, or lack required capabilities.
Features
- Context-aware selection - don't waste a 128k model on a 1k prompt
- Rate-limit avoidance - skips models hit by TPM/RPM limits (in-memory, O(1))
- Capability filtering - hard requirements for function calling, JSON mode
- Quality floor - reject models below a quality threshold per step
- Your keys, your models - curated catalog, no vendor proxy
- Zero-infra -
pip install proviz-elektoauto-starts the Rust server as a subprocess - Any language - HTTP API, not a library binding
- Pluggable storage - SQLite (default) or PostgreSQL
Installation
pip install proviz-elekto
The proviz-server binary is bundled in the wheel.
CLI tool (proviz) is also included:
proviz --help
Quickstart
import os
from proviz_elekto import ProvizElekto
# Auto-starts proviz-server on first use. Stops on process exit.
pz = ProvizElekto(database_url=os.environ["DATABASE_URL"])
# or SQLite: pz = ProvizElekto(db_path="./proviz.db")
# Seed brands and models once
# proviz seed --brands --models
tried = []
while True:
candidate = pz.select(
step="verdict",
estimated_tokens=2500,
requires_json_mode=True,
exclude_ids=tried,
)
try:
import litellm, os
result = litellm.completion(
model=f"{candidate.brand_slug}/{candidate.model_slug}",
messages=[{"role": "user", "content": "..."}],
api_key=os.environ.get(candidate.api_key_env, ""),
)
pz.report_success(candidate.model_id)
break
except litellm.RateLimitError:
pz.report_rate_limit(candidate.model_id, "tpm")
tried.append(candidate.model_id)
except Exception:
pz.report_error(candidate.model_id, "other")
tried.append(candidate.model_id)
Catalog Setup
1. Seed built-in brands and models
# Against a running server (use the port printed by proviz-server on startup)
proviz seed --brands --models --server http://localhost:<PORT>
# Or directly against the database
proviz seed --brands --models --storage postgres --database-url $DATABASE_URL
proviz seed --brands --models --storage sqlite --db-path ./proviz.db
2. Add selection rules per step
# verdict step: cheap model for small inputs, quality model for large
proviz rule add --step verdict --model llama-3.1-8b-instant --priority 1 --max-ctx 8000
proviz rule add --step verdict --model mistral-small-latest --priority 2 --max-ctx 32000
proviz rule add --step verdict --model llama-3.3-70b-versatile --priority 3
# synthesis step: needs quality, context can be very large
proviz rule add --step synthesis --model mistral-small-latest --priority 1 --max-ctx 30000
proviz rule add --step synthesis --model llama-3.3-70b-versatile --priority 2
proviz rule add --step synthesis --model mistral-large-2512 --priority 3
# planner step: cheap + fast
proviz rule add --step planner --model llama-3.1-8b-instant --priority 1
# agentic step: requires function calling
proviz rule add --step agentic --model mistral-small-latest --priority 1 --fn-call
proviz rule add --step agentic --model mistral-large-2512 --priority 2 --fn-call
# detector step: fast, small context
proviz rule add --step detector --model llama-3.1-8b-instant --priority 1 --max-ctx 8000
proviz rule add --step detector --model mistral-small-latest --priority 2
3. Add a custom model
proviz model add \
--brand mistral \
--slug mistral-small-latest \
--max-ctx 32000 \
--price-in 0.10 --price-out 0.30 \
--json-mode --function-calling \
--quality 0.65 --latency-ms 400
# Or bulk import from JSON
proviz model import --file catalog.json
4. Dry-run a selection
proviz select --step verdict --tokens 2500 --json-mode
# selected:
# brand: groq
# model: llama-3.1-8b-instant
# model_id: b3f1...
# api_key_env:GROQ_API_KEY
# max_ctx: 128000
# est_cost: $0.000125
Selection Algorithm
On every select() call (in-memory, ~microseconds):
- Load rules for step from cache (refreshed every 5 min or on
/catalog/reload) - Filter:
rule.is_enabled AND model.is_enabled AND brand.is_active - Filter:
model.max_context_tokens >= estimated_tokens - Filter: if
rule.max_ctx_tokensset →estimated_tokens <= rule.max_ctx_tokens(avoid overkill) - Filter: capability requirements (function calling, JSON mode)
- Filter:
quality_score >= quality_min(if set) - Filter:
model_id NOT IN exclude_ids(already tried this call) - Filter: not rate-limited (in-memory DashMap, O(1), TTL per error type)
- Sort by
priority ASC - Return first match or
409 AllModelsExhausted
Rate limit TTLs
| Error type | Cooldown |
|---|---|
tpm (tokens/min) |
60s |
rpm (requests/min) |
60s |
tpd (tokens/day) |
3600s |
auth |
300s |
timeout |
30s |
parse |
0s (logged, model not blocked) |
other |
60s |
HTTP API
ProvizElekto exposes a local HTTP server. Any language can use it.
Port handshake
The server binds to an OS-assigned ephemeral port by default (no port conflicts). After binding, it prints exactly one line to stdout before any other output:
PROVIZ_PORT=43912
All logs go to stderr. Clients must read this line to discover the port.
To force a specific port, set PROVIZ_PORT=63130 (env) or pass --port 63130 (CLI). The handshake line is still printed — clients always read it.
Spawning from any language:
start: proviz-server --port 0 [--storage ...] [--db-path ...]
read stdout line 1 → "PROVIZ_PORT=<n>"
parse port → use http://localhost:<n>/...
POST /select
{
"step": "verdict",
"estimated_tokens": 2500,
"requires_fn_call": false,
"requires_json_mode": true,
"quality_min": 0.6,
"exclude_ids": []
}
Response 200:
{
"model_id": "b3f1...",
"brand_slug": "groq",
"model_slug": "llama-3.3-70b-versatile",
"api_key_env": "GROQ_API_KEY",
"max_context_tokens": 128000,
"supports_function_calling": true,
"supports_json_mode": true,
"estimated_input_cost_usd": 0.00148
}
Response 409 (all candidates exhausted):
{ "error": "all_models_exhausted", "step": "verdict", "tried": 3 }
POST /report
{
"model_id": "b3f1...",
"outcome": "rate_limit",
"error_type": "tpm"
}
outcome: success | rate_limit | error
error_type: tpm | rpm | tpd | auth | timeout | parse | other
GET /health
{ "status": "ok", "uptime_secs": 3600 }
POST /catalog/reload
Hot-reload catalog from DB without restart.
{ "status": "ok", "models_loaded": 12, "rules_loaded": 28 }
Running the Server Manually
# SQLite (default, zero-infra) — port assigned by OS, printed to stdout
proviz-server --storage sqlite --db-path ./proviz.db
# Force a specific port
proviz-server --storage sqlite --db-path ./proviz.db --port 63130
# PostgreSQL (shares existing DB - tables are pz_* prefixed)
proviz-server --storage postgres --database-url "postgresql://user:pass@host/db"
# Via env vars
PROVIZ_STORAGE=postgres PROVIZ_DATABASE_URL=postgresql://... proviz-server
PROVIZ_PORT=63130 proviz-server # force port
In all cases, the server prints PROVIZ_PORT=<n> to stdout immediately after binding.
Data Model
Brands (pz_brands)
| Field | Type | Description |
|---|---|---|
id |
UUID | Primary key |
slug |
string | groq, mistral, openai, gemini, together, ollama |
name |
string | Display name |
api_key_env |
string? | Env var holding the API key (GROQ_API_KEY) |
base_url |
string? | Optional API base URL override |
plan |
string? | Plan for a provider if exists, for example free or dev for groq (default free) |
is_active |
bool | Disable an entire provider without deleting |
Models (pz_models)
| Field | Type | Description |
|---|---|---|
id |
UUID | Primary key |
brand_id |
UUID | FK → pz_brands |
slug |
string | Actual API model name sent to provider |
max_context_tokens |
int | Hard context window limit |
max_output_tokens |
int? | Max output tokens |
supports_function_calling |
bool | Required for agentic steps |
supports_json_mode |
bool | Required for verdict/synthesis |
price_input_per_1m |
float? | USD per 1M input tokens |
price_output_per_1m |
float? | USD per 1M output tokens |
tpm_limit |
int? | Provider tokens/minute rate limit |
rpm_limit |
int? | Provider requests/minute rate limit |
rpd_limit |
int? | Provider requests/day rate limit |
tpd_limit |
int? | Provider tokens/day limit |
tpm_limit_month |
int? | Provider tokens/month limit |
rps_limit |
float? | Provider requests/second limit |
quality_score |
float? | 0.0–1.0 internal benchmark |
avg_latency_ms |
int? | Known/estimated median latency |
is_enabled |
bool | Disable a model without deleting |
Selection Rules (pz_selection_rules)
| Field | Type | Description |
|---|---|---|
step |
string | Pipeline step name |
model_id |
UUID | FK → pz_models |
priority |
int16 | Lower = preferred |
max_ctx_tokens |
int? | Only eligible when estimated_tokens ≤ this |
requires_fn_call |
bool | Safety check (also filtered by model capability) |
is_enabled |
bool | Disable rule without deleting |
Building from Source
git clone https://github.com/YOUR_ORG/proviz-elekto
cd proviz-elekto
# Build server + CLI
cargo build --release
# Run server
./target/release/proviz-server --storage sqlite --db-path ./dev.db
# Run CLI
./target/release/proviz --help
# Build Python wheel (requires maturin)
pip install maturin
cd python && maturin build --release
Supported Providers (built-in seed)
| slug | Name |
|---|---|
groq |
Groq |
mistral |
Mistral AI |
ollama |
Ollama |
Add any provider supported by LiteLLM via proviz brand add.
License
Apache-2.0
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 Distributions
Built Distributions
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 proviz_elekto-0.1.3-py3-none-win_amd64.whl.
File metadata
- Download URL: proviz_elekto-0.1.3-py3-none-win_amd64.whl
- Upload date:
- Size: 2.7 MB
- Tags: Python 3, Windows x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
54a26c33a952cf06bb34a3b99e9998a76e73d216b72f82f094685c4821cfbc31
|
|
| MD5 |
20a88a07d17012badbc8a443f28c71e0
|
|
| BLAKE2b-256 |
f3152721c48288e0884878a4ae3a810f93616a9375cca836ca226ba688f468d1
|
Provenance
The following attestation bundles were made for proviz_elekto-0.1.3-py3-none-win_amd64.whl:
Publisher:
release.yml on JustGui/proviz-elekto
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
proviz_elekto-0.1.3-py3-none-win_amd64.whl -
Subject digest:
54a26c33a952cf06bb34a3b99e9998a76e73d216b72f82f094685c4821cfbc31 - Sigstore transparency entry: 1621149057
- Sigstore integration time:
-
Permalink:
JustGui/proviz-elekto@031abdd3aedb870d3b3a15722314a5c6eac602cc -
Branch / Tag:
refs/tags/v0.1.3 - Owner: https://github.com/JustGui
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@031abdd3aedb870d3b3a15722314a5c6eac602cc -
Trigger Event:
push
-
Statement type:
File details
Details for the file proviz_elekto-0.1.3-py3-none-musllinux_1_2_x86_64.whl.
File metadata
- Download URL: proviz_elekto-0.1.3-py3-none-musllinux_1_2_x86_64.whl
- Upload date:
- Size: 3.3 MB
- Tags: Python 3, musllinux: musl 1.2+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c1c44d5deb00c72c7313e196f498745b2c7f36791318484432b3fc7a02e86638
|
|
| MD5 |
e43e200b7b8f260a4d26a31ab442dbc6
|
|
| BLAKE2b-256 |
2abe8e380324c66509fe1c3aee6b5f454de6f52b06c2c37b5b0acd7b57e058d6
|
Provenance
The following attestation bundles were made for proviz_elekto-0.1.3-py3-none-musllinux_1_2_x86_64.whl:
Publisher:
release.yml on JustGui/proviz-elekto
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
proviz_elekto-0.1.3-py3-none-musllinux_1_2_x86_64.whl -
Subject digest:
c1c44d5deb00c72c7313e196f498745b2c7f36791318484432b3fc7a02e86638 - Sigstore transparency entry: 1621149355
- Sigstore integration time:
-
Permalink:
JustGui/proviz-elekto@031abdd3aedb870d3b3a15722314a5c6eac602cc -
Branch / Tag:
refs/tags/v0.1.3 - Owner: https://github.com/JustGui
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@031abdd3aedb870d3b3a15722314a5c6eac602cc -
Trigger Event:
push
-
Statement type:
File details
Details for the file proviz_elekto-0.1.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.
File metadata
- Download URL: proviz_elekto-0.1.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
- Upload date:
- Size: 3.0 MB
- Tags: Python 3, manylinux: glibc 2.17+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1b8caa544e52a8ee1cf2514389db4f3c70d9f2707d34f97283fa7827fbce127d
|
|
| MD5 |
1ca638c1c2617c6080ef5c1d95cdfe90
|
|
| BLAKE2b-256 |
badc69494c2af39df3cf350ceb6bd6683e95660bb202a1f407a0c395fabf5fa3
|
Provenance
The following attestation bundles were made for proviz_elekto-0.1.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:
Publisher:
release.yml on JustGui/proviz-elekto
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
proviz_elekto-0.1.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl -
Subject digest:
1b8caa544e52a8ee1cf2514389db4f3c70d9f2707d34f97283fa7827fbce127d - Sigstore transparency entry: 1621149149
- Sigstore integration time:
-
Permalink:
JustGui/proviz-elekto@031abdd3aedb870d3b3a15722314a5c6eac602cc -
Branch / Tag:
refs/tags/v0.1.3 - Owner: https://github.com/JustGui
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@031abdd3aedb870d3b3a15722314a5c6eac602cc -
Trigger Event:
push
-
Statement type:
File details
Details for the file proviz_elekto-0.1.3-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl.
File metadata
- Download URL: proviz_elekto-0.1.3-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl
- Upload date:
- Size: 5.9 MB
- Tags: Python 3, macOS 10.12+ universal2 (ARM64, x86-64), macOS 10.12+ x86-64, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7594a75efcddc962eea4ae4b07ef997fa491b689d48a19ecbc029c56feba2bc8
|
|
| MD5 |
4a1807d1b25c63e33bedbe579d2a0e7d
|
|
| BLAKE2b-256 |
bb71a0e879473aa226600402a7e00983019ab714e79dbc3a9690342787b5f5b3
|
Provenance
The following attestation bundles were made for proviz_elekto-0.1.3-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl:
Publisher:
release.yml on JustGui/proviz-elekto
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
proviz_elekto-0.1.3-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl -
Subject digest:
7594a75efcddc962eea4ae4b07ef997fa491b689d48a19ecbc029c56feba2bc8 - Sigstore transparency entry: 1621149256
- Sigstore integration time:
-
Permalink:
JustGui/proviz-elekto@031abdd3aedb870d3b3a15722314a5c6eac602cc -
Branch / Tag:
refs/tags/v0.1.3 - Owner: https://github.com/JustGui
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@031abdd3aedb870d3b3a15722314a5c6eac602cc -
Trigger Event:
push
-
Statement type: