MLPal Gateway
One API for Anthropic, OpenAI, Google, and AWS Bedrock.
Self-hosted. Model routing, per-request cost metering, per-key access control, admin console.
Quick start
cp .env.oss.example .env # add whichever provider keys you have
docker compose up # Postgres + Redis + gateway + admin console
| Gateway | http://localhost:8000 — Swagger at /docs |
| Admin console | http://localhost:8080 |
| Bootstrap admin key | printed once: docker compose logs seed |
Adapters activate based on which provider keys you set. GET /v1/models lists
only what your box can serve; a single-provider deployment works without any
extra configuration.
Calling it
Anthropic wire, any provider's model:
curl http://localhost:8000/v1/messages \
-H "Authorization: Bearer $MLPAL_KEY" -H "content-type: application/json" \
-d '{"model":"mlpal","max_tokens":512,"messages":[{"role":"user","content":"hi"}]}'
Existing OpenAI SDK code — point it at http://localhost:8000/v1. Python SDK —
pip install mlpal-assistants:
from mlpal_assistants import MLPal
client = MLPal(base_url="http://localhost:8000") # key from MLPAL_API_KEY
msg = client.messages.create(
model="mlpal", max_tokens=512,
messages=[{"role": "user", "content": "Hello"}],
)
print(msg.text, msg.compute_units)
What you get
- One wire format for every provider.
POST /v1/messagesspeaks the Anthropic Messages format for all served models; the gateway translates to each provider's native API and relays the SSE stream without re-chunking. OpenAI-compatible endpoints (/v1/chat/completions, embeddings, images, audio) serve existing OpenAI SDK code unchanged. - Router tags.
"model": "mlpal"resolves to the best model your deployment serves, walking a curated candidate list that spans providers. Model retired? Provider key missing? Resolution falls through to the next candidate. Client code never changes. - Cost on every response. Requests are metered in compute units
(1 CU = $10 of provider list price, no markup) and returned in an
X-MLPal-Compute-Unitsheader. The meter reproduces provider list pricing exactly, including prompt-cache discounts. - Per-key control. Model-policy globs (
allow: ["claude-*"]), multi-window spend budgets, permissions — all enforced at admission, before the provider call. A running stream is never cut. - Observability. Per-key cache hit rate, latency p50/p95, time-to-first-token, request traces, and optional payload capture (zlib-compressed, runtime toggle — your box, your data).
- Provider semantics preserved. Prompt caching (
cache_control), tools, structured output, and MCP config pass through untouched.
Measured
Gateway overhead isolated against a zero-latency fake upstream, so provider
variance can't hide anything — MLPal with its full admission pipeline
(auth, rate limit, billing, model policy, budgets, metering, capture) against
LiteLLM in both its bare mode and a production configuration with
database-backed virtual keys, budgets, and spend tracking (N=100 per system;
methodology in the technical report,
raw data and harness in paper/bench/):
+8.5 ms with everything on — less than a bare proxy checks one static key for, and with the tightest tail (p95 33 ms vs 58/41 ms). Admission-time governance is computationally free. Provider semantics survive the hop too: a 22k-token cached prefix passes through byte-faithfully and metered 0.002756 CU on write, 0.000224 CU on read (12.3×) — matching Anthropic's list price to five decimals. The managed deployment of this same codebase, measured the same night from the same client, served claude-haiku-4.5 at 642 ms median TTFT vs OpenRouter's 898 ms (report §5.3–5.4).
Why a curated catalog
Production models retire on roughly a 12-month cycle now — from the providers' own deprecation ledgers:
Serving a model well — valid parameter ranges, per-model cache minimums, reasoning budgets, provider quirks — is per-model engineering that does not scale to a 1,600-entry catalog. This gateway serves a curated set (~75 models) kept current by a data feed, and router tags absorb every retirement above server-side. You can always pin any explicit model tag or register your own adapter.
The full argument with benchmarks and sources: Curation Over Breadth · feature matrix vs. OpenRouter / LiteLLM / Portkey: docs/POSITIONING.md.
Router tags vs. catalog
Two ways to use the curated set — they differ in who picks the model:
Router tags — mlpal, mlpal-flash, mlpal-lite |
Catalog — GET /v1/catalog |
|
|---|---|---|
| Who decides | The gateway: tag resolves to the best served model for the operation | Your client: a ranked list with tiers, capabilities, per-token rates |
| Use when | You want a good default and zero model-name maintenance | You are writing routing logic (agents route sub-tasks this way) |
| One-provider box | Falls through to whatever your key serves | Unserved candidates are marked |
Both are driven by the same feed (catalog/*.json) and update as data, not
code.
API surface
| Endpoint | Purpose |
|---|---|
POST /v1/messages |
Anthropic-wire inference, all providers, streaming SSE |
POST /v1/chat/completions |
OpenAI-compatible chat |
/v1/embeddings · /v1/images/generations · /v1/audio/* |
OpenAI-compatible modalities |
GET /v1/models |
Models this deployment serves |
GET /v1/catalog |
Ranked catalog: tiers, capabilities, rates |
POST /v1/feedback |
Outcome feedback for routing scores |
GET /v1/usage/* · /v1/keys/* |
Self-scoped usage, traces, per-key stats |
/admin/v1/* |
Keys, policies, budgets, capture, routing |
Details: docs/API_SURFACE.md.
Use it with a coding agent
Yodex is a coding CLI built on this
gateway — it speaks the Anthropic wire and uses GET /v1/catalog to route
sub-tasks to cheaper models (~10× lower sub-agent cost in its
benchmarks):
npm install -g @mlpal/yodex
export YODEX_GATEWAY_URL=http://localhost:8000
export YODEX_API_KEY=mlpal_sk_... # minted in the console
yodex "fix the failing test"
Repository layout
src/ # FastAPI gateway: adapters, services, api, seams
console/ # admin UI (React + Vite): keys, traces, catalog, usage
docker-compose.yaml # one-command local deployment
alembic/ # database migrations
paper/ # technical report + benchmark harness + raw results
enterprise/ # commercial add-ons (separate license, NOT Apache)
docs/ # API surface, positioning, figures
Auth and billing sit behind composition-root seams (api/mounting.py); the
defaults (MLPAL_AUTH_BACKEND=local, MLPAL_BILLING_BACKEND=local) run fully
standalone with no external dependencies. src/ never imports from
enterprise/.
License and contact
Apache-2.0, except the enterprise/ directory (commercial — see
enterprise/LICENSE). Contributions welcome:
CONTRIBUTING.md · security and everything else:
contact@mlpal.ai
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 mlpal_gateway-0.2.2.tar.gz.
File metadata
- Download URL: mlpal_gateway-0.2.2.tar.gz
- Upload date:
- Size: 1.2 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.8.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8df34bb39f454dcc00247bbdc26c29abd6752581510b5d61d18e50c59716b3b6
|
|
| MD5 |
ce98135a92699750562382698097e0a8
|
|
| BLAKE2b-256 |
5d4db7f033eee4d657e08c7dfa6c594d1f61ba735bf894a3e85e0cf2ab4f6179
|
File details
Details for the file mlpal_gateway-0.2.2-py3-none-any.whl.
File metadata
- Download URL: mlpal_gateway-0.2.2-py3-none-any.whl
- Upload date:
- Size: 313.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.8.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2a8f4620058892baf07011f9f9e74c0aaf1097ff7350f4cedcb884a9ed8f436c
|
|
| MD5 |
322c63ab4b31d5b6c9a215cba138d988
|
|
| BLAKE2b-256 |
669fb2846ef401cac3be1c32e4520a7526afed2405bc5fce71192986b1443696
|