# Cortiq Gateway **English** · [Русский](README.ru.md) [![License: Apache 2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE) ![Rust](https://img.shields.io/badge/Rust-2021-orange.svg) ![Status](https://img.shields.io/badge/status-active-success.svg) **A universal LLM gateway with intelligent routing.** One OpenAI-compatible endpoint → automatic model selection from your pool (cheap local + expensive hosted) based on the task type and complexity decided by [allaigate / cortiq-router](https://api.allaigate.com). > In your agent/SDK you only change `base_url` — and you get "smart" routing across > models. No model-selection logic on the client side. ``` ┌─────────────┐ OpenAI / Anthropic / MCP ┌──────────────────┐ │ Agent / │ ─────────────────────────────▶│ Cortiq Gateway │ │ developer │ (standard protocol) │ (this project) │ └─────────────┘ ◀─────────────────────────────└──────────────────┘ response + metadata │ │ │ /v1/route│ call LLM ▼ ▼ ┌──────────────┐ ┌─────────────────────┐ │ cortiq-router│ │ Model pool │ │ (task type, │ │ • local llama.cpp │ │ complexity) │ │ • ollama / vLLM │ └──────────────┘ │ • OpenAI / Claude │ └─────────────────────┘ ``` --- ## ✨ Highlights - **Drop-in OpenAI API.** Point any OpenAI client at the gateway and send `model: "cortiq-auto"`. - **Intelligent routing.** `complexity.tier` → ordered model pool (`low → local`, `high → cloud`), with fallback and graceful degradation when the router is down. - **Built-in multilingual admin console.** Manage models, routing, protocols, keys and secrets from a web UI — **no hand-editing TOML, no restart** (changes are hot-reloaded). Available in **7 languages** (en, ru, de, fr, es, zh, tr) with light/dark themes. - **Live analytics.** Per-request stats (tokens, cost, latency, success rate, failovers), time-series charts, breakdowns by model/tier/task, and a Prometheus `GET /metrics` endpoint. - **Playground.** Send a prompt through the live pipeline and inspect the routing decision. - **Single self-contained binary.** The SPA is embedded into the Rust binary — nothing extra to deploy. - **Secrets stay in the gateway.** Agents hold only a virtual gateway key; provider keys live in the gateway and never leak to clients. --- ## 🖥️ Admin console ![Dashboard](docs/screenshots/dashboard.png) The gateway ships with an embedded web console at **`/admin`**: | | | |---|---| | ![Models](docs/screenshots/models.png) | ![Routing](docs/screenshots/routing.png) | | **Models** — add/edit/probe models, manage provider keys | **Routing** — visual tier editor (ordered, drag-to-reorder) | | ![Playground](docs/screenshots/playground.png) | ![Dashboard](docs/screenshots/dashboard.png) | | **Playground** — test the live pipeline, see the decision | **Dashboard** — traffic, cost, latency analytics | ```bash cargo run --release -- --config config/gateway.toml --admin-token # open http://localhost:9000/admin?token= ``` If `--admin-token` / `[admin].token_env` are not set, a token is generated at startup and printed to the log. All `/admin/api/*` endpoints require a Bearer admin token; secret values are never returned by the API (only their presence: `store` / `env` / `missing`). --- ## 🚀 Quick start ```bash # 1. start a router (the hosted allaigate router, or a local cortiq-router) # it listens e.g. on http://localhost:8080 (or https://api.allaigate.com) # 2. describe your model pool cp config/gateway.example.toml config/gateway.toml $EDITOR config/gateway.toml # 3. run the gateway cargo run --release -- --config config/gateway.toml # Gateway listens on 0.0.0.0:9000 and serves the admin console at /admin ``` Now any OpenAI client works through the gateway: ```python from openai import OpenAI client = OpenAI(base_url="http://localhost:9000/v1", api_key="sk-gw-...") resp = client.chat.completions.create( model="cortiq-auto", # ← magic model = "choose for me" messages=[{"role": "user", "content": "Solve x^2 - 5x + 6 = 0"}], ) print(resp.choices[0].message.content) # response headers show what the gateway picked: # X-Cortiq-Task-Label: math # X-Cortiq-Complexity-Tier: low # X-Cortiq-Selected-Model: local-qwen ``` `model: "cortiq-auto"` enables routing. Any **real** model name from the config (`"gpt-4o-mini"`, `"local-qwen"`) is a direct passthrough, without routing. > **Using the hosted allaigate router?** Set `url = "https://138.226.222.209"`, > `verify_tls = false`, `taxonomy_id = "data-assistant"`, and a `cortiq_…` key in > `CORTIQ_ROUTER_KEY`. On hard prompts the router escalates to an oracle (~10 s), so > set `timeout_ms = 12000+` — otherwise the gateway gracefully degrades to the default model. --- ## Request flow 1. Client → `POST /v1/chat/completions` (or Anthropic/MCP) with `model: "cortiq-auto"`. 2. The gateway extracts the **text to route on** (strategy is configurable, see [docs/ROUTING.md](docs/ROUTING.md)). 3. Gateway → `cortiq-router /v1/route` → gets `task_label` + `complexity.tier`. 4. Gateway selects a model from the pool via the routing table (with a fallback order). 5. Gateway → the selected model's provider (translating the protocol if needed), returns the response. 6. Gateway attaches routing metadata in headers/`usage` and records cost & statistics. Details — [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md). --- ## Supported protocols (inbound) Each adapter is toggled in the config — take only what you need. | Protocol | Endpoint | Status | |---|---|---| | OpenAI Chat Completions | `POST /v1/chat/completions` | ✅ implemented | | OpenAI Completions (legacy) | `POST /v1/completions` | planned | | OpenAI Embeddings | `POST /v1/embeddings` | planned | | OpenAI Models | `GET /v1/models` | planned | | Anthropic Messages | `POST /v1/messages` | planned | | MCP (Model Context Protocol) | JSON-RPC / SSE | planned | | Native passthrough | `POST /route` | planned | ## Supported providers (outbound) | Provider | Covers | |---|---| | `openai` (OpenAI-compatible) | OpenAI, OpenRouter, Together, Groq, **vLLM, llama.cpp server, LM Studio, Ollama (`/v1`)** | | `anthropic` | Claude Messages API (planned) | | `ollama` | native Ollama API | | `http` | arbitrary HTTP endpoint (custom adapter) | Most local servers already expose an OpenAI-compatible API — so the `openai` adapter makes the gateway **universal** with almost no effort. --- ## Configuration ```toml listen = "0.0.0.0:9000" [router] url = "http://localhost:8080" api_key_env = "CORTIQ_ROUTER_KEY" [[models]] id = "local-qwen" provider = "openai" base_url = "http://localhost:8000/v1" model = "qwen2.5-7b-instruct" cost_tier = "cheap" [[models]] id = "claude-opus" provider = "anthropic" base_url = "https://api.anthropic.com" model = "claude-opus-4" api_key_env = "ANTHROPIC_API_KEY" cost_tier = "expensive" [routing] low = ["local-qwen"] high = ["claude-opus", "local-qwen"] default = "local-qwen" ``` Full example — [config/gateway.example.toml](config/gateway.example.toml). Routing & cost-aware selection — [docs/ROUTING.md](docs/ROUTING.md). Everything in the config is also editable from the admin console at runtime. --- ## Cross-standards (like grown-up APIs) - **Auth:** `Authorization: Bearer sk-gw-...` (virtual gateway keys). - **Errors:** OpenAI-compatible `{"error": {...}}` envelope — parsed natively by SDKs. - **Routing metadata:** `X-Cortiq-*` headers on every response. - **Cost accounting:** token usage + USD cost and the selected model. - **Observability:** `GET /metrics` (Prometheus), `GET /healthz`, `GET /readyz`. Details — [docs/PROTOCOLS.md](docs/PROTOCOLS.md). --- ## Build & test ```bash cargo build --release # single self-contained binary (SPA embedded) cargo test # config round-trip, routing validation ``` --- ## Status 🚧 Implemented so far: OpenAI Chat Completions, routing with fallback & graceful degradation, cost/token accounting, the **embedded multilingual admin console with hot config reload**, statistics and Prometheus `GET /metrics`. Planned: streaming (SSE), Anthropic provider/inbound, embeddings, MCP. See the roadmap in [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md). Contributions welcome — see [CONTRIBUTING.md](CONTRIBUTING.md). ## License Apache-2.0 — see [LICENSE](LICENSE).