mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-18 12:20:52 +08:00
feat(routing): implement unified model routing with OAuth and API key providers
- Added a new routing package to manage provider registration and model resolution. - Introduced Router, Executor, and Provider interfaces to handle different provider types. - Implemented OAuthProvider and APIKeyProvider to support OAuth and API key authentication. - Enhanced DefaultModelMapper to include OAuth model alias handling and fallback mechanisms. - Updated context management in API handlers to preserve fallback models. - Added tests for routing logic and provider selection. - Enhanced Claude request conversion to handle reasoning content based on thinking mode.
This commit is contained in:
127
internal/routing/router.go
Normal file
127
internal/routing/router.go
Normal file
@@ -0,0 +1,127 @@
|
||||
package routing
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
|
||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/thinking"
|
||||
)
|
||||
|
||||
// Router resolves models to provider candidates.
|
||||
type Router struct {
|
||||
registry *Registry
|
||||
modelMappings map[string]string // normalized from -> to
|
||||
oauthAliases map[string][]string // normalized model -> []alias
|
||||
}
|
||||
|
||||
// NewRouter creates a new router with the given configuration.
|
||||
func NewRouter(registry *Registry, cfg *config.Config) *Router {
|
||||
r := &Router{
|
||||
registry: registry,
|
||||
modelMappings: make(map[string]string),
|
||||
oauthAliases: make(map[string][]string),
|
||||
}
|
||||
|
||||
if cfg != nil {
|
||||
r.loadModelMappings(cfg.AmpCode.ModelMappings)
|
||||
r.loadOAuthAliases(cfg.OAuthModelAlias)
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
// RoutingDecision contains the resolved routing information.
|
||||
type RoutingDecision struct {
|
||||
RequestedModel string // Original model from request
|
||||
ResolvedModel string // After model-mappings
|
||||
Candidates []ProviderCandidate // Ordered list of providers to try
|
||||
}
|
||||
|
||||
// Resolve determines the routing decision for the requested model.
|
||||
func (r *Router) Resolve(requestedModel string) *RoutingDecision {
|
||||
// 1. Extract thinking suffix
|
||||
suffixResult := thinking.ParseSuffix(requestedModel)
|
||||
baseModel := suffixResult.ModelName
|
||||
|
||||
// 2. Apply model-mappings
|
||||
targetModel := r.applyMappings(baseModel)
|
||||
|
||||
// 3. Find primary providers
|
||||
candidates := r.findCandidates(targetModel, suffixResult)
|
||||
|
||||
// 4. Add fallback aliases
|
||||
for _, alias := range r.oauthAliases[strings.ToLower(targetModel)] {
|
||||
candidates = append(candidates, r.findCandidates(alias, suffixResult)...)
|
||||
}
|
||||
|
||||
// 5. Sort by priority
|
||||
sort.Slice(candidates, func(i, j int) bool {
|
||||
return candidates[i].Provider.Priority() < candidates[j].Provider.Priority()
|
||||
})
|
||||
|
||||
return &RoutingDecision{
|
||||
RequestedModel: requestedModel,
|
||||
ResolvedModel: targetModel,
|
||||
Candidates: candidates,
|
||||
}
|
||||
}
|
||||
|
||||
// applyMappings applies model-mappings configuration.
|
||||
func (r *Router) applyMappings(model string) string {
|
||||
key := strings.ToLower(strings.TrimSpace(model))
|
||||
if mapped, ok := r.modelMappings[key]; ok {
|
||||
return mapped
|
||||
}
|
||||
return model
|
||||
}
|
||||
|
||||
// findCandidates finds all provider candidates for a model.
|
||||
func (r *Router) findCandidates(model string, suffixResult thinking.SuffixResult) []ProviderCandidate {
|
||||
var candidates []ProviderCandidate
|
||||
|
||||
for _, p := range r.registry.All() {
|
||||
if !p.SupportsModel(model) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Apply thinking suffix if needed
|
||||
actualModel := model
|
||||
if suffixResult.HasSuffix && !thinking.ParseSuffix(model).HasSuffix {
|
||||
actualModel = model + "(" + suffixResult.RawSuffix + ")"
|
||||
}
|
||||
|
||||
if p.Available(actualModel) {
|
||||
candidates = append(candidates, ProviderCandidate{
|
||||
Provider: p,
|
||||
Model: actualModel,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return candidates
|
||||
}
|
||||
|
||||
// loadModelMappings loads model-mappings from config.
|
||||
func (r *Router) loadModelMappings(mappings []config.AmpModelMapping) {
|
||||
for _, m := range mappings {
|
||||
from := strings.ToLower(strings.TrimSpace(m.From))
|
||||
to := strings.TrimSpace(m.To)
|
||||
if from != "" && to != "" {
|
||||
r.modelMappings[from] = to
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// loadOAuthAliases loads oauth-model-alias from config.
|
||||
func (r *Router) loadOAuthAliases(aliases map[string][]config.OAuthModelAlias) {
|
||||
for _, entries := range aliases {
|
||||
for _, entry := range entries {
|
||||
name := strings.ToLower(strings.TrimSpace(entry.Name))
|
||||
alias := strings.TrimSpace(entry.Alias)
|
||||
if name != "" && alias != "" && name != alias {
|
||||
r.oauthAliases[name] = append(r.oauthAliases[name], alias)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user