mirror of
https://github.com/lukaszraczylo/traefik-llmgateway.git
synced 2026-10-12 04:54:45 +00:00
docs: README, kubernetes and compose examples, MIT license
- README.md: what-it-is, quickstart (localPlugins today, catalog form
once published), full config reference table for every Config/
ProviderConfig/GroupConfig/LimitsConfig/UsersConfig/UserConfig/
RedisConfig/ModelPricing/TargetConfig/AgentConfig field, model routing,
limits and accounting semantics, unified vs passthrough differences,
MCP/A2A, security notes, known limitations (streaming buffering under
Yaegi, quota-on-reject nuances, identity-encoding, path-traversal
rejects), Kubernetes deployment notes, and development/catalog-
submission status. Every claim grounded against the actual source read
this session; two draft claims (user-limits "replace" group limits, an
unqualified "go-redis is documented as unsuitable in resp.go/
providers.go") were caught and corrected against the real
buildLimitScopes semantics and an actual grep before committing.
- examples/kubernetes.yaml: Secret (users.json) + Middleware CR +
IngressRoute + a Helm values snippet. The Middleware's plugin config
round-trips through the real Config struct and New() (verified via a
throwaway, since-deleted test), not just YAML-syntax-checked.
- examples/docker-compose.yml: minimal localPlugins-based demo (traefik +
whoami + inline file-provider config via compose's inline configs.content).
Brought up and exercised end-to-end against real OpenAI this session:
auth 401/200, /v1/models, /v1/chat/completions (upstream 401 envelope
with a fake key, proving passthrough+key-injection+wrapping all work),
and the unknown-route 404 envelope. Found and fixed a real bug in the
process: a bare "{}" empty group value fails Traefik's own dynamic-
config decode with "expected a map, got 'string'"; fixed to an
explicit empty list and documented the gotcha in both example files and
the README.
- LICENSE: MIT, copyright 2026 Lukasz Raczylo.
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
# Minimal, runnable local demo of the LLM Gateway plugin: one Traefik
|
||||
# instance, loading this repository as a localPlugin, in front of a single
|
||||
# OpenAI-type provider and a `whoami` dummy backend (every route the plugin
|
||||
# recognizes is short-circuited by the middleware itself; whoami only
|
||||
# answers a request that falls through as passthroughUnknown).
|
||||
#
|
||||
# Run from the repository root:
|
||||
#
|
||||
# OPENAI_API_KEY=sk-... docker compose -f examples/docker-compose.yml up
|
||||
#
|
||||
# Try it once traefik1 is healthy:
|
||||
#
|
||||
# curl -s http://localhost:8080/v1/models \
|
||||
# -H "Authorization: Bearer sk-demo-local"
|
||||
#
|
||||
# curl -s http://localhost:8080/v1/chat/completions \
|
||||
# -H "Authorization: Bearer sk-demo-local" \
|
||||
# -H "Content-Type: application/json" \
|
||||
# -d '{"model":"gpt-5-mini","messages":[{"role":"user","content":"hi"}]}'
|
||||
#
|
||||
# Override the demo API key with LLMGW_DEMO_KEY=... if you don't want the
|
||||
# default "sk-demo-local" in your shell history, and the published port
|
||||
# with LLMGW_DEMO_PORT=... if 8080 is already taken on your machine. See
|
||||
# ../README.md for the full configuration reference; see ../.traefik.yml
|
||||
# for the catalog-facing plugin manifest.
|
||||
|
||||
configs:
|
||||
dynamic:
|
||||
content: |
|
||||
http:
|
||||
routers:
|
||||
llmgateway-router:
|
||||
rule: "PathPrefix(`/`)"
|
||||
entryPoints: ["web"]
|
||||
service: whoami
|
||||
middlewares: ["llmgateway"]
|
||||
services:
|
||||
whoami:
|
||||
loadBalancer:
|
||||
servers:
|
||||
- url: "http://whoami:80"
|
||||
middlewares:
|
||||
llmgateway:
|
||||
plugin:
|
||||
llmgateway:
|
||||
providers:
|
||||
openai:
|
||||
type: openai
|
||||
apiKey: "env:OPENAI_API_KEY"
|
||||
# Explicit rather than discovery:true, so the demo
|
||||
# starts cleanly with no outbound network call — flip
|
||||
# this to discovery:true (and drop models) to pull the
|
||||
# live catalog from api.openai.com instead.
|
||||
models: ["gpt-5-mini", "gpt-5", "gpt-4o"]
|
||||
groups:
|
||||
default:
|
||||
providers: []
|
||||
users:
|
||||
inline:
|
||||
- name: demo
|
||||
group: default
|
||||
apiKey: "${LLMGW_DEMO_KEY:-sk-demo-local}"
|
||||
|
||||
services:
|
||||
whoami:
|
||||
image: traefik/whoami:v1.10
|
||||
networks: ["llmgw-demo"]
|
||||
|
||||
traefik1:
|
||||
image: traefik:v3.5
|
||||
command:
|
||||
- "--entrypoints.web.address=:80"
|
||||
- "--experimental.localPlugins.llmgateway.moduleName=github.com/lukaszraczylo/traefik-llmgateway"
|
||||
- "--providers.file.filename=/etc/traefik/dynamic.yml"
|
||||
- "--log.level=INFO"
|
||||
environment:
|
||||
# ":?" fails `docker compose config`/`up` immediately, with a clear
|
||||
# message, instead of starting a plugin whose provider has no key.
|
||||
OPENAI_API_KEY: "${OPENAI_API_KEY:?set OPENAI_API_KEY before running this demo}"
|
||||
ports:
|
||||
- "${LLMGW_DEMO_PORT:-8080}:80"
|
||||
volumes:
|
||||
# The plugin's own module, mounted read-only exactly as Traefik's
|
||||
# localPlugins loader expects: <plugins-local>/src/<moduleName>.
|
||||
- ..:/plugins-local/src/github.com/lukaszraczylo/traefik-llmgateway:ro
|
||||
configs:
|
||||
- source: dynamic
|
||||
target: /etc/traefik/dynamic.yml
|
||||
networks: ["llmgw-demo"]
|
||||
depends_on:
|
||||
- whoami
|
||||
|
||||
networks:
|
||||
llmgw-demo:
|
||||
driver: bridge
|
||||
Reference in New Issue
Block a user