mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-14 03:52:26 +08:00
Bill free Fast requests at Standard price
This commit is contained in:
@@ -3071,6 +3071,77 @@ func TestOpenAIGatewayServiceRecordUsage_ShadowUsesParentCredentialTierContract(
|
||||
require.InDelta(t, fastCost.TotalCost, usageRepo.lastLog.TotalCost, 1e-10)
|
||||
}
|
||||
|
||||
func TestOpenAIGatewayServiceRecordUsage_FreeOpenAIFastChargesStandard(t *testing.T) {
|
||||
usageRepo := &openAIRecordUsageLogRepoStub{inserted: true}
|
||||
svc := newOpenAIRecordUsageServiceForTest(
|
||||
usageRepo,
|
||||
&openAIRecordUsageUserRepoStub{},
|
||||
&openAIRecordUsageSubRepoStub{},
|
||||
nil,
|
||||
)
|
||||
svc.resolver = NewModelPricingResolver(nil, svc.billingService)
|
||||
groupID := int64(77)
|
||||
serviceTier := "priority"
|
||||
tokens := UsageTokens{InputTokens: 100, OutputTokens: 50}
|
||||
inputPrice := 0.001
|
||||
outputPrice := 0.002
|
||||
fastMultiplier := 3.0
|
||||
apiKey := &APIKey{
|
||||
ID: 1020,
|
||||
GroupID: &groupID,
|
||||
Group: &Group{
|
||||
ID: groupID, Platform: PlatformOpenAI, Status: StatusActive,
|
||||
Hydrated: true, RateMultiplier: 0.5, FreeOpenAIFast: true,
|
||||
ModelPricing: []ChannelModelPricing{{
|
||||
Models: []string{"gpt-5.6-sol"},
|
||||
BillingMode: BillingModeToken,
|
||||
InputPrice: &inputPrice,
|
||||
OutputPrice: &outputPrice,
|
||||
FastMultiplier: &fastMultiplier,
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
err := svc.RecordUsage(context.Background(), &OpenAIRecordUsageInput{
|
||||
Result: &OpenAIForwardResult{
|
||||
RequestID: "resp_free_fast",
|
||||
ServiceTier: &serviceTier,
|
||||
UpstreamResponseServiceTier: "default",
|
||||
Usage: OpenAIUsage{InputTokens: tokens.InputTokens, OutputTokens: tokens.OutputTokens},
|
||||
Model: "gpt-5.6-sol",
|
||||
Duration: time.Second,
|
||||
},
|
||||
APIKey: apiKey,
|
||||
User: &User{ID: 2020},
|
||||
Account: &Account{ID: 3020, Platform: PlatformOpenAI, Type: AccountTypeOAuth},
|
||||
})
|
||||
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, usageRepo.lastLog)
|
||||
require.NotNil(t, usageRepo.lastLog.ServiceTier)
|
||||
require.Equal(t, "priority", *usageRepo.lastLog.ServiceTier)
|
||||
require.InDelta(t, 0.5, usageRepo.lastLog.RateMultiplier, 1e-12)
|
||||
|
||||
standardTotal := float64(tokens.InputTokens)*inputPrice + float64(tokens.OutputTokens)*outputPrice
|
||||
require.InDelta(t, standardTotal*fastMultiplier, usageRepo.lastLog.TotalCost, 1e-10)
|
||||
require.InDelta(t, standardTotal*0.5, usageRepo.lastLog.ActualCost, 1e-10)
|
||||
}
|
||||
|
||||
func TestGroupBillsOpenAIFastAtStandardRequiresOpenAIAccount(t *testing.T) {
|
||||
apiKey := &APIKey{Group: &Group{Platform: PlatformComposite, FreeOpenAIFast: true}}
|
||||
|
||||
require.True(t, groupBillsOpenAIFastAtStandard(
|
||||
apiKey,
|
||||
&Account{Platform: PlatformOpenAI, Type: AccountTypeOAuth},
|
||||
"priority",
|
||||
))
|
||||
require.False(t, groupBillsOpenAIFastAtStandard(
|
||||
apiKey,
|
||||
&Account{Platform: PlatformGrok, Type: AccountTypeAPIKey},
|
||||
"priority",
|
||||
))
|
||||
}
|
||||
|
||||
func TestOpenAIGatewayServiceRecordUsage_ServiceTierNeverRaisedByUpstreamResponse(t *testing.T) {
|
||||
usageRepo := &openAIRecordUsageLogRepoStub{inserted: true}
|
||||
userRepo := &openAIRecordUsageUserRepoStub{}
|
||||
|
||||
@@ -132,6 +132,24 @@ func openAIUsagePricingAt(input *OpenAIRecordUsageInput) time.Time {
|
||||
return timezone.Now()
|
||||
}
|
||||
|
||||
func groupBillsOpenAIFastAtStandard(apiKey *APIKey, account *Account, serviceTier string) bool {
|
||||
if apiKey == nil || apiKey.Group == nil || !apiKey.Group.FreeOpenAIFast {
|
||||
return false
|
||||
}
|
||||
if account == nil || !account.IsOpenAI() {
|
||||
return false
|
||||
}
|
||||
if !groupSupportsOpenAIFast(apiKey.Group.Platform) {
|
||||
return false
|
||||
}
|
||||
switch normalizeBillingServiceTier(serviceTier) {
|
||||
case "priority", "fast":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// RecordUsage records usage and deducts balance
|
||||
func (s *OpenAIGatewayService) RecordUsage(ctx context.Context, input *OpenAIRecordUsageInput) error {
|
||||
if input == nil {
|
||||
@@ -277,6 +295,32 @@ func (s *OpenAIGatewayService) RecordUsage(ctx context.Context, input *OpenAIRec
|
||||
}
|
||||
}
|
||||
|
||||
// Free Fast changes only the customer charge. Keep priority TotalCost and
|
||||
// service_tier for upstream accounting, but evaluate ActualCost once more at
|
||||
// the Standard tier using the same channel, peak, and long-context policy.
|
||||
if groupBillsOpenAIFastAtStandard(apiKey, billingAccount, serviceTier) {
|
||||
standardCost, standardErr := s.calculateOpenAIRecordUsageCost(
|
||||
ctx,
|
||||
result,
|
||||
apiKey,
|
||||
billingModels,
|
||||
multiplier,
|
||||
imageMultiplier,
|
||||
videoMultiplier,
|
||||
baseMultiplier,
|
||||
tokens,
|
||||
"",
|
||||
longContextBillingGate,
|
||||
pricingAt,
|
||||
)
|
||||
if standardErr != nil {
|
||||
return standardErr
|
||||
}
|
||||
if cost != nil && standardCost != nil {
|
||||
cost.ActualCost = standardCost.ActualCost
|
||||
}
|
||||
}
|
||||
|
||||
// Determine billing type
|
||||
isSubscriptionBilling := subscription != nil && apiKey.Group != nil && apiKey.Group.IsSubscriptionType()
|
||||
billingType := BillingTypeBalance
|
||||
|
||||
Reference in New Issue
Block a user