fix(go): wire CheckConnection to ListModels in ollama, lm-studio, and vllm (#14614)

### What problem does this PR solve?

Three Go drivers had `CheckConnection` returning a hardcoded `no such
method` error, even though each one already has a working `ListModels`
that hits the configured base URL with the configured API key. So the
"Check connection" button in the model provider UI always failed for
these three providers, even when the underlying setup was fine.

Affected drivers:

- `internal/entity/models/ollama.go`
- `internal/entity/models/lmstudio.go`
- `internal/entity/models/vllm.go`

This is a real user-facing gap because Ollama and LM Studio are two of
the most popular local LLM runners, and vLLM is widely used for
self-hosted deployments.

### What this PR includes

For each of the three drivers, replace the stub with a small
implementation that calls `ListModels` and returns its error:

```go
func (o *OllamaModel) CheckConnection(apiConfig *APIConfig) error {
    _, err := o.ListModels(apiConfig)
    return err
}
```

This is the exact pattern that xai, moonshot, deepseek, aliyun, and
gitee already use for the same method.

No JSON change. No factory change. No interface change.

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)

### How was this tested?

- `go build ./internal/entity/models/...` in a clean go 1.25 image (the
go.mod minimum) returns exit 0.
- The full ModelDriver interface still resolves on each driver
(NewInstance, Name, ChatWithMessages, ChatStreamlyWithSender, Encode,
Rerank, ListModels, Balance, CheckConnection).
- Pattern parity with the existing xai, moonshot, deepseek, aliyun, and
gitee CheckConnection methods.

Closes #14609
This commit is contained in:
Panda Dev
2026-05-08 06:00:10 +02:00
committed by GitHub
parent 5d28bb0701
commit 2fd8cdc3cc
3 changed files with 114 additions and 12 deletions

View File

