mirror of
https://github.com/lukaszraczylo/traefik-llmgateway.git
synced 2026-10-18 05:47:38 +00:00
feat: model registry with discovery and aggregated /v1/models
Adds modelRegistry: per-provider explicit+discovered model sets, bare-id and provider-prefixed resolution with sorted-name collision precedence, group authorization pairing (allowsModel AND allowsProvider), and a group-filtered OpenAI-compatible /v1/models catalog. Discovery is lazy and throttled per provider: newGateway runs a synchronous 5s-bounded warm fill for discovery-enabled providers (non-fatal on error), and Gateway.ServeHTTP triggers a throttled, inFlight-guarded background refresh per provider per interval (default 1h, per-provider DiscoveryInterval parsed at construction). A failed refresh keeps the previous discovered set (stale-while-error). Wires GET /v1/models into ServeHTTP (auth required, 401 envelope otherwise) and gives Gateway its adapters/registry fields, both built in newGateway. newModelRegistry's signature gained an error return beyond the task brief's literal listing: an invalid DiscoveryInterval must be a constructor error per the controller ruling, which the brief's bare *modelRegistry signature could not express.
This commit is contained in:
+29
-7
@@ -101,13 +101,13 @@ func CreateConfig() *Config {
|
||||
|
||||
// Gateway is the Traefik middleware handler.
|
||||
type Gateway struct {
|
||||
next http.Handler
|
||||
cfg *Config
|
||||
auth *authStore
|
||||
limiter *limiter
|
||||
name string
|
||||
// wired in later tasks: registry *modelRegistry, adapters
|
||||
// map[string]providerAdapter
|
||||
next http.Handler
|
||||
cfg *Config
|
||||
auth *authStore
|
||||
limiter *limiter
|
||||
registry *modelRegistry
|
||||
adapters map[string]providerAdapter
|
||||
name string
|
||||
}
|
||||
|
||||
// New creates the middleware. NOTE: no tail call — Yaegi zeroes
|
||||
@@ -143,6 +143,21 @@ func newGateway(ctx context.Context, next http.Handler, config *Config, name str
|
||||
lim.logf = g.errorf
|
||||
g.limiter = lim
|
||||
|
||||
adapters, err := buildAdapters(config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
g.adapters = adapters
|
||||
|
||||
registry, err := newModelRegistry(adapters, config, g.errorf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
warmCtx, warmCancel := context.WithTimeout(ctx, warmFillTimeout)
|
||||
defer warmCancel()
|
||||
registry.warmFill(warmCtx)
|
||||
g.registry = registry
|
||||
|
||||
setPricingWarnFn(func(msg string) {
|
||||
if msg == warnCapMessage {
|
||||
g.logf("%s", msg)
|
||||
@@ -226,6 +241,13 @@ func (g *Gateway) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
sw := &statusTrackingWriter{ResponseWriter: w}
|
||||
defer recoverPanic(sw, g)
|
||||
g.auth.maybeReload()
|
||||
g.registry.maybeRefresh(r.Context())
|
||||
|
||||
if r.Method == http.MethodGet && r.URL.Path == "/v1/models" {
|
||||
g.handleModels(sw, r)
|
||||
return
|
||||
}
|
||||
|
||||
if g.cfg.PassthroughUnknown {
|
||||
g.next.ServeHTTP(sw, r)
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user