From babc7a5446d32ff3d0cffeb86350d7a7b70c41a9 Mon Sep 17 00:00:00 2001 From: mlsmaycon Date: Tue, 11 Aug 2026 03:20:26 +0000 Subject: [PATCH] [proxy] Keep slash-bearing model ids in the discovery filter The filter treated a slash in a listing entry's id as a gateway provider prefix and matched only the tail. Self-hosted backends serve ids that carry a slash of their own, so every "Qwen/Qwen2.5-0.5B-Instruct" style model was dropped from the picker even when the policy named it exactly. Try the id as written first and fall back to the tail, so both a prefixed id and a self-hosted one resolve. --- proxy/internal/proxy/discovery_filter.go | 37 ++++++++++++++----- proxy/internal/proxy/discovery_filter_test.go | 17 +++++++++ 2 files changed, 44 insertions(+), 10 deletions(-) diff --git a/proxy/internal/proxy/discovery_filter.go b/proxy/internal/proxy/discovery_filter.go index b4b0e92e0..5023111ec 100644 --- a/proxy/internal/proxy/discovery_filter.go +++ b/proxy/internal/proxy/discovery_filter.go @@ -109,7 +109,7 @@ func filterListingBody(body []byte, permitted map[string]struct{}) ([]byte, bool kept := make([]map[string]json.RawMessage, 0, len(entries)) for _, entry := range entries { - if _, ok := permitted[entryModelID(entry)]; ok { + if entryPermitted(entry, permitted) { kept = append(kept, entry) } } @@ -126,23 +126,40 @@ func filterListingBody(body []byte, permitted map[string]struct{}) ([]byte, bool return out, true } -// entryModelID returns the entry's model id in the form the policy stores -// it, or "" when the entry carries no usable id. A provider-prefixed id -// ("bedrock/anthropic.claude-sonnet-5") keeps only its last segment, which -// is what the operator registers. -func entryModelID(entry map[string]json.RawMessage) string { +// entryPermitted reports whether a listing entry names a model the policy +// authorises, trying every form the same model is written in. +func entryPermitted(entry map[string]json.RawMessage, permitted map[string]struct{}) bool { raw, ok := entry["id"] if !ok { - return "" + return false } var id string if err := json.Unmarshal(raw, &id); err != nil { - return "" + return false } + for _, candidate := range modelIDForms(id) { + if _, ok := permitted[candidate]; ok { + return true + } + } + return false +} + +// modelIDForms returns the forms a single model id may be written in: the id +// itself, its undated form, and the same two with a gateway's provider +// prefix removed ("vertex_ai/claude-sonnet-5"). The bare id is tried first, +// because a self-hosted id can legitimately contain a slash of its own +// ("Qwen/Qwen2.5-0.5B-Instruct") and must not be cut down to its tail. +func modelIDForms(id string) []string { + if id == "" { + return nil + } + forms := []string{id, sharedllm.NormalizeAnthropicModel(id)} if slash := strings.LastIndex(id, "/"); slash >= 0 { - id = id[slash+1:] + tail := id[slash+1:] + forms = append(forms, tail, sharedllm.NormalizeAnthropicModel(tail)) } - return sharedllm.NormalizeAnthropicModel(id) + return forms } // restoreBody puts body back on the response and fixes the length headers diff --git a/proxy/internal/proxy/discovery_filter_test.go b/proxy/internal/proxy/discovery_filter_test.go index ab61a0f3c..dc62512ec 100644 --- a/proxy/internal/proxy/discovery_filter_test.go +++ b/proxy/internal/proxy/discovery_filter_test.go @@ -153,3 +153,20 @@ func TestModelDiscoveryFilter_RunsNextHook(t *testing.T) { require.NoError(t, modelDiscoveryFilter([]string{"claude-sonnet-5"}, next)(resp)) //nolint:bodyclose // in-memory body, replaced by the filter assert.True(t, called, "the chained hook must still run") } + +// TestModelDiscoveryFilter_KeepsSlashBearingIDs covers self-hosted backends +// whose model ids carry a slash of their own. Treating the slash as a +// gateway prefix and keeping only the tail dropped every such model from +// the picker even though the policy named it exactly. +func TestModelDiscoveryFilter_KeepsSlashBearingIDs(t *testing.T) { + ids := listedIDs(t, []string{"Qwen/Qwen2.5-0.5B-Instruct"}, `{ + "object": "list", + "data": [ + {"id": "Qwen/Qwen2.5-0.5B-Instruct"}, + {"id": "Qwen/Qwen2.5-7B-Instruct"} + ] + }`) + + assert.Equal(t, []string{"Qwen/Qwen2.5-0.5B-Instruct"}, ids, + "a slash inside the model id is part of the id, not a provider prefix") +}