@ -363,11 +363,19 @@ func (l *LmStudioModel) Rerank(modelName *string, query string, texts []string,
// ListModels list supported models
func (l *LmStudioModel) ListModels(apiConfig *APIConfig) ([]string, error) {
var region = "default"
if apiConfig.Region != nil {
if apiConfig != nil && apiConfig.Region != nil && *apiConfig.Region != "" {
region = *apiConfig.Region
}
url := fmt.Sprintf("%s/%s", l.BaseURL[region], l.URLSuffix.Models)
baseURL := l.BaseURL[region]
if baseURL == "" {
baseURL = l.BaseURL["default"]
}
if baseURL == "" {
return nil, fmt.Errorf("missing base URL: please configure the local access address for LM Studio (e.g., http://127.0.0.1:1234/v1)")
}
url := fmt.Sprintf("%s/%s", baseURL, l.URLSuffix.Models)
reqBody := map[string]interface{}{}
@ -382,7 +390,12 @@ func (l *LmStudioModel) ListModels(apiConfig *APIConfig) ([]string, error) {
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", *apiConfig.ApiKey))
// LM Studio is a local provider and the API key is optional. Only
// set the Authorization header when a non-empty key was supplied.
// This also avoids a nil-pointer dereference on apiConfig or ApiKey.
if apiConfig != nil && apiConfig.ApiKey != nil && *apiConfig.ApiKey != "" {
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", *apiConfig.ApiKey))
}
resp, err := l.httpClient.Do(req)
if err != nil {
@ -420,6 +433,27 @@ func (l *LmStudioModel) Balance(apiConfig *APIConfig) (map[string]interface{}, e
return nil, fmt.Errorf("no such method")
}
// CheckConnection verifies that the configured LM Studio base URL
// is reachable and that the API key (if any) is accepted, by issuing
// a lightweight ListModels call. The empty-URL guard runs first so
// a user who has not yet set the local access address gets a clear,
// actionable error instead of a low-level transport message.
func (l *LmStudioModel) CheckConnection(apiConfig *APIConfig) error {
return fmt.Errorf("no such method")
var region = "default"
if apiConfig != nil && apiConfig.Region != nil && *apiConfig.Region != "" {
region = *apiConfig.Region
}
baseURL := l.BaseURL[region]
if baseURL == "" {
baseURL = l.BaseURL["default"]
}
if baseURL == "" {
return fmt.Errorf("missing base URL: please configure the local access address for LM Studio (e.g., http://127.0.0.1:1234/v1)")
}
if _, err := l.ListModels(apiConfig); err != nil {
return fmt.Errorf("connection check failed: %w", err)
}
return nil
}

View File

@ -361,11 +361,19 @@ func (o *OllamaModel) Rerank(modelName *string, query string, texts []string, ap
func (o *OllamaModel) ListModels(apiConfig *APIConfig) ([]string, error) {
var region = "default"
if apiConfig.Region != nil {
if apiConfig != nil && apiConfig.Region != nil && *apiConfig.Region != "" {
region = *apiConfig.Region
}
url := fmt.Sprintf("%s/%s", o.BaseURL[region], o.URLSuffix.Models)
baseURL := o.BaseURL[region]
if baseURL == "" {
baseURL = o.BaseURL["default"]
}
if baseURL == "" {
return nil, fmt.Errorf("missing base URL: please configure the local access address for Ollama (e.g., http://127.0.0.1:11434/v1)")
}
url := fmt.Sprintf("%s/%s", baseURL, o.URLSuffix.Models)
reqBody := map[string]interface{}{}
@ -380,7 +388,12 @@ func (o *OllamaModel) ListModels(apiConfig *APIConfig) ([]string, error) {
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", *apiConfig.ApiKey))
// Ollama is a local provider and the API key is optional. Only set
// the Authorization header when a non-empty key was supplied. This
// also avoids a nil-pointer dereference on apiConfig or ApiKey.
if apiConfig != nil && apiConfig.ApiKey != nil && *apiConfig.ApiKey != "" {
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", *apiConfig.ApiKey))
}
resp, err := o.httpClient.Do(req)
if err != nil {
@ -418,6 +431,27 @@ func (o *OllamaModel) Balance(apiConfig *APIConfig) (map[string]interface{}, err
return nil, fmt.Errorf("no such method")
}
// CheckConnection verifies that the configured Ollama base URL is
// reachable and that the API key (if any) is accepted, by issuing a
// lightweight ListModels call. The empty-URL guard runs first so a
// user who has not yet set the local access address gets a clear,
// actionable error instead of a low-level transport message.
func (o *OllamaModel) CheckConnection(apiConfig *APIConfig) error {
return fmt.Errorf("no such method")
var region = "default"
if apiConfig != nil && apiConfig.Region != nil && *apiConfig.Region != "" {
region = *apiConfig.Region
}
baseURL := o.BaseURL[region]
if baseURL == "" {
baseURL = o.BaseURL["default"]
}
if baseURL == "" {
return fmt.Errorf("missing base URL: please configure the local access address for Ollama (e.g., http://127.0.0.1:11434/v1)")
}
if _, err := o.ListModels(apiConfig); err != nil {
return fmt.Errorf("connection check failed: %w", err)
}
return nil
}

View File

@ -376,11 +376,19 @@ func (z *VllmModel) Encode(modelName *string, texts []string, apiConfig *APIConf
func (z *VllmModel) ListModels(apiConfig *APIConfig) ([]string, error) {
var region = "default"
if apiConfig.Region != nil {
if apiConfig != nil && apiConfig.Region != nil && *apiConfig.Region != "" {
region = *apiConfig.Region
}
url := fmt.Sprintf("%s/%s", z.BaseURL[region], z.URLSuffix.Models)
baseURL := z.BaseURL[region]
if baseURL == "" {
baseURL = z.BaseURL["default"]
}
if baseURL == "" {
return nil, fmt.Errorf("missing base URL: please configure the local access address for vLLM (e.g., http://127.0.0.1:8000/v1)")
}
url := fmt.Sprintf("%s/%s", baseURL, z.URLSuffix.Models)
reqBody := map[string]interface{}{}
@ -395,7 +403,12 @@ func (z *VllmModel) ListModels(apiConfig *APIConfig) ([]string, error) {
}
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", *apiConfig.ApiKey))
// vLLM is a local provider and the API key is optional. Only set
// the Authorization header when a non-empty key was supplied. This
// also avoids a nil-pointer dereference on apiConfig or ApiKey.
if apiConfig != nil && apiConfig.ApiKey != nil && *apiConfig.ApiKey != "" {
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", *apiConfig.ApiKey))
}
resp, err := z.httpClient.Do(req)
if err != nil {
@ -433,8 +446,29 @@ func (z *VllmModel) Balance(apiConfig *APIConfig) (map[string]interface{}, error
return nil, fmt.Errorf("no such method")
}
// CheckConnection verifies that the configured vLLM base URL is
// reachable and that the API key (if any) is accepted, by issuing a
// lightweight ListModels call. The empty-URL guard runs first so a
// user who has not yet set the local access address gets a clear,
// actionable error instead of a low-level transport message.
func (z *VllmModel) CheckConnection(apiConfig *APIConfig) error {
return fmt.Errorf("no such method")
var region = "default"
if apiConfig != nil && apiConfig.Region != nil && *apiConfig.Region != "" {
region = *apiConfig.Region
}
baseURL := z.BaseURL[region]
if baseURL == "" {
baseURL = z.BaseURL["default"]
}
if baseURL == "" {
return fmt.Errorf("missing base URL: please configure the local access address for vLLM (e.g., http://127.0.0.1:8000/v1)")
}
if _, err := z.ListModels(apiConfig); err != nil {
return fmt.Errorf("connection check failed: %w", err)
}
return nil
}
// Rerank calculates similarity scores between query and texts