Files
marfrit cc3be17994 Initial commit: llm-proxy (de-hardcoded, portable)
Zero-dependency OpenAI-compatible router: OpenRouter cloud + local model-aware
fanout, merged /v1/models catalog with cost tags, background free/cheap-tier
auto-curation. Extracted from the fleet's unversioned /opt/llm-proxy.py (kept
only as cp-backups) and de-hardcoded for portability:
- config dir via LLM_PROXY_CONFIG_DIR (default /etc/llm-proxy)
- default backends -> localhost (no fleet hosts); real ones in local-backends.json
- friendly-name aliases -> friendly-names.json (none baked in code)
- OpenRouter Referer/Title via LLM_PROXY_REFERER / LLM_PROXY_TITLE
- removed the ds4-escher one-off filter and the fleet-specific helper scripts
Sanitized: no fleet hostnames, IPs, names, or secrets (key comes from env).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EWpfhDgYNA21tETDP9ueBE
2026-07-24 08:15:18 +02:00

3.8 KiB

llm-proxy

An OpenAI-compatible HTTP proxy that routes /v1 requests to either OpenRouter (cloud) or local model servers. One file, Python standard library only, no dependencies.

What it does

  • Presents a single OpenAI-compatible /v1 API in front of many backends.
  • Routes each request by model id: cloud-prefixed ids go to OpenRouter; other ids go to the local backend that advertises them.
  • Serves a merged /v1/models list — the OpenRouter catalog plus every reachable local server's models — with each id tagged by source and cost tier.
  • Maintains the cloud catalog in the background: adds newly-free models, drops models that stopped being free, marks unavailable ones, and discovers cheap paid models.

Requirements

  • Python 3, standard library only.
  • OPENROUTER_API_KEY in the environment for cloud routing. Without it, only local routing works.

Run

OPENROUTER_API_KEY=sk-or-... python3 llm-proxy.py 8082

The listen port is the first argument (default 8082). The server binds 0.0.0.0.

Configuration

Environment

Variable Default Purpose
OPENROUTER_API_KEY Bearer key for OpenRouter. Substituted server-side; clients never send it.
LLM_PROXY_CONFIG_DIR /etc/llm-proxy Directory holding the files below.
LLM_PROXY_REFERER http://localhost HTTP-Referer sent to OpenRouter.
LLM_PROXY_TITLE llm-proxy X-Title sent to OpenRouter.

Files (in LLM_PROXY_CONFIG_DIR)

File In repo Purpose
local-backends.json example only Local backends: {name, host, port, slots}. Copy local-backends.example.json.
friendly-names.json example only Optional display aliases {model-id: label}. Copy friendly-names.example.json.
cloud-catalog.json no (state) Persisted OpenRouter catalog + status. Written by the proxy.
last-known-models.json no (state) Last-seen model list. Written by the proxy.

If local-backends.json is absent, the proxy falls back to one 127.0.0.1:8080 backend.

Endpoints

  • POST /v1/chat/completions — routed by model id (see Routing); streaming supported.
  • GET /v1/models — merged local + cloud catalog.

Resolved requests are forwarded to the chosen backend and the response streamed back.

Routing

  1. A cloud-prefixed model id (openai/, anthropic/, google/, deepseek/, qwen/, x-ai/, …) goes to OpenRouter over HTTPS with the server-side key.
  2. A known local model goes to the backend whose /v1/models advertises it (cached 30 s).
  3. Unknown model or empty body: the first reachable local backend.

The /v1/models ids carry display tags — [$] paid cloud, [free] free cloud, [local] local. The tag is stripped before routing, so a client may send back the exact id it was shown.

Background catalog maintenance

A thread runs every 3 hours and:

  • adds newly-free models over 7 B parameters to the free catalog,
  • removes models that are no longer free,
  • probes each free model with a 1-token request and suffixes -na on HTTP 429,
  • discovers paid models under $0.50 / M-token from known prefixes.

Paid models added by hand in cloud-catalog.json are never auto-removed.

Signals

  • SIGHUP — reload local-backends.json.
  • SIGUSR1 — drop discovery caches and re-check the cloud catalog now.

Scope

llm-proxy has no virtual keys, budgets, caching, observability callbacks, admin UI, or native non-OpenRouter providers. It trades those for a single dependency-free file plus OpenRouter free/cheap-tier auto-curation. For the larger feature set, use a full gateway such as litellm.

bullpen

Contributed as an optional component of bullpen. bullpen depends only on a generic OpenAI-compatible /v1 endpoint; llm-proxy is one way to provide it, not a requirement.