Sign inSign up

lordraw/llmproxy

By lordraw

β€’Updated about 2 months ago

an llm proxy

Image
Machine learning & AI
0

988

lordraw/llmproxy repository overview

⁠llmproxy

One proxy, three APIs, many providers. llmproxy speaks the Ollama, OpenAI and llama.cpp HTTP protocols at the same time and relays every request to the provider that owns the requested model β€” any OpenAI-compatible endpoint (NVIDIA by default, plus OpenAI, Mistral, Groq, OpenRouter, vLLM, a local Ollama…), Azure OpenAI, Anthropic and Google Gemini, the last two translated natively to and from the OpenAI shape. To your tools it looks like a local LLM server; the inference runs wherever you configured it.

Point Open WebUI, an IDE plugin, a chat frontend or an agent framework at llmproxy β€” no client change beyond the base URL β€” and serve responses from any hosted model. With no configuration file at all, a single NVIDIA provider is built from the NVIDIA_* variables.


⁠Features

  • Triple API surface β€” Ollama (/api/chat, /api/generate, /api/tags, /api/show, /api/version), OpenAI (/v1/chat/completions, /v1/completions, /v1/models, /v1/models/<id>, /v1/embeddings) and llama.cpp (/completion, /props).
  • Embeddings β€” OpenAI /v1/embeddings and Ollama /api/embed + legacy /api/embeddings, with a dedicated default embeddings model.
  • Streaming β€” SSE for OpenAI/llama.cpp, NDJSON for Ollama; token usage is logged and re-exposed.
  • Multi-model β€” advertise a whole list of models; clients with a model picker (e.g. Open WebUI) just work.
  • Multi-provider β€” serve several upstreams at once via providers.toml (OpenAI-compatible, Azure, Anthropic, Gemini); all their models are exposed together as provider:model and routed to the owning provider. With no file, a single NVIDIA provider is built from the NVIDIA_* vars (zero-config).
  • Vision β€” pass image_url content to vision-capable models.
  • Resilient β€” configurable upstream timeout and automatic retry with exponential backoff on 429/5xx/network errors (honours Retry-After).
  • Response caching β€” optional per-worker in-memory cache for non-streaming completions and embeddings, with configurable TTL and size (CACHE_ENABLED/CACHE_TTL/CACHE_MAX_SIZE/CACHE_POLICY) and hit/miss stats at /stats.
  • Optional inbound auth β€” protect the proxy with PROXY_API_KEY (Authorization: Bearer or X-Api-Key); / and /health stay open for health-checks.
  • Audit trail β€” optional (AUDIT_ENABLED): one structured record per request β€” prompt, completion, provider and native model, sampling parameters, token usage, retries, latency and time-to-first-token, and the conversation it belongs to. Written to a rotating file by a background thread, so the request never waits for it; under back-pressure records are dropped, never delayed.
  • Observability β€” per-request correlation IDs, structured logging, /health with an optional live upstream probe (?upstream=1), plus a live /stats dashboard (and /stats.json) with request/latency/token metrics, cache and audit counters, and a process-manager view (PID, workers, memory, uptime).
  • Clean architecture β€” a small layered Python/Flask package (config Β· domain Β· upstream Β· services Β· web), no database, run under gunicorn.

⁠Quick start

The zero-config path needs a valid NVIDIA API key (nvapi-…) from build.nvidia.com⁠. To serve several providers instead, mount a providers.toml and point PROVIDERS_CONFIG at it (-v ./providers.toml:/config/providers.toml:ro); see the configuration guide⁠.

⁠docker run
docker run -d --name llmproxy -p 11434:11434 \
  -e NVIDIA_API_KEY=nvapi-xxxxxxxx \
  -e NVIDIA_MODELS=meta/llama-3.1-8b-instruct,meta/llama-3.2-11b-vision-instruct \
  lordraw/llmproxy:latest
⁠docker compose
services:
  llmproxy:
    image: lordraw/llmproxy:latest
    container_name: llmproxy
    restart: unless-stopped
    env_file: .env
    ports:
      - "11434:11434"
    volumes:
      # Only needed with AUDIT_ENABLED: keeps the audit trail on the host.
      - ./logs:/app/logs
⁠Smoke test
# Discovery
curl http://localhost:11434/v1/models

# OpenAI chat
curl http://localhost:11434/v1/chat/completions \
  -H 'Content-Type: application/json' \
  -d '{"model":"meta/llama-3.1-8b-instruct",
       "messages":[{"role":"user","content":"Say hello."}]}'

# Ollama chat
curl http://localhost:11434/api/chat \
  -d '{"model":"meta/llama-3.1-8b-instruct","stream":false,
       "messages":[{"role":"user","content":"Say hello."}]}'

# Health (+ live upstream check)
curl "http://localhost:11434/health?upstream=1"

Using it as an Ollama backend (e.g. Open WebUI): set the Ollama base URL to http://<host>:11434. As an OpenAI backend: base URL http://<host>:11434/v1.


⁠Configuration

All configuration is via environment variables.

