package gateway import ( "context" "encoding/json" "fmt" "time" "github.com/rose_cat707/luminary/internal/model" "github.com/rose_cat707/luminary/internal/provider" ) func (s *Service) healthCheckEndpoints(p *model.Provider) []provider.EndpointType { var out []provider.EndpointType if s.providerAllowsEndpoint(p, provider.EndpointListModels) { out = append(out, provider.EndpointListModels) } switch p.Category { case model.CategoryEmbedding: if s.providerAllowsEndpoint(p, provider.EndpointEmbeddings) { out = append(out, provider.EndpointEmbeddings) } case model.CategoryRerank: if s.providerAllowsEndpoint(p, provider.EndpointRerank) { out = append(out, provider.EndpointRerank) } } return out } func (s *Service) runHealthChecks( ctx context.Context, p *model.Provider, keys []model.ProviderKey, cfg provider.ProviderConfig, ) (model.ProviderStatus, int64, string) { endpoints := s.healthCheckEndpoints(p) if len(endpoints) == 0 { return model.ProviderUnhealthy, 0, "no health check endpoint enabled" } start := time.Now() var lastErr string for _, ep := range endpoints { status, _, msg := s.runHealthCheck(ctx, p, keys, cfg, ep) if status == model.ProviderHealthy { return model.ProviderHealthy, time.Since(start).Milliseconds(), "" } if msg != "" { lastErr = msg } } if lastErr == "" { lastErr = "health check failed" } return model.ProviderUnhealthy, time.Since(start).Milliseconds(), lastErr } func (s *Service) runHealthCheck( ctx context.Context, p *model.Provider, keys []model.ProviderKey, cfg provider.ProviderConfig, endpoint provider.EndpointType, ) (model.ProviderStatus, int64, string) { start := time.Now() latency := func() int64 { return time.Since(start).Milliseconds() } switch endpoint { case provider.EndpointListModels: if len(keys) == 0 && p.Type == model.ProviderOllama { if err := s.openai.HealthCheckWithConfig(ctx, "", cfg); err == nil { return model.ProviderHealthy, latency(), "" } return model.ProviderUnhealthy, latency(), "health check failed" } for _, k := range keys { if !k.Enabled { continue } apiKey, err := s.DecryptAPIKey(k.APIKeyEnc) if err != nil { continue } var checkErr error if p.Type == model.ProviderAnthropic { checkErr = s.anthropic.HealthCheckWithConfig(ctx, apiKey, cfg) } else { checkErr = s.openai.HealthCheckWithConfig(ctx, apiKey, cfg) } if checkErr == nil { return model.ProviderHealthy, latency(), "" } } return model.ProviderUnhealthy, latency(), "health check failed" case provider.EndpointEmbeddings, provider.EndpointRerank: body, err := healthProbeBody(s, p, endpoint) if err != nil { return model.ProviderUnhealthy, latency(), err.Error() } if len(keys) == 0 && p.Type == model.ProviderOllama { ok, msg := s.probeEndpoint(ctx, "", cfg, endpoint, body) if ok { return model.ProviderHealthy, latency(), "" } return model.ProviderUnhealthy, latency(), msg } var lastErr string for _, k := range keys { if !k.Enabled { continue } apiKey, err := s.DecryptAPIKey(k.APIKeyEnc) if err != nil { continue } if ok, msg := s.probeEndpoint(ctx, apiKey, cfg, endpoint, body); ok { return model.ProviderHealthy, latency(), "" } else if msg != "" { lastErr = msg } } if lastErr == "" { lastErr = "health check failed" } return model.ProviderUnhealthy, latency(), lastErr default: return model.ProviderUnhealthy, latency(), "unsupported health check endpoint" } } func (s *Service) probeEndpoint( ctx context.Context, apiKey string, cfg provider.ProviderConfig, endpoint provider.EndpointType, body []byte, ) (bool, string) { resp, err := s.openai.ForwardWithConfig(ctx, apiKey, cfg, endpoint, body) if err != nil { return false, err.Error() } if resp == nil { return false, "empty response" } if isHealthCheckHTTPStatus(resp.StatusCode) { return true, "" } return false, fmt.Sprintf("%s: HTTP %d", endpoint, resp.StatusCode) } func healthProbeBody(s *Service, p *model.Provider, endpoint provider.EndpointType) ([]byte, error) { modelID := probeModelID(s, p.ID, endpoint) switch endpoint { case provider.EndpointEmbeddings: return json.Marshal(map[string]string{"model": modelID, "input": "."}) case provider.EndpointRerank: return json.Marshal(map[string]any{ "model": modelID, "query": "ping", "documents": []string{"ping"}, }) default: return nil, fmt.Errorf("unsupported probe endpoint %s", endpoint) } } func probeModelID(s *Service, providerID uint, endpoint provider.EndpointType) string { for _, m := range s.cache.GetModels(providerID) { if m.Enabled && m.ModelID != "" { return m.ModelID } } switch endpoint { case provider.EndpointEmbeddings: return "text-embedding-3-small" case provider.EndpointRerank: return "rerank-multilingual-v3.0" default: return "health-check" } } func isHealthCheckHTTPStatus(code int) bool { if code >= 200 && code < 300 { return true } // Upstream reachable but rejected payload/model — still counts as healthy. return code == 400 || code == 422 }