From 5c63e0066c0ab162a76e33c6591c1dfe35ada3d5 Mon Sep 17 00:00:00 2001 From: Ben Greene Date: Wed, 6 Aug 2025 12:04:52 -0500 Subject: [PATCH] return models sorted by id in /v1/models (#222) --- proxy/proxymanager.go | 8 +++++++ proxy/proxymanager_test.go | 45 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/proxy/proxymanager.go b/proxy/proxymanager.go index b714633..bdc31c7 100644 --- a/proxy/proxymanager.go +++ b/proxy/proxymanager.go @@ -8,6 +8,7 @@ import ( "mime/multipart" "net/http" "os" + "sort" "strconv" "strings" "sync" @@ -333,6 +334,13 @@ func (pm *ProxyManager) listModelsHandler(c *gin.Context) { data = append(data, record) } + // Sort by the "id" key + sort.Slice(data, func(i, j int) bool { + si, _ := data[i]["id"].(string) + sj, _ := data[j]["id"].(string) + return si < sj + }) + // Set CORS headers if origin exists if origin := c.GetHeader("Origin"); origin != "" { c.Header("Access-Control-Allow-Origin", origin) diff --git a/proxy/proxymanager_test.go b/proxy/proxymanager_test.go index 83c5bdc..2447ad9 100644 --- a/proxy/proxymanager_test.go +++ b/proxy/proxymanager_test.go @@ -279,6 +279,51 @@ func TestProxyManager_ListModelsHandler(t *testing.T) { assert.Empty(t, expectedModels, "not all expected models were returned") } +func TestProxyManager_ListModelsHandler_SortedByID(t *testing.T) { + // Intentionally add models in non-sorted order and with an unlisted model + config := Config{ + HealthCheckTimeout: 15, + Models: map[string]ModelConfig{ + "zeta": getTestSimpleResponderConfig("zeta"), + "alpha": getTestSimpleResponderConfig("alpha"), + "beta": getTestSimpleResponderConfig("beta"), + "hidden": func() ModelConfig { + mc := getTestSimpleResponderConfig("hidden") + mc.Unlisted = true + return mc + }(), + }, + LogLevel: "error", + } + + proxy := New(config) + + // Request models list + req := httptest.NewRequest("GET", "/v1/models", nil) + w := httptest.NewRecorder() + proxy.ServeHTTP(w, req) + + assert.Equal(t, http.StatusOK, w.Code) + + var response struct { + Data []map[string]interface{} `json:"data"` + } + if err := json.Unmarshal(w.Body.Bytes(), &response); err != nil { + t.Fatalf("Failed to parse JSON response: %v", err) + } + + // We expect only the listed models in sorted order by id + expectedOrder := []string{"alpha", "beta", "zeta"} + if assert.Len(t, response.Data, len(expectedOrder), "unexpected number of listed models") { + got := make([]string, 0, len(response.Data)) + for _, m := range response.Data { + id, _ := m["id"].(string) + got = append(got, id) + } + assert.Equal(t, expectedOrder, got, "models should be sorted by id ascending") + } +} + func TestProxyManager_Shutdown(t *testing.T) { // make broken model configurations model1Config := getTestSimpleResponderConfigPort("model1", 9991)