mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-03 04:50:52 +08:00
Merge pull request #237 from TUGOhost/feature/support_auto_model
feat: add auto model resolution and model creation timestamp tracking
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