diff --git a/internal/entity/models/mineru.go b/internal/entity/models/mineru.go index d4c39a2583..c41a3ef6bd 100644 --- a/internal/entity/models/mineru.go +++ b/internal/entity/models/mineru.go @@ -93,7 +93,45 @@ func (m *MinerUModel) Balance(ctx context.Context, apiConfig *APIConfig) (map[st } func (m *MinerUModel) CheckConnection(ctx context.Context, apiConfig *APIConfig) error { - return fmt.Errorf("%s no such method", m.Name()) + if err := m.baseModel.APIConfigCheck(apiConfig); err != nil { + return err + } + + resolvedBaseURL, err := m.baseModel.GetBaseURL(apiConfig) + if err != nil { + return err + } + + // Use the doc_parse endpoint with a dummy task ID to verify connectivity + // and authentication. The API returns 401/403 for invalid credentials, + // or 404 (task not found) when the key is valid but the task doesn't + // exist — both cases confirm the server is reachable. + apiURL := fmt.Sprintf("%s/api/v4/%s", resolvedBaseURL, m.baseModel.URLSuffix.DocumentParse) + + ctx, cancel := context.WithTimeout(ctx, nonStreamCallTimeout) + defer cancel() + + req, err := http.NewRequestWithContext(ctx, "GET", apiURL, nil) + if err != nil { + return fmt.Errorf("failed to create request: %w", err) + } + + req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", *apiConfig.ApiKey)) + + resp, err := m.baseModel.httpClient.Do(req) + if err != nil { + return fmt.Errorf("connection failed: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode == http.StatusUnauthorized || resp.StatusCode == http.StatusForbidden { + body, _ := io.ReadAll(resp.Body) + return fmt.Errorf("authentication failed (HTTP %d): %s", resp.StatusCode, string(body)) + } + + // Non-auth errors (e.g. 404) indicate the server is reachable and + // credentials are valid. + return nil } type mineruTaskSubmitResponse struct { diff --git a/internal/service/model_service.go b/internal/service/model_service.go index a5ddd32fc4..d31b26c662 100644 --- a/internal/service/model_service.go +++ b/internal/service/model_service.go @@ -1000,7 +1000,29 @@ func verifyProviderModel(ctx context.Context, driver modelModule.ModelDriver, pr } if len(modelsToVerify) == 0 { - return modelVerifyResult, fmt.Errorf("no models found for provider") + // Third fallback: try to fetch remote models via the provider's API, + // mirroring Python's get_model_list() fallback in verify_api_key. + remoteModels, listErr := driver.ListModels(ctx, apiConfig) + if listErr == nil { + for _, rm := range remoteModels { + modelName := strings.TrimSpace(rm.Name) + if modelName == "" { + continue + } + modelTypes := rm.ModelTypes + if len(modelTypes) == 0 { + modelTypes = modelModule.InferModelTypes(modelName) + } + modelsToVerify = append(modelsToVerify, &modelModule.Model{ + Name: modelName, + ModelTypes: modelTypes, + }) + } + } + + if len(modelsToVerify) == 0 { + return modelVerifyResult, fmt.Errorf("no models found for provider") + } } var errs []error @@ -1036,6 +1058,8 @@ func verifyProviderModel(ctx context.Context, driver modelModule.ModelDriver, pr err = verifyASRModel(ctx, driver, modelName, apiConfig) case "ocr": err = verifyOCRModel(ctx, driver, modelName, apiConfig) + case "doc_parse": + err = driver.CheckConnection(ctx, apiConfig) default: continue }