feat(*): 模型计费与渠道目录 - 支持视频 Token 报价结算和稳定模型展示
This commit is contained in:
41
backend/internal/kernel/token_billing.go
Normal file
41
backend/internal/kernel/token_billing.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package kernel
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidTokenBilling = errors.New("invalid token billing parameters")
|
||||
ErrTokenBillingOverflow = errors.New("token billing amount overflows int64")
|
||||
)
|
||||
|
||||
type TokenBillingTerm struct {
|
||||
Tokens int64
|
||||
PriceMicrocredits int64
|
||||
}
|
||||
|
||||
// TokenBillingAmount 按每百万 Token 单价及万分比倍率汇总费用,只对最终微积分金额上取整。
|
||||
func TokenBillingAmount(multiplierBPS int64, terms ...TokenBillingTerm) (int64, error) {
|
||||
if multiplierBPS <= 0 {
|
||||
return 0, ErrInvalidTokenBilling
|
||||
}
|
||||
var total, product big.Int
|
||||
for _, term := range terms {
|
||||
if term.Tokens < 0 || term.PriceMicrocredits < 0 {
|
||||
return 0, ErrInvalidTokenBilling
|
||||
}
|
||||
product.Mul(big.NewInt(term.Tokens), big.NewInt(term.PriceMicrocredits))
|
||||
total.Add(&total, &product)
|
||||
}
|
||||
total.Mul(&total, big.NewInt(multiplierBPS))
|
||||
var remainder big.Int
|
||||
total.QuoRem(&total, big.NewInt(10_000_000_000), &remainder)
|
||||
if remainder.Sign() != 0 {
|
||||
total.Add(&total, big.NewInt(1))
|
||||
}
|
||||
if !total.IsInt64() {
|
||||
return 0, ErrTokenBillingOverflow
|
||||
}
|
||||
return total.Int64(), nil
|
||||
}
|
||||
90
backend/internal/kernel/token_billing_test.go
Normal file
90
backend/internal/kernel/token_billing_test.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package kernel
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTokenBillingAmount(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
multiplier int64
|
||||
terms []TokenBillingTerm
|
||||
want int64
|
||||
}{
|
||||
{
|
||||
name: "maximum configured price", multiplier: 10_000,
|
||||
terms: []TokenBillingTerm{{Tokens: 10_000_000, PriceMicrocredits: 1_000_000_000_000}},
|
||||
want: 10_000_000_000_000,
|
||||
},
|
||||
{
|
||||
name: "maximum configured price with multiplier", multiplier: 12_500,
|
||||
terms: []TokenBillingTerm{{Tokens: 10_000_000, PriceMicrocredits: 1_000_000_000_000}},
|
||||
want: 12_500_000_000_000,
|
||||
},
|
||||
{
|
||||
name: "round once after combining terms", multiplier: 10_000,
|
||||
terms: []TokenBillingTerm{{Tokens: 1, PriceMicrocredits: 400_000}, {Tokens: 1, PriceMicrocredits: 400_000}, {Tokens: 1, PriceMicrocredits: 100_000}},
|
||||
want: 1,
|
||||
},
|
||||
{
|
||||
name: "apply multiplier before rounding", multiplier: 12_500,
|
||||
terms: []TokenBillingTerm{{Tokens: 1, PriceMicrocredits: 400_000}, {Tokens: 1, PriceMicrocredits: 400_000}},
|
||||
want: 1,
|
||||
},
|
||||
{
|
||||
name: "fractional microcredit rounds up", multiplier: 1,
|
||||
terms: []TokenBillingTerm{{Tokens: 1, PriceMicrocredits: 1}},
|
||||
want: 1,
|
||||
},
|
||||
{
|
||||
name: "maximum int64 result after overflowing intermediate sum", multiplier: 10_000,
|
||||
terms: []TokenBillingTerm{{Tokens: math.MaxInt64, PriceMicrocredits: 500_000}, {Tokens: math.MaxInt64, PriceMicrocredits: 500_000}},
|
||||
want: math.MaxInt64,
|
||||
},
|
||||
{
|
||||
name: "free usage", multiplier: math.MaxInt64,
|
||||
terms: []TokenBillingTerm{{Tokens: math.MaxInt64, PriceMicrocredits: 0}},
|
||||
want: 0,
|
||||
},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
amount, err := TokenBillingAmount(test.multiplier, test.terms...)
|
||||
if err != nil || amount != test.want {
|
||||
t.Fatalf("TokenBillingAmount() = %d, %v; want %d", amount, err, test.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTokenBillingAmountRejectsInvalidParametersAndFinalOverflow(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
multiplier int64
|
||||
terms []TokenBillingTerm
|
||||
wantErr error
|
||||
}{
|
||||
{name: "zero multiplier", terms: []TokenBillingTerm{{Tokens: 1, PriceMicrocredits: 1}}, wantErr: ErrInvalidTokenBilling},
|
||||
{name: "negative multiplier", multiplier: -1, wantErr: ErrInvalidTokenBilling},
|
||||
{name: "negative tokens", multiplier: 10_000, terms: []TokenBillingTerm{{Tokens: -1}}, wantErr: ErrInvalidTokenBilling},
|
||||
{name: "negative price even with zero tokens", multiplier: 10_000, terms: []TokenBillingTerm{{PriceMicrocredits: -1}}, wantErr: ErrInvalidTokenBilling},
|
||||
{
|
||||
name: "final amount overflow", multiplier: 10_001,
|
||||
terms: []TokenBillingTerm{{Tokens: math.MaxInt64, PriceMicrocredits: 1_000_000}},
|
||||
wantErr: ErrTokenBillingOverflow,
|
||||
},
|
||||
{
|
||||
name: "rounding causes final overflow", multiplier: 10_000,
|
||||
terms: []TokenBillingTerm{{Tokens: math.MaxInt64, PriceMicrocredits: 1_000_000}, {Tokens: 1, PriceMicrocredits: 1}},
|
||||
wantErr: ErrTokenBillingOverflow,
|
||||
},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
amount, err := TokenBillingAmount(test.multiplier, test.terms...)
|
||||
if amount != 0 || !errors.Is(err, test.wantErr) {
|
||||
t.Fatalf("TokenBillingAmount() = %d, %v; want 0, %v", amount, err, test.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user