VariableDefaultDescription
PROVIDERS_CONFIGproviders.tomlPath to the multi-provider TOML file. When present it defines the providers; when absent, the NVIDIA_* vars synthesize a single provider.
NVIDIA_API_KEY(required in env fallback)Your NVIDIA API key (nvapi-…).
NVIDIA_API_BASEhttps://integrate.api.nvidia.com/v1Upstream base URL (env fallback).
NVIDIA_MODELmeta/llama-3.1-8b-instructSingle default model (fallback).
NVIDIA_MODELS(unset)Comma-separated model list; the first is the default. Overrides NVIDIA_MODEL.
NVIDIA_EMBEDDINGS_MODELnvidia/nv-embedqa-e5-v5Default model for embeddings endpoints.
EMBEDDINGS_INPUT_TYPEqueryinput_type applied if the client omits it (query/passage; empty to skip).
PROXY_API_KEY(empty)If set, requires this key on inbound requests. Empty = open proxy.
UPSTREAM_TIMEOUT120Upstream read timeout (seconds). Raise it for slow non-streaming models.
FORCE_UPSTREAM_STREAMfalseAlways stream towards the upstream on /chat/completions (transparent to the caller). Avoids read timeouts on slow/non-streaming requests. 1/true/yes/on.
CACHE_ENABLEDfalseEnable the response cache for non-streaming replies (1/true/yes/on). Identical requests skip the upstream call. Stats at /stats.
CACHE_TTL300Cache entry time-to-live (seconds).
CACHE_MAX_SIZE512Max cache entries (LRU eviction past the cap).
CACHE_POLICYdeterministicWhich requests are eligible: off, embeddings, deterministic (embeddings + completions with a seed or temperature: 0), all (everything β€” identical prompts return identical text for the whole TTL).
HTTP_PROXY / HTTPS_PROXY / NO_PROXY(empty)Outbound egress proxy to reach the upstream (corporate proxy). Without it, a proxied host hangs until UPSTREAM_TIMEOUT. Keep localhost,127.0.0.1 in NO_PROXY.
RETRY_MAX2Retries beyond the first attempt on transient errors (0 disables).
RETRY_BACKOFF0.5Base of the exponential backoff (seconds).
AUDIT_ENABLEDfalseWrite one structured record per request (prompt, reply, provider, parameters, tokens, timings, session) to AUDIT_FILE. Built and written by a background thread: it costs the request nothing.
AUDIT_FILElogs/audit.jsonlDestination file; {pid} and {date} are expanded. Mount the directory or the records die with the container, and use {pid} with several workers β€” they can share a file but cannot coordinate its rotation.
AUDIT_FORMATjsonljsonl (one record per line, for tail/jq) or pretty (indented).
AUDIT_BODIEStruncatedContent recorded: none (accounting only), truncated (clipped to AUDIT_MAX_CHARS), full. Unless none, the file holds conversations in clear text β€” protect it and give it a retention policy.
AUDIT_MAX_CHARS2000Character budget per captured text under truncated.
AUDIT_QUEUE_SIZE10000Records that may wait for the writer. Past this they are dropped and counted at /stats, rather than making a request wait.
AUDIT_MAX_MB / AUDIT_BACKUPS64 / 5Rotation: at most AUDIT_MAX_MB Γ— (AUDIT_BACKUPS + 1) on disk.
AUDIT_SESSION_HEADER(empty)Header carrying your front-end's conversation id (e.g. X-OpenWebUI-Chat-Id), consulted before the built-in X-Session-Id/X-Conversation-Id/X-Chat-Id. Without one, the session is fingerprinted from the conversation's opening message.
LOG_LEVELINFODEBUG also logs the payload sent upstream.
LOG_TZTZ env, else UTCIANA timezone for log timestamps (e.g. Europe/Rome).
PORT / HOST11434 / 0.0.0.0Bind address.
WEB_CONCURRENCY2gunicorn workers.
THREADS8Threads per worker (SSE-friendly).
GUNICORN_TIMEOUT600gunicorn worker timeout (seconds).
UPSTREAM_POOL_SIZEvalue of THREADSPooled HTTP connections kept open towards each upstream. Below THREADS, concurrent calls queue for a free connection.

Minimal .env:

NVIDIA_API_KEY=nvapi-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
NVIDIA_MODELS=meta/llama-3.1-8b-instruct,meta/llama-3.2-11b-vision-instruct
LOG_TZ=Europe/Rome
# PROXY_API_KEY=change-me   # uncomment to require inbound auth
# AUDIT_ENABLED=on          # uncomment to record every request (needs ./logs mounted)

⁠Image tags

TagMeaning
latestLatest released version.
X.Y.ZSpecific released version (recommended for production).

The image runs as a non-root user, exposes port 11434, includes a Docker HEALTHCHECK, and is served by gunicorn with threaded workers (compatible with SSE streaming). It is built for linux/amd64 (extendable to other platforms via the Makefile's PLATFORMS).


⁠Health & monitoring

  • GET /health β€” liveness plus basic config (api_key_configured, number of models, default model).
  • GET /health?upstream=1 β€” also probes every configured provider; returns status: degraded (HTTP 503) if one is unreachable.
  • GET /stats β€” self-contained, auto-refreshing HTML dashboard with live statistics, metrics (requests, latency, tokens, upstream calls, cache and audit counters) and the process-manager view (PID, worker pool, memory, uptime). Open it in a browser at http://<host>:11434/stats.
  • GET /stats.json β€” the same data as JSON for scraping/scripting.

Metrics are in-memory per gunicorn worker: one response reflects the worker (process.pid) that served it. /stats and /stats.json honour PROXY_API_KEY like the API endpoints; only / and /health stay open for health-checks.

⁠Smoke test (metrics)
curl http://localhost:11434/stats.json      # JSON snapshot
# open http://localhost:11434/stats in a browser for the dashboard

⁠License

Released under the MIT License β€” free to use, copy, modify, and distribute, with attribution and no warranty. See the LICENSE⁠ in the GitHub repository for the full text.

Tag summary

Content type

Image

Digest

sha256:34f6da88a…

Size

47.9 MB

Last updated

about 2 months ago

docker pull lordraw/llmproxy