mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-18 04:10:51 +08:00
- 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.
112 lines
2.8 KiB
Go
112 lines
2.8 KiB
Go
package routing
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
|
|
"github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor"
|
|
log "github.com/sirupsen/logrus"
|
|
)
|
|
|
|
// Executor handles request execution with fallback support.
|
|
type Executor struct {
|
|
router *Router
|
|
}
|
|
|
|
// NewExecutor creates a new executor with the given router.
|
|
func NewExecutor(router *Router) *Executor {
|
|
return &Executor{router: router}
|
|
}
|
|
|
|
// Execute sends the request through the routing decision.
|
|
func (e *Executor) Execute(ctx context.Context, req executor.Request) (executor.Response, error) {
|
|
decision := e.router.Resolve(req.Model)
|
|
|
|
log.Debugf("routing: %s -> %s (%d candidates)",
|
|
decision.RequestedModel,
|
|
decision.ResolvedModel,
|
|
len(decision.Candidates))
|
|
|
|
var lastErr error
|
|
tried := make(map[string]struct{})
|
|
|
|
for i, candidate := range decision.Candidates {
|
|
key := candidate.Provider.Name() + "/" + candidate.Model
|
|
if _, ok := tried[key]; ok {
|
|
continue
|
|
}
|
|
tried[key] = struct{}{}
|
|
|
|
log.Debugf("routing: trying candidate %d/%d: %s with model %s",
|
|
i+1, len(decision.Candidates), candidate.Provider.Name(), candidate.Model)
|
|
|
|
req.Model = candidate.Model
|
|
resp, err := candidate.Provider.Execute(ctx, candidate.Model, req)
|
|
if err == nil {
|
|
return resp, nil
|
|
}
|
|
|
|
lastErr = err
|
|
log.Debugf("routing: candidate failed: %v", err)
|
|
|
|
// Check if it's a fatal error (not retryable)
|
|
if isFatalError(err) {
|
|
break
|
|
}
|
|
}
|
|
|
|
if lastErr != nil {
|
|
return executor.Response{}, lastErr
|
|
}
|
|
return executor.Response{}, errors.New("no available providers")
|
|
}
|
|
|
|
// ExecuteStream sends a streaming request through the routing decision.
|
|
func (e *Executor) ExecuteStream(ctx context.Context, req executor.Request) (<-chan executor.StreamChunk, error) {
|
|
decision := e.router.Resolve(req.Model)
|
|
|
|
log.Debugf("routing stream: %s -> %s (%d candidates)",
|
|
decision.RequestedModel,
|
|
decision.ResolvedModel,
|
|
len(decision.Candidates))
|
|
|
|
var lastErr error
|
|
tried := make(map[string]struct{})
|
|
|
|
for i, candidate := range decision.Candidates {
|
|
key := candidate.Provider.Name() + "/" + candidate.Model
|
|
if _, ok := tried[key]; ok {
|
|
continue
|
|
}
|
|
tried[key] = struct{}{}
|
|
|
|
log.Debugf("routing stream: trying candidate %d/%d: %s with model %s",
|
|
i+1, len(decision.Candidates), candidate.Provider.Name(), candidate.Model)
|
|
|
|
req.Model = candidate.Model
|
|
chunks, err := candidate.Provider.ExecuteStream(ctx, candidate.Model, req)
|
|
if err == nil {
|
|
return chunks, nil
|
|
}
|
|
|
|
lastErr = err
|
|
log.Debugf("routing stream: candidate failed: %v", err)
|
|
|
|
if isFatalError(err) {
|
|
break
|
|
}
|
|
}
|
|
|
|
if lastErr != nil {
|
|
return nil, lastErr
|
|
}
|
|
return nil, errors.New("no available providers")
|
|
}
|
|
|
|
// isFatalError returns true if the error is not retryable.
|
|
func isFatalError(err error) bool {
|
|
// TODO: implement based on error type
|
|
// For now, all errors are retryable
|
|
return false
|
|
}
|