openai: align models list with tags (#16556)
This commit is contained in:
+7
-2
@@ -408,11 +408,16 @@ func ToCompleteChunk(id string, r api.GenerateResponse) CompletionChunk {
|
|||||||
func ToListCompletion(r api.ListResponse) ListCompletion {
|
func ToListCompletion(r api.ListResponse) ListCompletion {
|
||||||
var data []Model
|
var data []Model
|
||||||
for _, m := range r.Models {
|
for _, m := range r.Models {
|
||||||
|
id := m.Model
|
||||||
|
if id == "" {
|
||||||
|
id = m.Name
|
||||||
|
}
|
||||||
|
|
||||||
data = append(data, Model{
|
data = append(data, Model{
|
||||||
Id: m.Name,
|
Id: id,
|
||||||
Object: "model",
|
Object: "model",
|
||||||
Created: m.ModifiedAt.Unix(),
|
Created: m.ModifiedAt.Unix(),
|
||||||
OwnedBy: model.ParseName(m.Name).Namespace,
|
OwnedBy: model.ParseName(id).Namespace,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -356,6 +356,48 @@ func TestFromCompleteRequest_WithLogprobs(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestToListCompletionUsesModelIdentity(t *testing.T) {
|
||||||
|
modified := time.Unix(1234567890, 0).UTC()
|
||||||
|
|
||||||
|
result := ToListCompletion(api.ListResponse{
|
||||||
|
Models: []api.ListModelResponse{
|
||||||
|
{
|
||||||
|
Name: "legacy-name:latest",
|
||||||
|
Model: "namespace/exposed-model:latest",
|
||||||
|
ModifiedAt: modified,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "fallback-name:latest",
|
||||||
|
ModifiedAt: modified.Add(time.Second),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
if result.Object != "list" {
|
||||||
|
t.Fatalf("object = %q, want list", result.Object)
|
||||||
|
}
|
||||||
|
if len(result.Data) != 2 {
|
||||||
|
t.Fatalf("models = %d, want 2", len(result.Data))
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.Data[0].Id != "namespace/exposed-model:latest" {
|
||||||
|
t.Fatalf("id = %q, want model field", result.Data[0].Id)
|
||||||
|
}
|
||||||
|
if result.Data[0].OwnedBy != "namespace" {
|
||||||
|
t.Fatalf("owned_by = %q, want namespace", result.Data[0].OwnedBy)
|
||||||
|
}
|
||||||
|
if result.Data[0].Created != modified.Unix() {
|
||||||
|
t.Fatalf("created = %d, want %d", result.Data[0].Created, modified.Unix())
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.Data[1].Id != "fallback-name:latest" {
|
||||||
|
t.Fatalf("fallback id = %q, want name field", result.Data[1].Id)
|
||||||
|
}
|
||||||
|
if result.Data[1].OwnedBy != "library" {
|
||||||
|
t.Fatalf("fallback owned_by = %q, want library", result.Data[1].OwnedBy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestToChatCompletion_WithLogprobs(t *testing.T) {
|
func TestToChatCompletion_WithLogprobs(t *testing.T) {
|
||||||
createdAt := time.Unix(1234567890, 0)
|
createdAt := time.Unix(1234567890, 0)
|
||||||
resp := api.ChatResponse{
|
resp := api.ChatResponse{
|
||||||
|
|||||||
@@ -3,13 +3,20 @@ package server
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"os"
|
||||||
"slices"
|
"slices"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
"github.com/ollama/ollama/api"
|
"github.com/ollama/ollama/api"
|
||||||
|
"github.com/ollama/ollama/manifest"
|
||||||
|
"github.com/ollama/ollama/openai"
|
||||||
|
"github.com/ollama/ollama/types/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestList(t *testing.T) {
|
func TestList(t *testing.T) {
|
||||||
@@ -76,3 +83,104 @@ func TestList(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestOpenAIListMatchesTagsModels(t *testing.T) {
|
||||||
|
gin.SetMode(gin.TestMode)
|
||||||
|
setTestHome(t, t.TempDir())
|
||||||
|
|
||||||
|
cache := newModelListCache()
|
||||||
|
s := Server{modelCaches: &modelCaches{modelList: cache}}
|
||||||
|
cache.Start(context.Background())
|
||||||
|
if err := cache.Wait(context.Background()); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
createModel := func(name string) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
_, digest := createBinFile(t, nil, nil)
|
||||||
|
w := createRequest(t, s.CreateHandler, api.CreateRequest{
|
||||||
|
Model: name,
|
||||||
|
Files: map[string]string{"model.gguf": digest},
|
||||||
|
Stream: &stream,
|
||||||
|
})
|
||||||
|
if w.Code != http.StatusOK {
|
||||||
|
t.Fatalf("create %s status = %d, want 200: %s", name, w.Code, w.Body.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
createModel("older-model")
|
||||||
|
createModel("newer-model")
|
||||||
|
|
||||||
|
setManifestTime := func(name string, modified time.Time) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
parsed := model.ParseName(name)
|
||||||
|
path, err := manifest.PathForName(parsed)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("manifest path for %s: %v", name, err)
|
||||||
|
}
|
||||||
|
if err := os.Chtimes(path, modified, modified); err != nil {
|
||||||
|
t.Fatalf("set manifest time for %s: %v", name, err)
|
||||||
|
}
|
||||||
|
if err := cache.RefreshModel(parsed); err != nil {
|
||||||
|
t.Fatalf("refresh %s: %v", name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
older := time.Unix(1000, 0).UTC()
|
||||||
|
newer := time.Unix(2000, 0).UTC()
|
||||||
|
setManifestTime("older-model:latest", older)
|
||||||
|
setManifestTime("newer-model:latest", newer)
|
||||||
|
|
||||||
|
router, err := s.GenerateRoutes(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
doGet := func(path string, dest any) {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodGet, path, nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
router.ServeHTTP(w, req)
|
||||||
|
|
||||||
|
body, err := io.ReadAll(w.Body)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("read %s response: %v", path, err)
|
||||||
|
}
|
||||||
|
if w.Code != http.StatusOK {
|
||||||
|
t.Fatalf("GET %s status = %d, want 200: %s", path, w.Code, string(body))
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(body, dest); err != nil {
|
||||||
|
t.Fatalf("decode %s response: %v", path, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var tags api.ListResponse
|
||||||
|
doGet("/api/tags", &tags)
|
||||||
|
|
||||||
|
var models openai.ListCompletion
|
||||||
|
doGet("/v1/models", &models)
|
||||||
|
|
||||||
|
if len(tags.Models) != 2 {
|
||||||
|
t.Fatalf("/api/tags models = %d, want 2: %+v", len(tags.Models), tags.Models)
|
||||||
|
}
|
||||||
|
if len(models.Data) != len(tags.Models) {
|
||||||
|
t.Fatalf("/v1/models data = %d, want %d", len(models.Data), len(tags.Models))
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tagModel := range tags.Models {
|
||||||
|
v1Model := models.Data[i]
|
||||||
|
if v1Model.Id != tagModel.Model {
|
||||||
|
t.Fatalf("model %d id = %q, want /api/tags model %q", i, v1Model.Id, tagModel.Model)
|
||||||
|
}
|
||||||
|
if v1Model.Created != tagModel.ModifiedAt.Unix() {
|
||||||
|
t.Fatalf("model %d created = %d, want modified_at %d", i, v1Model.Created, tagModel.ModifiedAt.Unix())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if got, want := models.Data[0].Id, "newer-model:latest"; got != want {
|
||||||
|
t.Fatalf("first /v1/models id = %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user