Drop-in LLM API proxy that routes requests to the cheapest capable model
Project description
Kestrel
Drop-in LLM API proxy that routes requests to the cheapest capable model.
Kestrel sits between your AI agent and LLM providers. It intercepts every outgoing API request, classifies the complexity of the prompt, and automatically routes it to the cheapest model that can handle it. You change one line of code — your base URL — and start saving 50-80% on LLM API costs. The response format is identical. Streaming works. Function calling works. Your agent doesn't know routing happened.
# One-line integration: just swap the base URL
client = openai.OpenAI(
base_url="https://api.usekestrel.io/v1",
api_key="ks-your-key",
)
Managed service: usekestrel.io — sign up, add your provider keys, and start saving.
How It Works
- Receive — Your agent sends a request to Kestrel instead of directly to OpenAI/Anthropic/etc.
- Analyze — Extract features: prompt length, analytical/technical keywords, tool presence, domain keywords, code blocks, conversation depth
- Score — Rate complexity across 5 dimensions (reasoning depth, output complexity, domain specificity, instruction nuance, error tolerance)
- Route — Map the score to a tier (Economy/Standard/Premium) and pick the cheapest model that fits, never exceeding the model you specified
- Forward — Translate the request to the selected provider's format, forward it, translate the response back to OpenAI format
Quick Start
Using the managed service
The fastest way to get started — no infrastructure to manage:
- Sign in at app.usekestrel.io with GitHub or Google
- Add your provider API keys and generate a Kestrel key
- Change your base URL:
from openai import OpenAI
client = OpenAI(
base_url="https://api.usekestrel.io/v1",
api_key="ks-your-key",
)
response = client.chat.completions.create(
model="gpt-4o", # Kestrel may route to a cheaper model
messages=[{"role": "user", "content": "Hello"}],
)
See the docs for more examples.
Self-hosted
cd packages/core
uv sync --all-extras
cp ../../.env.example ../../.env
# Edit .env — add at least one provider API key
KS_DEV_MODE=true KS_DEV_OPENAI_API_KEY=sk-... uv run kestrel serve --reload
Docker
git clone https://github.com/andber6/kestrel.git
cd kestrel
cp .env.example .env
# Edit .env — add at least one provider API key
docker compose up
Test it
curl http://localhost:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer sk-your-openai-key" \
-d '{
"model": "gpt-4o",
"messages": [{"role": "user", "content": "What is 2+2?"}]
}'
Python SDK
pip install kestrel-sdk
import kestrel_sdk
client = kestrel_sdk.Client(
api_key="ks-your-kestrel-key",
provider_key="sk-your-openai-key",
base_url="https://api.usekestrel.io/v1",
)
response = client.chat.completions.create(
model="gpt-4o", # ceiling — Kestrel may route to a cheaper model
messages=[{"role": "user", "content": "What is 2+2?"}],
)
print(response.choices[0].message.content)
Async:
import kestrel_sdk
client = kestrel_sdk.AsyncClient(
api_key="ks-your-key",
provider_key="sk-your-openai-key",
base_url="https://api.usekestrel.io/v1",
)
response = await client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Hello!"}],
)
Supported Providers
| Provider | Models | Format |
|---|---|---|
| OpenAI | gpt-4o, gpt-4o-mini, o1, o3, o4 | Native (pass-through) |
| Anthropic | claude-opus-4-6, claude-sonnet-4-6, claude-haiku-4-5 | Full translation (Messages API) |
| Google Gemini | gemini-2.5-pro, gemini-2.5-flash | Full translation (generateContent API) |
| Groq | llama-3.1-8b/70b, mixtral, gemma | OpenAI-compatible (field stripping) |
| xAI | grok-3, grok-3-mini, grok-4 | OpenAI-compatible (field stripping) |
| Mistral | mistral-large, mistral-small, codestral | OpenAI-compatible (field stripping) |
| Cohere | command-r-plus, command-r, command-light | Full translation (Chat V2 API) |
| Together AI | meta-llama/, mistralai/, qwen/* | OpenAI-compatible (field stripping) |
All providers expose the same OpenAI-compatible API. Send any supported model name and Kestrel auto-detects the provider and handles format translation.
Routing
Every request is scored across 5 dimensions (each 1-5):
| Dimension | What it measures | Low score example | High score example |
|---|---|---|---|
| Reasoning Depth | Multi-step logic needed | "What is 2+2?" | "Analyze strategic implications of..." |
| Output Complexity | Structure of expected response | Yes/no answer | Multi-section report with code |
| Domain Specificity | Specialized knowledge required | General chat | Legal/medical/financial analysis |
| Instruction Nuance | Precision of instruction following | Simple question | Complex system prompt with tools |
| Error Tolerance | Cost of imperfect response | Draft note | Production code, legal document |
The composite score (5-25) maps to a tier:
| Score | Tier | Example Models |
|---|---|---|
| 5-8 | Economy | gpt-4o-mini, claude-haiku, gemini-flash, llama-3.1-8b |
| 9-14 | Standard | gpt-4o-mini, claude-haiku, gemini-flash, mistral-small |
| 15-25 | Premium | gpt-4o, claude-sonnet, gemini-pro, grok-3 |
The model you specify is the ceiling. If you send model=gpt-4o (Premium), a simple prompt may route to gpt-4o-mini (Economy). If you send model=gpt-4o-mini (Standard), the request will never route to a more expensive model.
Reliability
- Failover with backoff — If a provider returns 429/5xx or times out, Kestrel retries with a fallback provider using exponential backoff with jitter (up to 2 retries)
- Cross-provider fallback —
gpt-4ocan fall back toclaude-sonnet-4-6orgemini-2.5-proif OpenAI is down - Health monitoring — Background health checks ping all 8 providers every 30 seconds. Unhealthy providers are skipped during routing
- Non-blocking logging — Request logs are written asynchronously. A database outage never blocks API requests
Health endpoints
| Endpoint | Purpose |
|---|---|
GET /health |
Liveness probe — always returns {"status": "ok"} |
GET /ready |
Readiness probe — checks database connectivity, reports provider health. Returns 503 if DB is unreachable |
CLI
kestrel serve # Start the proxy server
kestrel serve --port 9090 --reload # Custom port with auto-reload
kestrel key generate --name "my-app" # Generate an API key
kestrel key generate --name "multi" \
--openai-key sk-... \
--anthropic-key sk-ant-... \
--xai-key xai-... # Key with multiple providers
kestrel key list # List all API keys
kestrel key revoke ks-xxxxx... # Revoke a key
kestrel migrate # Run database migrations
kestrel logs prune --older-than 30d # Delete old request logs
kestrel logs prune --older-than 7d --dry-run # Preview deletion count
kestrel --version # Show version
Configuration
Copy .env.example to .env and configure:
| Variable | Default | Description |
|---|---|---|
KS_DEV_MODE |
false |
Bypass auth, use dev API keys |
KS_ROUTING_ENABLED |
true |
Enable/disable automatic routing |
KS_ENCRYPTION_KEY |
— | Fernet key for encrypting provider keys at rest |
KS_DEV_OPENAI_API_KEY |
— | OpenAI API key (dev mode) |
KS_DEV_ANTHROPIC_API_KEY |
— | Anthropic API key (dev mode) |
KS_DEV_GEMINI_API_KEY |
— | Gemini API key (dev mode) |
KS_DEV_GROQ_API_KEY |
— | Groq API key (dev mode) |
KS_DEV_XAI_API_KEY |
— | xAI API key (dev mode) |
KS_DEV_MISTRAL_API_KEY |
— | Mistral API key (dev mode) |
KS_DEV_COHERE_API_KEY |
— | Cohere API key (dev mode) |
KS_DEV_TOGETHER_API_KEY |
— | Together AI API key (dev mode) |
KS_ROUTING_TIER_FLOOR |
— | Minimum tier (economy, standard, premium) |
KS_ROUTING_TIER_CEILING |
— | Maximum tier |
See .env.example for the full list.
Architecture
packages/core/src/kestrel/
app.py # FastAPI application factory
config.py # Settings (KS_ env vars)
cli.py # CLI (serve, key, migrate)
providers/ # LLM provider adapters (8 providers)
openai.py # OpenAI (native pass-through)
anthropic.py # Anthropic (full format translation)
gemini.py # Google Gemini (full format translation)
groq.py # Groq (OpenAI-compatible, field stripping)
xai.py # xAI Grok (OpenAI-compatible, field stripping)
mistral.py # Mistral (OpenAI-compatible, field stripping)
cohere.py # Cohere (full format translation)
together.py # Together AI (OpenAI-compatible, field stripping)
base.py # Abstract LLMProvider interface
openai_compat.py # Shared base for OpenAI-format APIs
routing/ # Complexity analysis and model selection
analyzer.py # Extract features from requests (structural + text-level)
scorer.py # Rule-based 5-dimension scoring
tier_resolver.py # Score → tier with ceiling logic
model_selector.py # Tier → concrete model selection
engine.py # Orchestrates the routing pipeline
services/ # Business logic
proxy.py # Request forwarding with failover
provider_registry.py # Model → provider mapping, health tracking
health_check.py # Background provider health monitoring
request_log.py # Async request/response logging
auth/ # API key authentication, encryption
models/ # Pydantic models (OpenAI format), DB models
routes/ # FastAPI route handlers
middleware/ # Request ID, timing
Development
Requires Python 3.12+ and uv.
cd packages/core
uv sync --all-extras # Install all dependencies
# Install pre-commit hook (auto-formats with ruff)
./scripts/install-hooks.sh
uv run ruff check src/ # Lint
uv run ruff format src/ # Format
uv run mypy src/ # Type check (strict)
uv run pytest -v # Test with coverage
167 tests, all mocked — no real API calls.
Contributing
See CONTRIBUTING.md for development setup, code style, and how to add new providers.
License
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 kestrel_ai-0.2.0.tar.gz.
File metadata
- Download URL: kestrel_ai-0.2.0.tar.gz
- Upload date:
- Size: 128.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5c9803bf3d94a63427566af83baa9bcde8f77bd3ee4ab86e2314add8d943614b
|
|
| MD5 |
63aeac6da2d5112538acef373b616a84
|
|
| BLAKE2b-256 |
3970dba5638ca8c04f3b8692fa96d1b363ea31e2904c21a8df187840229c4c71
|
Provenance
The following attestation bundles were made for kestrel_ai-0.2.0.tar.gz:
Publisher:
publish.yml on andber6/kestrel
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
kestrel_ai-0.2.0.tar.gz -
Subject digest:
5c9803bf3d94a63427566af83baa9bcde8f77bd3ee4ab86e2314add8d943614b - Sigstore transparency entry: 1253310098
- Sigstore integration time:
-
Permalink:
andber6/kestrel@5d9f643da0afbd654f8e1d7b7b3f9f68ea4b6004 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/andber6
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@5d9f643da0afbd654f8e1d7b7b3f9f68ea4b6004 -
Trigger Event:
release
-
Statement type:
File details
Details for the file kestrel_ai-0.2.0-py3-none-any.whl.
File metadata
- Download URL: kestrel_ai-0.2.0-py3-none-any.whl
- Upload date:
- Size: 54.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e9e0ca41bd5d1bef63ff01d6b3d35f8ff44aa95eea4cfc8781a233de71792a31
|
|
| MD5 |
c035d00f917dd3e24416dc9bddd62c7b
|
|
| BLAKE2b-256 |
4be4bac6cee010e9fa07dea7e2f1bdfc3b4010b6533e366cfb24cda48cf9a1d6
|
Provenance
The following attestation bundles were made for kestrel_ai-0.2.0-py3-none-any.whl:
Publisher:
publish.yml on andber6/kestrel
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
kestrel_ai-0.2.0-py3-none-any.whl -
Subject digest:
e9e0ca41bd5d1bef63ff01d6b3d35f8ff44aa95eea4cfc8781a233de71792a31 - Sigstore transparency entry: 1253310213
- Sigstore integration time:
-
Permalink:
andber6/kestrel@5d9f643da0afbd654f8e1d7b7b3f9f68ea4b6004 -
Branch / Tag:
refs/tags/v0.2.0 - Owner: https://github.com/andber6
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@5d9f643da0afbd654f8e1d7b7b3f9f68ea4b6004 -
Trigger Event:
release
-
Statement type: