mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-31 08:25:35 +00:00
fix(scheduler): 为调度倍率补充账号倍率回退
This commit is contained in:
@@ -506,7 +506,7 @@ func diffSettings(before *service.SystemSettings, after *service.SystemSettings,
|
||||
if before.OpenAILowUpstreamRatePriorityEnabled != after.OpenAILowUpstreamRatePriorityEnabled {
|
||||
changed = append(changed, "openai_low_upstream_rate_priority_enabled")
|
||||
}
|
||||
if before.OpenAIOAuthSchedulingRateMultiplier != after.OpenAIOAuthSchedulingRateMultiplier {
|
||||
if !equalNullableFloat(before.OpenAIOAuthSchedulingRateMultiplier, after.OpenAIOAuthSchedulingRateMultiplier) {
|
||||
changed = append(changed, "openai_oauth_scheduling_rate_multiplier")
|
||||
}
|
||||
if before.OpenAIAdvancedSchedulerEnabled != after.OpenAIAdvancedSchedulerEnabled {
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/Wei-Shaw/sub2api/internal/config"
|
||||
"github.com/Wei-Shaw/sub2api/internal/service"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestSettingHandler_OAuthSchedulingRateRoundTrip(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
for _, tt := range []struct {
|
||||
name string
|
||||
initial map[string]string
|
||||
body string
|
||||
stored string
|
||||
value any
|
||||
status int
|
||||
}{
|
||||
{"absent keeps default", nil, `{}`, "1", float64(1), http.StatusOK},
|
||||
{"omitted preserves override", map[string]string{service.SettingKeyOpenAIOAuthSchedulingRateMultiplier: "0.7"}, `{}`, "0.7", 0.7, http.StatusOK},
|
||||
{"clear override", map[string]string{service.SettingKeyOpenAIOAuthSchedulingRateMultiplier: "0.7"}, `{"openai_oauth_scheduling_rate_multiplier":null}`, "", nil, http.StatusOK},
|
||||
{"omitted preserves cleared", map[string]string{service.SettingKeyOpenAIOAuthSchedulingRateMultiplier: ""}, `{}`, "", nil, http.StatusOK},
|
||||
{"set override", nil, `{"openai_oauth_scheduling_rate_multiplier":0.7}`, "0.7", 0.7, http.StatusOK},
|
||||
{"zero is explicit", nil, `{"openai_oauth_scheduling_rate_multiplier":0}`, "0", float64(0), http.StatusOK},
|
||||
{"negative is rejected", nil, `{"openai_oauth_scheduling_rate_multiplier":-1}`, "", nil, http.StatusBadRequest},
|
||||
{"string is rejected", nil, `{"openai_oauth_scheduling_rate_multiplier":"invalid"}`, "", nil, http.StatusBadRequest},
|
||||
} {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
repo := &settingHandlerRepoStub{values: tt.initial}
|
||||
svc := service.NewSettingService(repo, &config.Config{})
|
||||
handler := NewSettingHandler(svc, nil, nil, nil, nil, nil, nil)
|
||||
rec := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(rec)
|
||||
c.Request = httptest.NewRequest(http.MethodPut, "/api/v1/admin/settings", bytes.NewBufferString(tt.body))
|
||||
c.Request.Header.Set("Content-Type", "application/json")
|
||||
handler.UpdateSettings(c)
|
||||
require.Equal(t, tt.status, rec.Code, rec.Body.String())
|
||||
if tt.status != http.StatusOK {
|
||||
require.Empty(t, repo.lastUpdates)
|
||||
return
|
||||
}
|
||||
require.Equal(t, tt.stored, repo.values[service.SettingKeyOpenAIOAuthSchedulingRateMultiplier])
|
||||
var body struct {
|
||||
Data map[string]any `json:"data"`
|
||||
}
|
||||
require.NoError(t, json.Unmarshal(rec.Body.Bytes(), &body))
|
||||
require.Contains(t, body.Data, service.SettingKeyOpenAIOAuthSchedulingRateMultiplier)
|
||||
require.Equal(t, tt.value, body.Data[service.SettingKeyOpenAIOAuthSchedulingRateMultiplier])
|
||||
|
||||
getRec := httptest.NewRecorder()
|
||||
getContext, _ := gin.CreateTestContext(getRec)
|
||||
getContext.Request = httptest.NewRequest(http.MethodGet, "/api/v1/admin/settings", nil)
|
||||
handler.GetSettings(getContext)
|
||||
require.Equal(t, http.StatusOK, getRec.Code, getRec.Body.String())
|
||||
require.NoError(t, json.Unmarshal(getRec.Body.Bytes(), &body))
|
||||
require.Contains(t, body.Data, service.SettingKeyOpenAIOAuthSchedulingRateMultiplier)
|
||||
require.Equal(t, tt.value, body.Data[service.SettingKeyOpenAIOAuthSchedulingRateMultiplier])
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSettingsAuditTracksOAuthRateValueNotPointer(t *testing.T) {
|
||||
beforeRate, afterRate := 0.7, 0.7
|
||||
before := &service.SystemSettings{OpenAIOAuthSchedulingRateMultiplier: &beforeRate}
|
||||
after := &service.SystemSettings{OpenAIOAuthSchedulingRateMultiplier: &afterRate}
|
||||
require.NotContains(t, diffSettings(before, after, nil, nil, UpdateSettingsRequest{}), service.SettingKeyOpenAIOAuthSchedulingRateMultiplier)
|
||||
after.OpenAIOAuthSchedulingRateMultiplier = nil
|
||||
require.Contains(t, diffSettings(before, after, nil, nil, UpdateSettingsRequest{}), service.SettingKeyOpenAIOAuthSchedulingRateMultiplier)
|
||||
}
|
||||
@@ -1809,9 +1809,10 @@ func (h *SettingHandler) UpdateSettings(c *gin.Context) {
|
||||
}
|
||||
return previousSettings.OpenAILowUpstreamRatePriorityEnabled
|
||||
}(),
|
||||
OpenAIOAuthSchedulingRateMultiplier: func() float64 {
|
||||
if req.OpenAIOAuthSchedulingRateMultiplier != nil {
|
||||
return *req.OpenAIOAuthSchedulingRateMultiplier
|
||||
OpenAIOAuthSchedulingRateMultiplier: func() *float64 {
|
||||
// Omitted fields preserve the override; explicit null clears it.
|
||||
if _, sent := sentFields[service.SettingKeyOpenAIOAuthSchedulingRateMultiplier]; sent {
|
||||
return req.OpenAIOAuthSchedulingRateMultiplier
|
||||
}
|
||||
return previousSettings.OpenAIOAuthSchedulingRateMultiplier
|
||||
}(),
|
||||
|
||||
@@ -236,33 +236,33 @@ type SystemSettings struct {
|
||||
PaymentVisibleMethodWxpayEnabled bool `json:"payment_visible_method_wxpay_enabled"`
|
||||
|
||||
// OpenAI account scheduling
|
||||
OpenAILowUpstreamRatePriorityEnabled bool `json:"openai_low_upstream_rate_priority_enabled"`
|
||||
OpenAIOAuthSchedulingRateMultiplier float64 `json:"openai_oauth_scheduling_rate_multiplier"`
|
||||
OpenAIAdvancedSchedulerEnabled bool `json:"openai_advanced_scheduler_enabled"`
|
||||
OpenAIAdvancedSchedulerStickyWeightedEnabled bool `json:"openai_advanced_scheduler_sticky_weighted_enabled"`
|
||||
OpenAIAdvancedSchedulerSubscriptionPriorityEnabled bool `json:"openai_advanced_scheduler_subscription_priority_enabled"`
|
||||
OpenAIAdvancedSchedulerLBTopK string `json:"openai_advanced_scheduler_lb_top_k"`
|
||||
OpenAIAdvancedSchedulerWeightPriority string `json:"openai_advanced_scheduler_weight_priority"`
|
||||
OpenAIAdvancedSchedulerWeightLoad string `json:"openai_advanced_scheduler_weight_load"`
|
||||
OpenAIAdvancedSchedulerWeightQueue string `json:"openai_advanced_scheduler_weight_queue"`
|
||||
OpenAIAdvancedSchedulerWeightErrorRate string `json:"openai_advanced_scheduler_weight_error_rate"`
|
||||
OpenAIAdvancedSchedulerWeightTTFT string `json:"openai_advanced_scheduler_weight_ttft"`
|
||||
OpenAIAdvancedSchedulerWeightReset string `json:"openai_advanced_scheduler_weight_reset"`
|
||||
OpenAIAdvancedSchedulerWeightQuotaHeadroom string `json:"openai_advanced_scheduler_weight_quota_headroom"`
|
||||
OpenAIAdvancedSchedulerWeightUpstreamCost string `json:"openai_advanced_scheduler_weight_upstream_cost"`
|
||||
OpenAIAdvancedSchedulerWeightPreviousResponse string `json:"openai_advanced_scheduler_weight_previous_response"`
|
||||
OpenAIAdvancedSchedulerWeightSessionSticky string `json:"openai_advanced_scheduler_weight_session_sticky"`
|
||||
OpenAIAdvancedSchedulerEffectiveLBTopK string `json:"openai_advanced_scheduler_effective_lb_top_k"`
|
||||
OpenAIAdvancedSchedulerEffectiveWeightPriority string `json:"openai_advanced_scheduler_effective_weight_priority"`
|
||||
OpenAIAdvancedSchedulerEffectiveWeightLoad string `json:"openai_advanced_scheduler_effective_weight_load"`
|
||||
OpenAIAdvancedSchedulerEffectiveWeightQueue string `json:"openai_advanced_scheduler_effective_weight_queue"`
|
||||
OpenAIAdvancedSchedulerEffectiveWeightErrorRate string `json:"openai_advanced_scheduler_effective_weight_error_rate"`
|
||||
OpenAIAdvancedSchedulerEffectiveWeightTTFT string `json:"openai_advanced_scheduler_effective_weight_ttft"`
|
||||
OpenAIAdvancedSchedulerEffectiveWeightReset string `json:"openai_advanced_scheduler_effective_weight_reset"`
|
||||
OpenAIAdvancedSchedulerEffectiveWeightQuotaHeadroom string `json:"openai_advanced_scheduler_effective_weight_quota_headroom"`
|
||||
OpenAIAdvancedSchedulerEffectiveWeightUpstreamCost string `json:"openai_advanced_scheduler_effective_weight_upstream_cost"`
|
||||
OpenAIAdvancedSchedulerEffectiveWeightPreviousResponse string `json:"openai_advanced_scheduler_effective_weight_previous_response"`
|
||||
OpenAIAdvancedSchedulerEffectiveWeightSessionSticky string `json:"openai_advanced_scheduler_effective_weight_session_sticky"`
|
||||
OpenAILowUpstreamRatePriorityEnabled bool `json:"openai_low_upstream_rate_priority_enabled"`
|
||||
OpenAIOAuthSchedulingRateMultiplier *float64 `json:"openai_oauth_scheduling_rate_multiplier"`
|
||||
OpenAIAdvancedSchedulerEnabled bool `json:"openai_advanced_scheduler_enabled"`
|
||||
OpenAIAdvancedSchedulerStickyWeightedEnabled bool `json:"openai_advanced_scheduler_sticky_weighted_enabled"`
|
||||
OpenAIAdvancedSchedulerSubscriptionPriorityEnabled bool `json:"openai_advanced_scheduler_subscription_priority_enabled"`
|
||||
OpenAIAdvancedSchedulerLBTopK string `json:"openai_advanced_scheduler_lb_top_k"`
|
||||
OpenAIAdvancedSchedulerWeightPriority string `json:"openai_advanced_scheduler_weight_priority"`
|
||||
OpenAIAdvancedSchedulerWeightLoad string `json:"openai_advanced_scheduler_weight_load"`
|
||||
OpenAIAdvancedSchedulerWeightQueue string `json:"openai_advanced_scheduler_weight_queue"`
|
||||
OpenAIAdvancedSchedulerWeightErrorRate string `json:"openai_advanced_scheduler_weight_error_rate"`
|
||||
OpenAIAdvancedSchedulerWeightTTFT string `json:"openai_advanced_scheduler_weight_ttft"`
|
||||
OpenAIAdvancedSchedulerWeightReset string `json:"openai_advanced_scheduler_weight_reset"`
|
||||
OpenAIAdvancedSchedulerWeightQuotaHeadroom string `json:"openai_advanced_scheduler_weight_quota_headroom"`
|
||||
OpenAIAdvancedSchedulerWeightUpstreamCost string `json:"openai_advanced_scheduler_weight_upstream_cost"`
|
||||
OpenAIAdvancedSchedulerWeightPreviousResponse string `json:"openai_advanced_scheduler_weight_previous_response"`
|
||||
OpenAIAdvancedSchedulerWeightSessionSticky string `json:"openai_advanced_scheduler_weight_session_sticky"`
|
||||
OpenAIAdvancedSchedulerEffectiveLBTopK string `json:"openai_advanced_scheduler_effective_lb_top_k"`
|
||||
OpenAIAdvancedSchedulerEffectiveWeightPriority string `json:"openai_advanced_scheduler_effective_weight_priority"`
|
||||
OpenAIAdvancedSchedulerEffectiveWeightLoad string `json:"openai_advanced_scheduler_effective_weight_load"`
|
||||
OpenAIAdvancedSchedulerEffectiveWeightQueue string `json:"openai_advanced_scheduler_effective_weight_queue"`
|
||||
OpenAIAdvancedSchedulerEffectiveWeightErrorRate string `json:"openai_advanced_scheduler_effective_weight_error_rate"`
|
||||
OpenAIAdvancedSchedulerEffectiveWeightTTFT string `json:"openai_advanced_scheduler_effective_weight_ttft"`
|
||||
OpenAIAdvancedSchedulerEffectiveWeightReset string `json:"openai_advanced_scheduler_effective_weight_reset"`
|
||||
OpenAIAdvancedSchedulerEffectiveWeightQuotaHeadroom string `json:"openai_advanced_scheduler_effective_weight_quota_headroom"`
|
||||
OpenAIAdvancedSchedulerEffectiveWeightUpstreamCost string `json:"openai_advanced_scheduler_effective_weight_upstream_cost"`
|
||||
OpenAIAdvancedSchedulerEffectiveWeightPreviousResponse string `json:"openai_advanced_scheduler_effective_weight_previous_response"`
|
||||
OpenAIAdvancedSchedulerEffectiveWeightSessionSticky string `json:"openai_advanced_scheduler_effective_weight_session_sticky"`
|
||||
|
||||
// Payment configuration
|
||||
PaymentEnabled bool `json:"payment_enabled"`
|
||||
|
||||
@@ -45,7 +45,7 @@ const (
|
||||
|
||||
type cachedOpenAIAdvancedSchedulerSetting struct {
|
||||
lowUpstreamRatePriorityEnabled bool
|
||||
oauthSchedulingRateMultiplier float64
|
||||
oauthSchedulingRateMultiplier *float64
|
||||
enabled bool
|
||||
stickyWeightedEnabled bool
|
||||
subscriptionPriorityEnabled bool
|
||||
@@ -56,7 +56,7 @@ type cachedOpenAIAdvancedSchedulerSetting struct {
|
||||
|
||||
type openAIAdvancedSchedulerRuntimeSettings struct {
|
||||
lowUpstreamRatePriorityEnabled bool
|
||||
oauthSchedulingRateMultiplier float64
|
||||
oauthSchedulingRateMultiplier *float64
|
||||
enabled bool
|
||||
stickyWeightedEnabled bool
|
||||
subscriptionPriorityEnabled bool
|
||||
@@ -1904,7 +1904,7 @@ func (s *OpenAIGatewayService) openAIAdvancedSchedulerRuntimeSettings(ctx contex
|
||||
}
|
||||
|
||||
lowUpstreamRatePriorityEnabled := false
|
||||
oauthSchedulingRateMultiplier := defaultOpenAIOAuthSchedulingRateMultiplier
|
||||
oauthSchedulingRateMultiplier := parseOpenAIOAuthSchedulingRateMultiplier(nil)
|
||||
enabled := false
|
||||
stickyWeightedEnabled := false
|
||||
subscriptionPriorityEnabled := false
|
||||
@@ -1916,7 +1916,7 @@ func (s *OpenAIGatewayService) openAIAdvancedSchedulerRuntimeSettings(ctx contex
|
||||
|
||||
if values, err := repo.GetMultiple(dbCtx, openAIAdvancedSchedulerRuntimeSettingKeys()); err == nil {
|
||||
lowUpstreamRatePriorityEnabled = strings.EqualFold(strings.TrimSpace(values[SettingKeyOpenAILowUpstreamRatePriorityEnabled]), "true")
|
||||
oauthSchedulingRateMultiplier = parseOpenAIOAuthSchedulingRateMultiplier(values[SettingKeyOpenAIOAuthSchedulingRateMultiplier])
|
||||
oauthSchedulingRateMultiplier = parseOpenAIOAuthSchedulingRateMultiplier(values)
|
||||
enabled = strings.EqualFold(strings.TrimSpace(values[openAIAdvancedSchedulerSettingKey]), "true")
|
||||
stickyWeightedEnabled = strings.EqualFold(strings.TrimSpace(values[SettingKeyOpenAIAdvancedSchedulerStickyWeightedEnabled]), "true")
|
||||
subscriptionPriorityEnabled = strings.EqualFold(strings.TrimSpace(values[SettingKeyOpenAIAdvancedSchedulerSubscriptionPriorityEnabled]), "true")
|
||||
@@ -1933,7 +1933,7 @@ func (s *OpenAIGatewayService) openAIAdvancedSchedulerRuntimeSettings(ctx contex
|
||||
}
|
||||
}
|
||||
lowUpstreamRatePriorityEnabled = strings.EqualFold(strings.TrimSpace(fallbackValues[SettingKeyOpenAILowUpstreamRatePriorityEnabled]), "true")
|
||||
oauthSchedulingRateMultiplier = parseOpenAIOAuthSchedulingRateMultiplier(fallbackValues[SettingKeyOpenAIOAuthSchedulingRateMultiplier])
|
||||
oauthSchedulingRateMultiplier = parseOpenAIOAuthSchedulingRateMultiplier(fallbackValues)
|
||||
enabled = strings.EqualFold(strings.TrimSpace(fallbackValues[openAIAdvancedSchedulerSettingKey]), "true")
|
||||
stickyWeightedEnabled = strings.EqualFold(strings.TrimSpace(fallbackValues[SettingKeyOpenAIAdvancedSchedulerStickyWeightedEnabled]), "true")
|
||||
subscriptionPriorityEnabled = strings.EqualFold(strings.TrimSpace(fallbackValues[SettingKeyOpenAIAdvancedSchedulerSubscriptionPriorityEnabled]), "true")
|
||||
@@ -1976,7 +1976,7 @@ func (s *OpenAIGatewayService) isOpenAILowUpstreamRatePriorityEnabled(ctx contex
|
||||
return !settings.enabled && settings.lowUpstreamRatePriorityEnabled
|
||||
}
|
||||
|
||||
func (s *OpenAIGatewayService) openAIOAuthSchedulingRateMultiplier(ctx context.Context) float64 {
|
||||
func (s *OpenAIGatewayService) openAIOAuthSchedulingRateMultiplier(ctx context.Context) *float64 {
|
||||
return s.openAIAdvancedSchedulerRuntimeSettings(ctx).oauthSchedulingRateMultiplier
|
||||
}
|
||||
|
||||
@@ -2664,7 +2664,7 @@ func BuildOpenAIAccountSchedulerScoreSnapshot(
|
||||
loadMap map[int64]*AccountLoadInfo,
|
||||
) map[int64]OpenAIAccountSchedulerScoreSnapshot {
|
||||
gateway := &OpenAIGatewayService{}
|
||||
return buildOpenAIAccountSchedulerScoreSnapshot(accounts, loadMap, gateway.openAIWSSchedulerWeights(), false, defaultOpenAIOAuthSchedulingRateMultiplier)
|
||||
return buildOpenAIAccountSchedulerScoreSnapshot(accounts, loadMap, gateway.openAIWSSchedulerWeights(), false, parseOpenAIOAuthSchedulingRateMultiplier(nil))
|
||||
}
|
||||
|
||||
func buildOpenAIAccountSchedulerScoreSnapshot(
|
||||
@@ -2672,7 +2672,7 @@ func buildOpenAIAccountSchedulerScoreSnapshot(
|
||||
loadMap map[int64]*AccountLoadInfo,
|
||||
weights GatewayOpenAIWSSchedulerScoreWeightsView,
|
||||
stickyWeightedEnabled bool,
|
||||
oauthSchedulingRateMultiplier float64,
|
||||
oauthSchedulingRateMultiplier *float64,
|
||||
) map[int64]OpenAIAccountSchedulerScoreSnapshot {
|
||||
if len(accounts) == 0 {
|
||||
return nil
|
||||
@@ -2795,7 +2795,7 @@ func buildOpenAIAccountSchedulerScoreSnapshot(
|
||||
return result
|
||||
}
|
||||
|
||||
func openAIUpstreamCostFactors(accounts []*Account, now time.Time, oauthSchedulingRateMultiplier float64) map[int64]float64 {
|
||||
func openAIUpstreamCostFactors(accounts []*Account, now time.Time, oauthSchedulingRateMultiplier *float64) map[int64]float64 {
|
||||
type rateSample struct {
|
||||
accountID int64
|
||||
rate float64
|
||||
@@ -2862,7 +2862,7 @@ type openAILegacyUpstreamRateOrder struct {
|
||||
rates map[int64]float64
|
||||
}
|
||||
|
||||
func newOpenAILegacyUpstreamRateOrder(accounts []*Account, now time.Time, oauthSchedulingRateMultiplier float64) openAILegacyUpstreamRateOrder {
|
||||
func newOpenAILegacyUpstreamRateOrder(accounts []*Account, now time.Time, oauthSchedulingRateMultiplier *float64) openAILegacyUpstreamRateOrder {
|
||||
rates := make(map[int64]float64, len(accounts))
|
||||
var first float64
|
||||
distinct := false
|
||||
@@ -2890,11 +2890,19 @@ func newOpenAILegacyUpstreamRateOrder(accounts []*Account, now time.Time, oauthS
|
||||
return openAILegacyUpstreamRateOrder{enabled: len(rates) >= 2 && distinct, rates: rates}
|
||||
}
|
||||
|
||||
func openAISchedulingRate(account *Account, now time.Time, oauthSchedulingRateMultiplier float64) (float64, bool) {
|
||||
if account != nil && account.IsOpenAIOAuthLike() {
|
||||
return oauthSchedulingRateMultiplier, true
|
||||
func openAISchedulingRate(account *Account, now time.Time, oauthSchedulingRateMultiplier *float64) (float64, bool) {
|
||||
if account == nil || (!account.IsOpenAIApiKey() && !account.IsOpenAIOAuthLike()) {
|
||||
return 0, false
|
||||
}
|
||||
return openAIFreshUpstreamBillingRate(account, now)
|
||||
if account.IsOpenAIOAuthLike() {
|
||||
if rate := oauthSchedulingRateMultiplier; rate != nil && *rate >= 0 && !math.IsNaN(*rate) && !math.IsInf(*rate, 0) {
|
||||
return *rate, true
|
||||
}
|
||||
} else if rate, ok := openAIFreshUpstreamBillingRate(account, now); ok {
|
||||
return rate, true
|
||||
}
|
||||
rate := account.BillingRateMultiplier()
|
||||
return rate, !math.IsNaN(rate) && !math.IsInf(rate, 0)
|
||||
}
|
||||
|
||||
// compare returns -1 when a should be selected before b, 1 when b should be
|
||||
|
||||
@@ -3,7 +3,6 @@ package service
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"math"
|
||||
"strconv"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -402,16 +401,17 @@ func TestOpenAIFreshUpstreamBillingRateRecomputesPeakAtSelectionTime(t *testing.
|
||||
"timezone": "UTC",
|
||||
}
|
||||
|
||||
duringPeak, ok := openAIFreshUpstreamBillingRate(account, time.Date(2026, 7, 13, 17, 59, 0, 0, time.UTC))
|
||||
account.RateMultiplier = floatPtr(0.1)
|
||||
duringPeak, ok := openAISchedulingRate(account, time.Date(2026, 7, 13, 17, 59, 0, 0, time.UTC), nil)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, 0.8, duringPeak)
|
||||
|
||||
afterPeak, ok := openAIFreshUpstreamBillingRate(account, time.Date(2026, 7, 13, 18, 1, 0, 0, time.UTC))
|
||||
afterPeak, ok := openAISchedulingRate(account, time.Date(2026, 7, 13, 18, 1, 0, 0, time.UTC), nil)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, 0.4, afterPeak)
|
||||
}
|
||||
|
||||
func TestOpenAIUpstreamCostFactorsSparseProbeIsNeutral(t *testing.T) {
|
||||
func TestOpenAIUpstreamCostFactorsEqualProbeAndFallbackRatesAreNeutral(t *testing.T) {
|
||||
now := time.Date(2026, 7, 13, 12, 0, 0, 0, time.UTC)
|
||||
accounts := make([]*Account, 0, 10)
|
||||
accounts = append(accounts, upstreamCostTestAccount(1, UpstreamBillingProbeStatusOK, 1, now.Add(-time.Minute), 30*time.Minute))
|
||||
@@ -430,13 +430,13 @@ func TestOpenAIUpstreamCostFactorsSparseProbeIsNeutral(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
factors := openAIUpstreamCostFactors(accounts, now, defaultOpenAIOAuthSchedulingRateMultiplier)
|
||||
factors := openAIUpstreamCostFactors(accounts, now, floatPtr(defaultOpenAIOAuthSchedulingRateMultiplier))
|
||||
for id := int64(1); id <= 10; id++ {
|
||||
require.Equal(t, openAIUpstreamCostNeutralFactor, factors[id])
|
||||
}
|
||||
}
|
||||
|
||||
func TestOpenAIUpstreamCostFactorsCoverageShrinksSparseSignal(t *testing.T) {
|
||||
func TestOpenAIUpstreamCostFactorsIncludeAccountFallbacksInCoverage(t *testing.T) {
|
||||
now := time.Date(2026, 7, 13, 12, 0, 0, 0, time.UTC)
|
||||
accounts := []*Account{
|
||||
upstreamCostTestAccount(1, UpstreamBillingProbeStatusOK, 0.03, now.Add(-time.Minute), 30*time.Minute),
|
||||
@@ -446,10 +446,10 @@ func TestOpenAIUpstreamCostFactorsCoverageShrinksSparseSignal(t *testing.T) {
|
||||
accounts = append(accounts, &Account{ID: id, Platform: PlatformOpenAI, Type: AccountTypeAPIKey})
|
||||
}
|
||||
|
||||
factors := openAIUpstreamCostFactors(accounts, now, defaultOpenAIOAuthSchedulingRateMultiplier)
|
||||
center := math.Sqrt(0.03 * 0.8)
|
||||
require.InDelta(t, 0.5+0.2*(1/(1+0.03/center)-0.5), factors[1], 1e-12)
|
||||
require.InDelta(t, 0.5+0.2*(1/(1+0.8/center)-0.5), factors[2], 1e-12)
|
||||
factors := openAIUpstreamCostFactors(accounts, now, floatPtr(defaultOpenAIOAuthSchedulingRateMultiplier))
|
||||
// Eight unprobed accounts default to 1x, so the median is 1 and coverage is complete.
|
||||
require.InDelta(t, 1/(1+0.03), factors[1], 1e-12)
|
||||
require.InDelta(t, 1/(1+0.8), factors[2], 1e-12)
|
||||
require.Equal(t, openAIUpstreamCostNeutralFactor, factors[3])
|
||||
}
|
||||
|
||||
@@ -461,7 +461,7 @@ func TestOpenAIUpstreamCostFactorsUseMedianAgainstOutlier(t *testing.T) {
|
||||
upstreamCostTestAccount(3, UpstreamBillingProbeStatusOK, 100, now.Add(-time.Minute), 30*time.Minute),
|
||||
}
|
||||
|
||||
factors := openAIUpstreamCostFactors(accounts, now, defaultOpenAIOAuthSchedulingRateMultiplier)
|
||||
factors := openAIUpstreamCostFactors(accounts, now, floatPtr(defaultOpenAIOAuthSchedulingRateMultiplier))
|
||||
require.InDelta(t, 2.0/3.0, factors[1], 1e-12)
|
||||
require.InDelta(t, 0.5, factors[2], 1e-12)
|
||||
require.InDelta(t, 1/(1+100/0.2), factors[3], 1e-12)
|
||||
@@ -472,20 +472,21 @@ func TestOpenAILegacyUpstreamRateOrderRequiresComparableRates(t *testing.T) {
|
||||
oneKnown := newOpenAILegacyUpstreamRateOrder([]*Account{
|
||||
upstreamCostTestAccount(1, UpstreamBillingProbeStatusOK, 0.03, now.Add(-time.Minute), 30*time.Minute),
|
||||
{ID: 2, Platform: PlatformOpenAI, Type: AccountTypeAPIKey},
|
||||
}, now, defaultOpenAIOAuthSchedulingRateMultiplier)
|
||||
require.False(t, oneKnown.enabled)
|
||||
}, now, floatPtr(defaultOpenAIOAuthSchedulingRateMultiplier))
|
||||
require.True(t, oneKnown.enabled)
|
||||
require.Equal(t, 1.0, oneKnown.rates[2])
|
||||
|
||||
allEqual := newOpenAILegacyUpstreamRateOrder([]*Account{
|
||||
upstreamCostTestAccount(1, UpstreamBillingProbeStatusOK, 0.3, now.Add(-time.Minute), 30*time.Minute),
|
||||
upstreamCostTestAccount(2, UpstreamBillingProbeStatusOK, 0.3, now.Add(-time.Minute), 30*time.Minute),
|
||||
}, now, defaultOpenAIOAuthSchedulingRateMultiplier)
|
||||
}, now, floatPtr(defaultOpenAIOAuthSchedulingRateMultiplier))
|
||||
require.False(t, allEqual.enabled)
|
||||
|
||||
distinct := newOpenAILegacyUpstreamRateOrder([]*Account{
|
||||
upstreamCostTestAccount(1, UpstreamBillingProbeStatusOK, 0.03, now.Add(-time.Minute), 30*time.Minute),
|
||||
upstreamCostTestAccount(2, UpstreamBillingProbeStatusOK, 0.8, now.Add(-time.Minute), 30*time.Minute),
|
||||
{ID: 3, Platform: PlatformOpenAI, Type: AccountTypeAPIKey},
|
||||
}, now, defaultOpenAIOAuthSchedulingRateMultiplier)
|
||||
}, now, floatPtr(defaultOpenAIOAuthSchedulingRateMultiplier))
|
||||
require.True(t, distinct.enabled)
|
||||
require.Negative(t, distinct.compare(&Account{ID: 1}, &Account{ID: 2}))
|
||||
require.Negative(t, distinct.compare(&Account{ID: 2}, &Account{ID: 3}))
|
||||
@@ -506,12 +507,12 @@ func TestOpenAILegacyUpstreamRateOrderIgnoresNonOpenAIPlatforms(t *testing.T) {
|
||||
grokCheap := nonOpenAI(1, PlatformGrok, 0.01)
|
||||
anthropicExpensive := nonOpenAI(2, PlatformAnthropic, 0.9)
|
||||
|
||||
order := newOpenAILegacyUpstreamRateOrder([]*Account{grokCheap, anthropicExpensive, nil}, now, defaultOpenAIOAuthSchedulingRateMultiplier)
|
||||
order := newOpenAILegacyUpstreamRateOrder([]*Account{grokCheap, anthropicExpensive, nil}, now, floatPtr(defaultOpenAIOAuthSchedulingRateMultiplier))
|
||||
require.False(t, order.enabled)
|
||||
require.Empty(t, order.rates)
|
||||
require.Zero(t, order.compare(grokCheap, anthropicExpensive))
|
||||
|
||||
factors := openAIUpstreamCostFactors([]*Account{grokCheap, anthropicExpensive}, now, defaultOpenAIOAuthSchedulingRateMultiplier)
|
||||
factors := openAIUpstreamCostFactors([]*Account{grokCheap, anthropicExpensive}, now, floatPtr(defaultOpenAIOAuthSchedulingRateMultiplier))
|
||||
require.Equal(t, openAIUpstreamCostNeutralFactor, factors[grokCheap.ID])
|
||||
require.Equal(t, openAIUpstreamCostNeutralFactor, factors[anthropicExpensive.ID])
|
||||
|
||||
@@ -520,7 +521,7 @@ func TestOpenAILegacyUpstreamRateOrderIgnoresNonOpenAIPlatforms(t *testing.T) {
|
||||
openAIExpensive := upstreamCostTestAccount(4, UpstreamBillingProbeStatusOK, 0.12, now.Add(-time.Minute), 30*time.Minute)
|
||||
mixed := newOpenAILegacyUpstreamRateOrder(
|
||||
[]*Account{grokCheap, openAICheap, anthropicExpensive, openAIExpensive},
|
||||
now, defaultOpenAIOAuthSchedulingRateMultiplier,
|
||||
now, floatPtr(defaultOpenAIOAuthSchedulingRateMultiplier),
|
||||
)
|
||||
require.True(t, mixed.enabled)
|
||||
require.Len(t, mixed.rates, 2)
|
||||
@@ -537,12 +538,12 @@ func TestOpenAISchedulingRatePlacesOAuthAtConfiguredReference(t *testing.T) {
|
||||
oauth := upstreamCostTestOAuthAccount(2)
|
||||
expensive := upstreamCostTestAccount(3, UpstreamBillingProbeStatusOK, 0.12, now.Add(-time.Minute), 30*time.Minute)
|
||||
|
||||
order := newOpenAILegacyUpstreamRateOrder([]*Account{cheap, oauth, expensive}, now, 0.05)
|
||||
order := newOpenAILegacyUpstreamRateOrder([]*Account{cheap, oauth, expensive}, now, floatPtr(0.05))
|
||||
require.True(t, order.enabled)
|
||||
require.Negative(t, order.compare(cheap, oauth))
|
||||
require.Negative(t, order.compare(oauth, expensive))
|
||||
|
||||
factors := openAIUpstreamCostFactors([]*Account{cheap, oauth, expensive}, now, 0.05)
|
||||
factors := openAIUpstreamCostFactors([]*Account{cheap, oauth, expensive}, now, floatPtr(0.05))
|
||||
require.Greater(t, factors[cheap.ID], factors[oauth.ID])
|
||||
require.Greater(t, factors[oauth.ID], factors[expensive.ID])
|
||||
}
|
||||
@@ -834,9 +835,9 @@ func TestBuildOpenAIAccountSchedulerScoreSnapshotUpstreamCostIsExactNoOpWithoutS
|
||||
2: {AccountID: 2, LoadRate: 80},
|
||||
}
|
||||
weights := GatewayOpenAIWSSchedulerScoreWeightsView{Priority: 1, Load: 1, Queue: 0.7, ErrorRate: 0.8, TTFT: 0.5}
|
||||
baseline := buildOpenAIAccountSchedulerScoreSnapshot(accounts, loadMap, weights, false, defaultOpenAIOAuthSchedulingRateMultiplier)
|
||||
baseline := buildOpenAIAccountSchedulerScoreSnapshot(accounts, loadMap, weights, false, floatPtr(defaultOpenAIOAuthSchedulingRateMultiplier))
|
||||
weights.UpstreamCost = 1.5
|
||||
withCost := buildOpenAIAccountSchedulerScoreSnapshot(accounts, loadMap, weights, false, defaultOpenAIOAuthSchedulingRateMultiplier)
|
||||
withCost := buildOpenAIAccountSchedulerScoreSnapshot(accounts, loadMap, weights, false, floatPtr(defaultOpenAIOAuthSchedulingRateMultiplier))
|
||||
|
||||
require.Equal(t, baseline, withCost)
|
||||
}
|
||||
@@ -848,7 +849,7 @@ func TestBuildOpenAIAccountSchedulerScoreSnapshotUsesUpstreamCostSignal(t *testi
|
||||
upstreamCostTestAccount(2, UpstreamBillingProbeStatusOK, 0.8, now.Add(-time.Minute), 30*time.Minute),
|
||||
}
|
||||
weights := GatewayOpenAIWSSchedulerScoreWeightsView{UpstreamCost: 1.5}
|
||||
scores := buildOpenAIAccountSchedulerScoreSnapshot(accounts, nil, weights, false, defaultOpenAIOAuthSchedulingRateMultiplier)
|
||||
scores := buildOpenAIAccountSchedulerScoreSnapshot(accounts, nil, weights, false, floatPtr(defaultOpenAIOAuthSchedulingRateMultiplier))
|
||||
|
||||
require.Greater(t, scores[1].BaseScore, scores[2].BaseScore)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestOpenAISchedulingRateFallback(t *testing.T) {
|
||||
now := time.Now()
|
||||
for _, tt := range []struct {
|
||||
name string
|
||||
status string
|
||||
age time.Duration
|
||||
probe float64
|
||||
want float64
|
||||
}{
|
||||
{"fresh snapshot wins", UpstreamBillingProbeStatusOK, time.Minute, 0.6, 0.6},
|
||||
{"fresh snapshot survives failed refresh", UpstreamBillingProbeStatusFailed, time.Minute, 0.6, 0.6},
|
||||
{"expired snapshot falls back", UpstreamBillingProbeStatusOK, 3 * time.Hour, 0.6, 0.3},
|
||||
{"future snapshot falls back", UpstreamBillingProbeStatusOK, -time.Hour, 0.6, 0.3},
|
||||
{"invalid snapshot falls back", UpstreamBillingProbeStatusOK, time.Minute, -1, 0.3},
|
||||
{"zero snapshot remains valid", UpstreamBillingProbeStatusOK, time.Minute, 0, 0},
|
||||
} {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
account := upstreamCostTestAccount(1, tt.status, tt.probe, now.Add(-tt.age), time.Hour)
|
||||
account.RateMultiplier = floatPtr(0.3)
|
||||
rate, ok := openAISchedulingRate(account, now, floatPtr(0.9))
|
||||
require.True(t, ok)
|
||||
require.Equal(t, tt.want, rate)
|
||||
})
|
||||
}
|
||||
|
||||
for _, accountType := range []string{AccountTypeAPIKey, AccountTypeOAuth} {
|
||||
t.Run(accountType+" without probe or override", func(t *testing.T) {
|
||||
account := &Account{ID: 1, Platform: PlatformOpenAI, Type: accountType}
|
||||
for _, tt := range []struct {
|
||||
configured *float64
|
||||
want float64
|
||||
known bool
|
||||
}{
|
||||
{floatPtr(0.3), 0.3, true},
|
||||
{floatPtr(0), 0, true},
|
||||
{nil, 1, true},
|
||||
{floatPtr(-1), 1, true},
|
||||
{floatPtr(math.NaN()), 0, false},
|
||||
{floatPtr(math.Inf(1)), 0, false},
|
||||
} {
|
||||
account.RateMultiplier = tt.configured
|
||||
rate, ok := openAISchedulingRate(account, now, nil)
|
||||
require.Equal(t, tt.known, ok)
|
||||
if ok {
|
||||
require.Equal(t, tt.want, rate)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
oauth := upstreamCostTestOAuthAccount(1)
|
||||
oauth.RateMultiplier = floatPtr(0.3)
|
||||
for _, override := range []float64{0, 0.7} {
|
||||
rate, ok := openAISchedulingRate(oauth, now, &override)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, override, rate)
|
||||
}
|
||||
for _, override := range []float64{-1, math.NaN(), math.Inf(1)} {
|
||||
rate, ok := openAISchedulingRate(oauth, now, &override)
|
||||
require.True(t, ok)
|
||||
require.Equal(t, 0.3, rate)
|
||||
}
|
||||
_, ok := openAISchedulingRate(nil, now, nil)
|
||||
require.False(t, ok)
|
||||
}
|
||||
|
||||
func TestOpenAISchedulingRateFallbackSharedByBothModes(t *testing.T) {
|
||||
now := time.Now()
|
||||
for _, accountType := range []string{AccountTypeAPIKey, AccountTypeOAuth} {
|
||||
t.Run(accountType, func(t *testing.T) {
|
||||
cheap := &Account{ID: 1, Platform: PlatformOpenAI, Type: accountType, RateMultiplier: floatPtr(0.3)}
|
||||
expensive := &Account{ID: 2, Platform: PlatformOpenAI, Type: accountType, RateMultiplier: floatPtr(0.8)}
|
||||
accounts := []*Account{cheap, expensive}
|
||||
order := newOpenAILegacyUpstreamRateOrder(accounts, now, nil)
|
||||
require.True(t, order.enabled)
|
||||
require.Negative(t, order.compare(cheap, expensive))
|
||||
factors := openAIUpstreamCostFactors(accounts, now, nil)
|
||||
require.Greater(t, factors[cheap.ID], factors[expensive.ID])
|
||||
if accountType == AccountTypeOAuth {
|
||||
order = newOpenAILegacyUpstreamRateOrder(accounts, now, floatPtr(0.7))
|
||||
require.False(t, order.enabled)
|
||||
factors = openAIUpstreamCostFactors(accounts, now, floatPtr(0.7))
|
||||
require.Equal(t, factors[cheap.ID], factors[expensive.ID])
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestOpenAIOAuthSchedulingRateRuntimePreservesExplicitClear(t *testing.T) {
|
||||
resetOpenAIAdvancedSchedulerSettingCacheForTest()
|
||||
defer resetOpenAIAdvancedSchedulerSettingCacheForTest()
|
||||
for _, tt := range []struct {
|
||||
name string
|
||||
values map[string]string
|
||||
want *float64
|
||||
}{
|
||||
{"absent", map[string]string{}, floatPtr(1)},
|
||||
{"cleared", map[string]string{SettingKeyOpenAIOAuthSchedulingRateMultiplier: ""}, nil},
|
||||
{"zero", map[string]string{SettingKeyOpenAIOAuthSchedulingRateMultiplier: "0"}, floatPtr(0)},
|
||||
{"override", map[string]string{SettingKeyOpenAIOAuthSchedulingRateMultiplier: "0.7"}, floatPtr(0.7)},
|
||||
} {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
resetOpenAIAdvancedSchedulerSettingCacheForTest()
|
||||
svc := &OpenAIGatewayService{
|
||||
rateLimitService: &RateLimitService{settingService: &SettingService{
|
||||
settingRepo: &openAIAdvancedSchedulerSettingRepoStub{values: tt.values},
|
||||
}},
|
||||
}
|
||||
for i := 0; i < 2; i++ {
|
||||
require.Equal(t, tt.want, svc.openAIOAuthSchedulingRateMultiplier(context.Background()))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -915,7 +915,7 @@ func (s *SettingService) parseSettings(settings map[string]string) *SystemSettin
|
||||
result.PaymentVisibleMethodAlipayEnabled = settings[SettingPaymentVisibleMethodAlipayEnabled] == "true"
|
||||
result.PaymentVisibleMethodWxpayEnabled = settings[SettingPaymentVisibleMethodWxpayEnabled] == "true"
|
||||
result.OpenAILowUpstreamRatePriorityEnabled = settings[SettingKeyOpenAILowUpstreamRatePriorityEnabled] == "true"
|
||||
result.OpenAIOAuthSchedulingRateMultiplier = parseOpenAIOAuthSchedulingRateMultiplier(settings[SettingKeyOpenAIOAuthSchedulingRateMultiplier])
|
||||
result.OpenAIOAuthSchedulingRateMultiplier = parseOpenAIOAuthSchedulingRateMultiplier(settings)
|
||||
result.OpenAIAdvancedSchedulerEnabled = settings[openAIAdvancedSchedulerSettingKey] == "true"
|
||||
result.OpenAIAdvancedSchedulerStickyWeightedEnabled = settings[SettingKeyOpenAIAdvancedSchedulerStickyWeightedEnabled] == "true"
|
||||
result.OpenAIAdvancedSchedulerSubscriptionPriorityEnabled = settings[SettingKeyOpenAIAdvancedSchedulerSubscriptionPriorityEnabled] == "true"
|
||||
@@ -1080,7 +1080,7 @@ func formatOpenAIAdvancedSchedulerFloat(value float64) string {
|
||||
}
|
||||
|
||||
func (s *SettingService) normalizeOpenAIAdvancedSchedulerOverrides(settings *SystemSettings) error {
|
||||
if rate := settings.OpenAIOAuthSchedulingRateMultiplier; rate < 0 || math.IsNaN(rate) || math.IsInf(rate, 0) {
|
||||
if rate := settings.OpenAIOAuthSchedulingRateMultiplier; rate != nil && (*rate < 0 || math.IsNaN(*rate) || math.IsInf(*rate, 0)) {
|
||||
return infraerrors.BadRequest("INVALID_OPENAI_OAUTH_SCHEDULING_RATE_MULTIPLIER", "OpenAI OAuth scheduling rate multiplier must be a finite non-negative number")
|
||||
}
|
||||
|
||||
@@ -1132,12 +1132,18 @@ func (s *SettingService) normalizeOpenAIAdvancedSchedulerOverrides(settings *Sys
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseOpenAIOAuthSchedulingRateMultiplier(raw string) float64 {
|
||||
func parseOpenAIOAuthSchedulingRateMultiplier(settings map[string]string) *float64 {
|
||||
raw, exists := settings[SettingKeyOpenAIOAuthSchedulingRateMultiplier]
|
||||
if !exists {
|
||||
// Preserve the legacy default until an administrator explicitly clears it.
|
||||
value := defaultOpenAIOAuthSchedulingRateMultiplier
|
||||
return &value
|
||||
}
|
||||
value, err := strconv.ParseFloat(strings.TrimSpace(raw), 64)
|
||||
if err != nil || value < 0 || math.IsNaN(value) || math.IsInf(value, 0) {
|
||||
return defaultOpenAIOAuthSchedulingRateMultiplier
|
||||
return nil
|
||||
}
|
||||
return value
|
||||
return &value
|
||||
}
|
||||
|
||||
// resolveOpenAIAdvancedSchedulerWeight 返回覆盖值(已归一化的非空字符串),空则回退默认值。
|
||||
|
||||
@@ -410,7 +410,7 @@ func TestSettingService_UpdateSettings_PaymentVisibleMethodsAndAdvancedScheduler
|
||||
PaymentVisibleMethodAlipayEnabled: true,
|
||||
PaymentVisibleMethodWxpayEnabled: false,
|
||||
OpenAILowUpstreamRatePriorityEnabled: true,
|
||||
OpenAIOAuthSchedulingRateMultiplier: 0.05,
|
||||
OpenAIOAuthSchedulingRateMultiplier: testPtrFloat64(0.05),
|
||||
OpenAIAdvancedSchedulerEnabled: true,
|
||||
OpenAIAdvancedSchedulerStickyWeightedEnabled: true,
|
||||
OpenAIAdvancedSchedulerSubscriptionPriorityEnabled: true,
|
||||
@@ -454,7 +454,7 @@ func TestSettingService_UpdateSettingsRejectsInvalidOpenAIOAuthSchedulingRateMul
|
||||
svc := NewSettingService(repo, &config.Config{})
|
||||
|
||||
for _, rate := range []float64{-0.01, math.NaN(), math.Inf(1)} {
|
||||
err := svc.UpdateSettings(context.Background(), &SystemSettings{OpenAIOAuthSchedulingRateMultiplier: rate})
|
||||
err := svc.UpdateSettings(context.Background(), &SystemSettings{OpenAIOAuthSchedulingRateMultiplier: &rate})
|
||||
require.Error(t, err)
|
||||
}
|
||||
}
|
||||
@@ -515,8 +515,12 @@ func TestSettingService_UpdateSettings_OpenAIAdvancedSchedulerWeightSums(t *test
|
||||
func TestSettingService_ParseSettingsDefaultsOpenAIOAuthSchedulingRateMultiplier(t *testing.T) {
|
||||
svc := NewSettingService(&settingUpdateRepoStub{}, &config.Config{})
|
||||
|
||||
require.Equal(t, 1.0, svc.parseSettings(map[string]string{}).OpenAIOAuthSchedulingRateMultiplier)
|
||||
require.Equal(t, 0.05, svc.parseSettings(map[string]string{SettingKeyOpenAIOAuthSchedulingRateMultiplier: "0.05"}).OpenAIOAuthSchedulingRateMultiplier)
|
||||
require.Equal(t, testPtrFloat64(1.0), svc.parseSettings(map[string]string{}).OpenAIOAuthSchedulingRateMultiplier)
|
||||
require.Equal(t, testPtrFloat64(0.05), svc.parseSettings(map[string]string{SettingKeyOpenAIOAuthSchedulingRateMultiplier: "0.05"}).OpenAIOAuthSchedulingRateMultiplier)
|
||||
require.Equal(t, testPtrFloat64(0), svc.parseSettings(map[string]string{SettingKeyOpenAIOAuthSchedulingRateMultiplier: "0"}).OpenAIOAuthSchedulingRateMultiplier)
|
||||
for _, raw := range []string{"", " ", "invalid", "-1", "NaN", "+Inf"} {
|
||||
require.Nil(t, svc.parseSettings(map[string]string{SettingKeyOpenAIOAuthSchedulingRateMultiplier: raw}).OpenAIOAuthSchedulingRateMultiplier, raw)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSettingService_GetAllSettings_OpenAIAdvancedSchedulerEffectiveValuesUseConfig(t *testing.T) {
|
||||
|
||||
@@ -499,7 +499,10 @@ func (s *SettingService) buildSystemSettingsUpdates(ctx context.Context, setting
|
||||
updates[SettingPaymentVisibleMethodAlipayEnabled] = strconv.FormatBool(settings.PaymentVisibleMethodAlipayEnabled)
|
||||
updates[SettingPaymentVisibleMethodWxpayEnabled] = strconv.FormatBool(settings.PaymentVisibleMethodWxpayEnabled)
|
||||
updates[SettingKeyOpenAILowUpstreamRatePriorityEnabled] = strconv.FormatBool(settings.OpenAILowUpstreamRatePriorityEnabled)
|
||||
updates[SettingKeyOpenAIOAuthSchedulingRateMultiplier] = strconv.FormatFloat(settings.OpenAIOAuthSchedulingRateMultiplier, 'f', -1, 64)
|
||||
updates[SettingKeyOpenAIOAuthSchedulingRateMultiplier] = ""
|
||||
if rate := settings.OpenAIOAuthSchedulingRateMultiplier; rate != nil {
|
||||
updates[SettingKeyOpenAIOAuthSchedulingRateMultiplier] = strconv.FormatFloat(*rate, 'f', -1, 64)
|
||||
}
|
||||
updates[openAIAdvancedSchedulerSettingKey] = strconv.FormatBool(settings.OpenAIAdvancedSchedulerEnabled)
|
||||
updates[SettingKeyOpenAIAdvancedSchedulerStickyWeightedEnabled] = strconv.FormatBool(settings.OpenAIAdvancedSchedulerStickyWeightedEnabled)
|
||||
updates[SettingKeyOpenAIAdvancedSchedulerSubscriptionPriorityEnabled] = strconv.FormatBool(settings.OpenAIAdvancedSchedulerSubscriptionPriorityEnabled)
|
||||
|
||||
@@ -268,7 +268,7 @@ type SystemSettings struct {
|
||||
|
||||
// OpenAI 账号调度
|
||||
OpenAILowUpstreamRatePriorityEnabled bool
|
||||
OpenAIOAuthSchedulingRateMultiplier float64
|
||||
OpenAIOAuthSchedulingRateMultiplier *float64
|
||||
OpenAIAdvancedSchedulerEnabled bool
|
||||
OpenAIAdvancedSchedulerStickyWeightedEnabled bool
|
||||
OpenAIAdvancedSchedulerSubscriptionPriorityEnabled bool
|
||||
|
||||
Reference in New Issue
Block a user