mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-03 04:50:52 +08:00
feat: add auto model resolution and model creation timestamp tracking
- Add 'created' field to model registry for tracking model creation time - Implement GetFirstAvailableModel() to find the first available model by newest creation timestamp - Add ResolveAutoModel() utility function to resolve "auto" model name to actual available model - Update request handler to resolve "auto" model before processing requests - Ensures automatic model selection when "auto" is specified as model name This enables dynamic model selection based on availability and creation time, improving the user experience when no specific model is requested.
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
package registry
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -800,6 +801,9 @@ func (r *ModelRegistry) convertModelToMap(model *ModelInfo, handlerType string)
|
||||
if model.Type != "" {
|
||||
result["type"] = model.Type
|
||||
}
|
||||
if model.Created != 0 {
|
||||
result["created"] = model.Created
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
@@ -821,3 +825,47 @@ func (r *ModelRegistry) CleanupExpiredQuotas() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// GetFirstAvailableModel returns the first available model for the given handler type.
|
||||
// It prioritizes models by their creation timestamp (newest first) and checks if they have
|
||||
// available clients that are not suspended or over quota.
|
||||
//
|
||||
// Parameters:
|
||||
// - handlerType: The API handler type (e.g., "openai", "claude", "gemini")
|
||||
//
|
||||
// Returns:
|
||||
// - string: The model ID of the first available model, or empty string if none available
|
||||
// - error: An error if no models are available
|
||||
func (r *ModelRegistry) GetFirstAvailableModel(handlerType string) (string, error) {
|
||||
r.mutex.RLock()
|
||||
defer r.mutex.RUnlock()
|
||||
|
||||
// Get all available models for this handler type
|
||||
models := r.GetAvailableModels(handlerType)
|
||||
if len(models) == 0 {
|
||||
return "", fmt.Errorf("no models available for handler type: %s", handlerType)
|
||||
}
|
||||
|
||||
// Sort models by creation timestamp (newest first)
|
||||
sort.Slice(models, func(i, j int) bool {
|
||||
// Extract created timestamps from map
|
||||
createdI, okI := models[i]["created"].(int64)
|
||||
createdJ, okJ := models[j]["created"].(int64)
|
||||
if !okI || !okJ {
|
||||
return false
|
||||
}
|
||||
return createdI > createdJ
|
||||
})
|
||||
|
||||
// Find the first model with available clients
|
||||
for _, model := range models {
|
||||
if modelID, ok := model["id"].(string); ok {
|
||||
if count := r.GetModelCount(modelID); count > 0 {
|
||||
return modelID, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("no available clients for any model in handler type: %s", handlerType)
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/registry"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// GetProviderName determines all AI service providers capable of serving a registered model.
|
||||
@@ -59,6 +60,30 @@ func GetProviderName(modelName string) []string {
|
||||
return providers
|
||||
}
|
||||
|
||||
// ResolveAutoModel resolves the "auto" model name to an actual available model.
|
||||
// It uses an empty handler type to get any available model from the registry.
|
||||
//
|
||||
// Parameters:
|
||||
// - modelName: The model name to check (should be "auto")
|
||||
//
|
||||
// Returns:
|
||||
// - string: The resolved model name, or the original if not "auto" or resolution fails
|
||||
func ResolveAutoModel(modelName string) string {
|
||||
if modelName != "auto" {
|
||||
return modelName
|
||||
}
|
||||
|
||||
// Use empty string as handler type to get any available model
|
||||
firstModel, err := registry.GetGlobalRegistry().GetFirstAvailableModel("")
|
||||
if err != nil {
|
||||
log.Warnf("Failed to resolve 'auto' model: %v, falling back to original model name", err)
|
||||
return modelName
|
||||
}
|
||||
|
||||
log.Infof("Resolved 'auto' model to: %s", firstModel)
|
||||
return firstModel
|
||||
}
|
||||
|
||||
// IsOpenAICompatibilityAlias checks if the given model name is an alias
|
||||
// configured for OpenAI compatibility routing.
|
||||
//
|
||||
|
||||
@@ -295,11 +295,14 @@ func (h *BaseAPIHandler) ExecuteStreamWithAuthManager(ctx context.Context, handl
|
||||
}
|
||||
|
||||
func (h *BaseAPIHandler) getRequestDetails(modelName string) (providers []string, normalizedModel string, metadata map[string]any, err *interfaces.ErrorMessage) {
|
||||
providerName, extractedModelName, isDynamic := h.parseDynamicModel(modelName)
|
||||
// Resolve "auto" model to an actual available model first
|
||||
resolvedModelName := util.ResolveAutoModel(modelName)
|
||||
|
||||
providerName, extractedModelName, isDynamic := h.parseDynamicModel(resolvedModelName)
|
||||
|
||||
// First, normalize the model name to handle suffixes like "-thinking-128"
|
||||
// This needs to happen before determining the provider for non-dynamic models.
|
||||
normalizedModel, metadata = normalizeModelMetadata(modelName)
|
||||
normalizedModel, metadata = normalizeModelMetadata(resolvedModelName)
|
||||
|
||||
if isDynamic {
|
||||
providers = []string{providerName}
|
||||
|
||||
Reference in New Issue
Block a user