[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.
This commit is contained in:
mlsmaycon
2026-08-11 03:20:26 +00:00
committed by GitHub
parent 53f320c318
commit 87dad71d6a
2 changed files with 44 additions and 10 deletions

View File

@@ -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

View File

@@ -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")
}