From 2eed0d4679c00d619d2e0ec426a48c042c665662 Mon Sep 17 00:00:00 2001 From: bitloi <89318445+bitloi@users.noreply.github.com> Date: Wed, 3 Jun 2026 08:16:28 -0300 Subject: [PATCH] refactor(go-models): add unsupported model driver defaults (#15431) ### What problem does this PR solve? Adds a shared safe default implementation for unsupported Go model-driver capability methods and migrates the confirmed panic-stub providers to use it. The Go `ModelDriver` interface requires providers to implement many capability methods even when the provider does not support them. XunFei had unsupported capability methods implemented as `panic("implement me")`, Mistral still had a panic in `ParseFile`, and HuaweiCloud carried an unreachable `panic("implement me")` after a normal chat return. ### Type of change - [x] Refactoring Co-authored-by: Haruko386 --- internal/entity/models/huaweicloud.go | 1 - internal/entity/models/mistral.go | 7 +- internal/entity/models/mistral_test.go | 43 ++++++++++++ .../entity/models/model_test_helpers_test.go | 16 +++++ internal/entity/models/xunfei.go | 49 ++++---------- internal/entity/models/xunfei_test.go | 67 +++++++++++++++++++ 6 files changed, 142 insertions(+), 41 deletions(-) create mode 100644 internal/entity/models/model_test_helpers_test.go create mode 100644 internal/entity/models/xunfei_test.go diff --git a/internal/entity/models/huaweicloud.go b/internal/entity/models/huaweicloud.go index ec3e4da07..63052428a 100644 --- a/internal/entity/models/huaweicloud.go +++ b/internal/entity/models/huaweicloud.go @@ -280,7 +280,6 @@ func (h *HuaweiCloudModel) ChatWithMessages(modelName string, messages []Message Answer: &content, ReasonContent: &reasonContent, }, nil - panic("implement me") } func (h *HuaweiCloudModel) ChatStreamlyWithSender(modelName string, messages []Message, apiConfig *APIConfig, chatModelConfig *ChatConfig, sender func(*string, *string) error) error { diff --git a/internal/entity/models/mistral.go b/internal/entity/models/mistral.go index d91419ea2..fb0a74b89 100644 --- a/internal/entity/models/mistral.go +++ b/internal/entity/models/mistral.go @@ -609,7 +609,7 @@ func (m *MistralModel) ListModels(apiConfig *APIConfig) ([]string, error) { // Balance is not exposed by the Mistral API, so this returns "no such method". func (m *MistralModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) { - return nil, fmt.Errorf("no such method") + return nil, fmt.Errorf("%s, no such method", m.Name()) } // CheckConnection runs a lightweight ListModels call to verify the API key. @@ -624,7 +624,7 @@ func (m *MistralModel) CheckConnection(apiConfig *APIConfig) error { // Rerank calculates similarity scores between query and documents. Mistral // does not expose a public rerank API, so this returns "no such method". func (m *MistralModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig) (*RerankResponse, error) { - return nil, fmt.Errorf("no such method") + return nil, fmt.Errorf("%s, no such method", m.Name()) } // TranscribeAudio transcribe audio @@ -731,8 +731,7 @@ func (m *MistralModel) OCRFile(modelName *string, content []byte, urls *string, } func (m *MistralModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig) (*ParseFileResponse, error) { - //TODO implement me - panic("implement me") + return nil, fmt.Errorf("%s, no such method", m.Name()) } func (m *MistralModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) { diff --git a/internal/entity/models/mistral_test.go b/internal/entity/models/mistral_test.go index 7f288faff..8be9d8993 100644 --- a/internal/entity/models/mistral_test.go +++ b/internal/entity/models/mistral_test.go @@ -390,6 +390,49 @@ func TestMistralRerankReturnsNoSuchMethod(t *testing.T) { } } +func TestMistralUnsupportedDefaultsReturnNoSuchMethod(t *testing.T) { + m := newMistralForTest("http://unused") + modelName := "mistral-large-latest" + + checks := []struct { + name string + call func() error + }{ + {"TranscribeAudio", func() error { + _, err := m.TranscribeAudio(&modelName, nil, &APIConfig{}, nil) + return err + }}, + {"TranscribeAudioWithSender", func() error { + return m.TranscribeAudioWithSender(&modelName, nil, &APIConfig{}, nil, nil) + }}, + {"AudioSpeech", func() error { + _, err := m.AudioSpeech(&modelName, nil, &APIConfig{}, nil) + return err + }}, + {"AudioSpeechWithSender", func() error { + return m.AudioSpeechWithSender(&modelName, nil, &APIConfig{}, nil, nil) + }}, + {"ParseFile", func() error { + _, err := m.ParseFile(&modelName, nil, nil, &APIConfig{}, nil) + return err + }}, + {"ListTasks", func() error { + _, err := m.ListTasks(&APIConfig{}) + return err + }}, + {"ShowTask", func() error { + _, err := m.ShowTask("task-id", &APIConfig{}) + return err + }}, + } + + for _, check := range checks { + t.Run(check.name, func(t *testing.T) { + requireNoSuchMethod(t, check.name, check.call()) + }) + } +} + func TestMistralEmbedHappyPath(t *testing.T) { srv := newMistralServer(t, "/embeddings", func(t *testing.T, body map[string]interface{}, w http.ResponseWriter) { if body["model"] != "mistral-embed" { diff --git a/internal/entity/models/model_test_helpers_test.go b/internal/entity/models/model_test_helpers_test.go new file mode 100644 index 000000000..8e32defcf --- /dev/null +++ b/internal/entity/models/model_test_helpers_test.go @@ -0,0 +1,16 @@ +package models + +import ( + "strings" + "testing" +) + +func requireNoSuchMethod(t *testing.T, name string, err error) { + t.Helper() + if err == nil { + t.Fatalf("%s: expected no such method error, got nil", name) + } + if !strings.Contains(err.Error(), "no such method") { + t.Fatalf("%s: expected no such method error, got %v", name, err) + } +} diff --git a/internal/entity/models/xunfei.go b/internal/entity/models/xunfei.go index 3cde72eb2..f85a9e480 100644 --- a/internal/entity/models/xunfei.go +++ b/internal/entity/models/xunfei.go @@ -51,18 +51,7 @@ func NewXunFeiModel(baseURL map[string]string, urlSuffix URLSuffix) *XunFeiModel } func (x *XunFeiModel) NewInstance(baseURL map[string]string) ModelDriver { - return &XunFeiModel{ - BaseURL: baseURL, - URLSuffix: x.URLSuffix, - httpClient: &http.Client{ - Transport: &http.Transport{ - MaxIdleConns: 10, - MaxIdleConnsPerHost: 100, - IdleConnTimeout: time.Second * 90, - DisableCompression: false, - }, - }, - } + return NewXunFeiModel(baseURL, x.URLSuffix) } func (x *XunFeiModel) Name() string { @@ -358,43 +347,35 @@ func (x *XunFeiModel) ChatStreamlyWithSender(modelName string, messages []Messag } func (x *XunFeiModel) Embed(modelName *string, texts []string, apiConfig *APIConfig, embeddingConfig *EmbeddingConfig) ([]EmbeddingData, error) { - //TODO implement me - panic("implement me") + return nil, fmt.Errorf("%s, no such method", x.Name()) } func (x *XunFeiModel) Rerank(modelName *string, query string, documents []string, apiConfig *APIConfig, rerankConfig *RerankConfig) (*RerankResponse, error) { - //TODO implement me - panic("implement me") + return nil, fmt.Errorf("%s, no such method", x.Name()) } func (x *XunFeiModel) TranscribeAudio(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig) (*ASRResponse, error) { - //TODO implement me - panic("implement me") + return nil, fmt.Errorf("%s, no such method", x.Name()) } func (x *XunFeiModel) TranscribeAudioWithSender(modelName *string, file *string, apiConfig *APIConfig, asrConfig *ASRConfig, sender func(*string, *string) error) error { - //TODO implement me - panic("implement me") + return fmt.Errorf("%s, no such method", x.Name()) } func (x *XunFeiModel) AudioSpeech(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig) (*TTSResponse, error) { - //TODO implement me - panic("implement me") + return nil, fmt.Errorf("%s, no such method", x.Name()) } func (x *XunFeiModel) AudioSpeechWithSender(modelName *string, audioContent *string, apiConfig *APIConfig, ttsConfig *TTSConfig, sender func(*string, *string) error) error { - //TODO implement me - panic("implement me") + return fmt.Errorf("%s, no such method", x.Name()) } func (x *XunFeiModel) OCRFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, ocrConfig *OCRConfig) (*OCRFileResponse, error) { - //TODO implement me - panic("implement me") + return nil, fmt.Errorf("%s, no such method", x.Name()) } func (x *XunFeiModel) ParseFile(modelName *string, content []byte, url *string, apiConfig *APIConfig, parseFileConfig *ParseFileConfig) (*ParseFileResponse, error) { - //TODO implement me - panic("implement me") + return nil, fmt.Errorf("%s, no such method", x.Name()) } func (x *XunFeiModel) ListModels(apiConfig *APIConfig) ([]string, error) { @@ -457,21 +438,17 @@ func (x *XunFeiModel) ListModels(apiConfig *APIConfig) ([]string, error) { } func (x *XunFeiModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error) { - //TODO implement me - panic("implement me") + return nil, fmt.Errorf("%s, no such method", x.Name()) } func (x *XunFeiModel) CheckConnection(apiConfig *APIConfig) error { - //TODO implement me - panic("implement me") + return fmt.Errorf("%s, no such method", x.Name()) } func (x *XunFeiModel) ListTasks(apiConfig *APIConfig) ([]ListTaskStatus, error) { - //TODO implement me - panic("implement me") + return nil, fmt.Errorf("%s, no such method", x.Name()) } func (x *XunFeiModel) ShowTask(taskID string, apiConfig *APIConfig) (*TaskResponse, error) { - //TODO implement me - panic("implement me") + return nil, fmt.Errorf("%s, no such method", x.Name()) } diff --git a/internal/entity/models/xunfei_test.go b/internal/entity/models/xunfei_test.go new file mode 100644 index 000000000..3b79fca74 --- /dev/null +++ b/internal/entity/models/xunfei_test.go @@ -0,0 +1,67 @@ +package models + +import "testing" + +func TestXunFeiUnsupportedMethodsReturnNoSuchMethod(t *testing.T) { + driver := NewXunFeiModel(map[string]string{"default": "http://unused"}, URLSuffix{}). + NewInstance(map[string]string{"default": "http://unused"}) + modelName := "spark" + text := "hello" + + checks := []struct { + name string + call func() error + }{ + {"Embed", func() error { + _, err := driver.Embed(&modelName, []string{text}, &APIConfig{}, nil) + return err + }}, + {"Rerank", func() error { + _, err := driver.Rerank(&modelName, text, []string{text}, &APIConfig{}, nil) + return err + }}, + {"TranscribeAudio", func() error { + _, err := driver.TranscribeAudio(&modelName, &text, &APIConfig{}, nil) + return err + }}, + {"TranscribeAudioWithSender", func() error { + return driver.TranscribeAudioWithSender(&modelName, &text, &APIConfig{}, nil, nil) + }}, + {"AudioSpeech", func() error { + _, err := driver.AudioSpeech(&modelName, &text, &APIConfig{}, nil) + return err + }}, + {"AudioSpeechWithSender", func() error { + return driver.AudioSpeechWithSender(&modelName, &text, &APIConfig{}, nil, nil) + }}, + {"OCRFile", func() error { + _, err := driver.OCRFile(&modelName, nil, &text, &APIConfig{}, nil) + return err + }}, + {"ParseFile", func() error { + _, err := driver.ParseFile(&modelName, nil, &text, &APIConfig{}, nil) + return err + }}, + {"Balance", func() error { + _, err := driver.Balance(&APIConfig{}) + return err + }}, + {"CheckConnection", func() error { + return driver.CheckConnection(&APIConfig{}) + }}, + {"ListTasks", func() error { + _, err := driver.ListTasks(&APIConfig{}) + return err + }}, + {"ShowTask", func() error { + _, err := driver.ShowTask("task-id", &APIConfig{}) + return err + }}, + } + + for _, check := range checks { + t.Run(check.name, func(t *testing.T) { + requireNoSuchMethod(t, check.name, check.call()) + }) + } +}