diff --git a/internal/entity/models/lmstudio.go b/internal/entity/models/lmstudio.go index 55122bedc..ec6a47323 100644 --- a/internal/entity/models/lmstudio.go +++ b/internal/entity/models/lmstudio.go @@ -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 } diff --git a/internal/entity/models/ollama.go b/internal/entity/models/ollama.go index 9cc1907f5..4f680e0cc 100644 --- a/internal/entity/models/ollama.go +++ b/internal/entity/models/ollama.go @@ -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 } diff --git a/internal/entity/models/vllm.go b/internal/entity/models/vllm.go index 8d675f904..1497012a7 100644 --- a/internal/entity/models/vllm.go +++ b/internal/entity/models/vllm.go @@ -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