fix cognitive local model discovery
CI / test (push) Failing after 36s

This commit is contained in:
m
2026-08-30 23:54:18 +03:00
parent f803cb67f0
commit dfbee7568f
5 changed files with 160 additions and 1 deletions
@@ -65,6 +65,9 @@ type compatibleModelsResponse struct {
ID string `json:"id"`
Created int64 `json:"created"`
OwnedBy string `json:"owned_by"`
ModelKind string `json:"model_kind"`
Capabilities map[string]bool `json:"capabilities"`
Modalities map[string]bool `json:"modalities"`
Meta struct {
Context int64 `json:"n_ctx"`
TrainingContext int64 `json:"n_ctx_train"`
@@ -94,6 +97,12 @@ func (a *OpenAICompatibleLocalAdapter) Discover(ctx context.Context) ([]Discover
if item.ID == "" {
continue
}
// The gateway catalogue may contain image/audio runtimes beside local
// chat models. Discovery is not allowed to qualify those as text
// advisory models merely because they appear in /v1/models.
if !isTextAdvisoryModel(item.ModelKind, item.Capabilities, item.Modalities) {
continue
}
stableMetadata := fmt.Sprintf("%s\x00%s\x00%d\x00%d\x00%d\x00%d\x00%s", item.ID, item.OwnedBy, item.Meta.Context, item.Meta.TrainingContext, item.Meta.Parameters, item.Meta.Size, item.Meta.FileType)
digest := sha256.Sum256([]byte(stableMetadata))
contextBytes := item.Meta.Context * 4
@@ -105,6 +114,19 @@ func (a *OpenAICompatibleLocalAdapter) Discover(ctx context.Context) ([]Discover
return models, ProviderProbe{Health: capability.HealthAvailable}
}
func isTextAdvisoryModel(kind string, capabilities, modalities map[string]bool) bool {
if strings.EqualFold(strings.TrimSpace(kind), "image-generation") || strings.EqualFold(strings.TrimSpace(kind), "audio-transcription") || strings.EqualFold(strings.TrimSpace(kind), "text-to-speech") {
return false
}
if chat, known := capabilities["chat"]; known && !chat {
return false
}
if text, known := modalities["text"]; known && !text {
return false
}
return true
}
type compatibleChatRequest struct {
Model string `json:"model"`
Messages []compatibleMessage `json:"messages"`
@@ -236,6 +258,8 @@ func mapCompatibleStatus(status int) cognitive.FinishReason {
return cognitive.FinishContextTooLarge
case 429:
return cognitive.FinishRateLimited
case 400, 422:
return cognitive.FinishInvalidProviderResponse
default:
return cognitive.FinishProviderUnavailable
}
@@ -29,6 +29,37 @@ func (modelsMetadataRoundTripper) RoundTrip(request *http.Request) (*http.Respon
}, nil
}
type mixedModelsRoundTripper struct{}
func (mixedModelsRoundTripper) RoundTrip(request *http.Request) (*http.Response, error) {
return &http.Response{
StatusCode: http.StatusOK,
Body: io.NopCloser(strings.NewReader(`{"data":[{"id":"image-model","model_kind":"image-generation","capabilities":{"chat":false,"image_generation":true},"modalities":{"text":false,"image_generation":true}},{"id":"text-model","model_kind":"chat","capabilities":{"chat":true},"modalities":{"text":true},"meta":{}}]}`)),
Header: make(http.Header),
Request: request,
}, nil
}
func TestOpenAICompatibleLocalAdapterDoesNotQualifyNonTextModels(t *testing.T) {
adapter, err := NewOpenAICompatibleLocalAdapter("http://gateway/v1", mixedModelsRoundTripper{})
if err != nil {
t.Fatal(err)
}
models, probe := adapter.Discover(context.Background())
if probe.Health != capability.HealthAvailable || len(models) != 1 || models[0].ProviderModelID != "text-model" {
t.Fatalf("unexpected filtered discovery: probe=%+v models=%+v", probe, models)
}
}
func TestOpenAICompatibleLocalAdapterMapsImmediateBadRequest(t *testing.T) {
if got := mapCompatibleStatus(http.StatusBadRequest); got != cognitive.FinishInvalidProviderResponse {
t.Fatalf("400 finish=%s, want %s", got, cognitive.FinishInvalidProviderResponse)
}
if got := mapCompatibleStatus(http.StatusUnprocessableEntity); got != cognitive.FinishInvalidProviderResponse {
t.Fatalf("422 finish=%s, want %s", got, cognitive.FinishInvalidProviderResponse)
}
}
func TestOpenAICompatibleLocalAdapterNormalizesMinimalModelMetadata(t *testing.T) {
adapter, err := NewOpenAICompatibleLocalAdapter("http://gateway/v1", modelsMetadataRoundTripper{})
if err != nil {