package helper import ( "fmt" "net/http" "strings" "github.com/QuantumNous/new-api/common" "github.com/QuantumNous/new-api/dto" "github.com/QuantumNous/new-api/types" ) type pricingLookupResult struct { Mode types.PricingMode PriceUSD float64 Snapshot map[string]any } func lookupPricingConfig(cfg *types.PricingConfig, resolved *ResolvedDimensionValues) (*pricingLookupResult, *dto.TaskError) { if cfg == nil { return nil, localPricingTaskError("pricing config is required", "pricing_config_required") } var best types.PricingRow bestWildcardCount := len(cfg.Dimensions) + 1 bestCount := 0 for _, row := range cfg.Table { wildcards, ok := rowMatchesDimensions(cfg.Dimensions, row, resolved.Values) if !ok { continue } if wildcards < bestWildcardCount { best = row bestWildcardCount = wildcards bestCount = 1 continue } if wildcards == bestWildcardCount { bestCount++ } } if best != nil && bestCount == 1 { price, _ := pricingNumberValue(best["price"]) return &pricingLookupResult{ Mode: types.PricingModeMatched, PriceUSD: price, Snapshot: buildPricingSnapshot(cfg, resolved, price, types.PricingModeMatched, ""), }, nil } if bestCount > 1 { return fallbackPricingConfig(cfg, resolved) } return fallbackPricingConfig(cfg, resolved) } func buildMatchedDecision(cfg *types.PricingConfig, result *pricingLookupResult, group types.GroupRatioInfo) *types.PricingDecision { return buildMatrixDecision(cfg, result, group) } func buildFallbackDecision(cfg *types.PricingConfig, result *pricingLookupResult, group types.GroupRatioInfo) *types.PricingDecision { return buildMatrixDecision(cfg, result, group) } func buildLegacyDecision(modelPrice float64, group types.GroupRatioInfo, free bool, quota int) *types.PricingDecision { return &types.PricingDecision{ Mode: types.PricingModeLegacy, PriceUSD: modelPrice, Quota: quota, GroupRatioInfo: group, FreeModel: free, LegacyModelPrice: modelPrice, UsePrice: true, PerCallBilling: true, Finalized: true, } } func buildRemixMatrixDecision(origin *types.OriginPricingSnapshot, group types.GroupRatioInfo) (*types.PricingDecision, *dto.TaskError) { if origin == nil { return nil, localPricingTaskError("origin pricing snapshot is required", "invalid_pricing_snapshot") } billingUnit := origin.BillingUnit if billingUnit == "" { billingUnit = types.BillingUnitPerCall } result := &pricingLookupResult{ Mode: types.PricingModeRemix, PriceUSD: origin.PriceUSD, Snapshot: types.CloneMapAny(origin.Snapshot), } cfg := &types.PricingConfig{BillingUnit: billingUnit} decision := buildMatrixDecision(cfg, result, group) decision.Mode = types.PricingModeRemix decision.TokenUnitPriceUSD = origin.TokenUnitPriceUSD decision.OtherRatios = types.CloneRatios(origin.OtherRatios) decision.PerCallBilling = origin.PerCallBilling if billingUnit == types.BillingUnitPer1MTokens { decision.PriceUSD = origin.TokenUnitPriceUSD decision.Quota = minimumUsagePreconsumeQuota(origin.TokenUnitPriceUSD, group.GroupRatio) } return decision, nil } func buildRemixLegacyDecision(origin *types.OriginPricingSnapshot, group types.GroupRatioInfo) (*types.PricingDecision, *dto.TaskError) { if origin == nil { return nil, localPricingTaskError("origin legacy pricing snapshot is required", "invalid_legacy_pricing_snapshot") } quota := int(origin.PriceUSD * common.QuotaPerUnit * group.GroupRatio) return &types.PricingDecision{ Mode: types.PricingModeRemix, PriceUSD: origin.PriceUSD, Quota: quota, GroupRatioInfo: group, LegacyModelPrice: origin.PriceUSD, OtherRatios: types.CloneRatios(origin.OtherRatios), UsePrice: true, PerCallBilling: origin.PerCallBilling, Finalized: true, }, nil } func buildMatrixDecision(cfg *types.PricingConfig, result *pricingLookupResult, group types.GroupRatioInfo) *types.PricingDecision { billingUnit := cfg.BillingUnit if billingUnit == "" { billingUnit = types.BillingUnitPerCall } decision := &types.PricingDecision{ Mode: result.Mode, BillingMode: types.BillingModeMatrix, BillingUnit: billingUnit, GroupRatioInfo: group, Snapshot: types.CloneMapAny(result.Snapshot), UsePrice: true, Finalized: true, } if billingUnit == types.BillingUnitPer1MTokens { decision.TokenUnitPriceUSD = result.PriceUSD decision.PriceUSD = result.PriceUSD decision.Quota = minimumUsagePreconsumeQuota(result.PriceUSD, group.GroupRatio) decision.PerCallBilling = false return decision } decision.PriceUSD = result.PriceUSD decision.Quota = int(result.PriceUSD * common.QuotaPerUnit * group.GroupRatio) decision.PerCallBilling = true if group.GroupRatio == 0 || result.PriceUSD == 0 { decision.FreeModel = true decision.Quota = 0 } return decision } func fallbackPricingConfig(cfg *types.PricingConfig, resolved *ResolvedDimensionValues) (*pricingLookupResult, *dto.TaskError) { switch cfg.Fallback.Strategy { case types.PricingFallbackReject: return nil, localPricingTaskError("pricing dimensions did not match any row", "pricing_no_match") case types.PricingFallbackMax: price := maxPricingTablePrice(cfg.Table) return &pricingLookupResult{ Mode: types.PricingModeFallbackMax, PriceUSD: price, Snapshot: buildPricingSnapshot(cfg, resolved, price, types.PricingModeFallbackMax, cfg.Fallback.Strategy), }, nil case types.PricingFallbackDefault: price := cfg.Fallback.DefaultPrice return &pricingLookupResult{ Mode: types.PricingModeFallbackDefault, PriceUSD: price, Snapshot: buildPricingSnapshot(cfg, resolved, price, types.PricingModeFallbackDefault, cfg.Fallback.Strategy), }, nil default: return nil, localPricingTaskError(fmt.Sprintf("unsupported fallback strategy %q", cfg.Fallback.Strategy), "invalid_pricing_config") } } func buildPricingSnapshot(cfg *types.PricingConfig, resolved *ResolvedDimensionValues, price float64, mode types.PricingMode, fallbackStrategy string) map[string]any { snapshot := make(map[string]any, len(cfg.Dimensions)+4) for _, dim := range cfg.Dimensions { snapshot[dim.Key] = resolved.Values[dim.Key] } snapshot["price"] = price snapshot["billing_unit"] = cfg.BillingUnit snapshot["pricing_mode"] = string(mode) if fallbackStrategy != "" { snapshot["fallback_strategy"] = fallbackStrategy } return snapshot } func rowMatchesDimensions(dimensions []types.PricingDimension, row types.PricingRow, values map[string]any) (int, bool) { wildcards := 0 for _, dim := range dimensions { rv := row[dim.Key] if s, ok := rv.(string); ok && s == "*" { wildcards++ continue } if !samePricingLiteral(rv, values[dim.Key], dim.Type) { return 0, false } } return wildcards, true } func maxPricingTablePrice(rows []types.PricingRow) float64 { var max float64 for i, row := range rows { price, ok := pricingNumberValue(row["price"]) if !ok { continue } if i == 0 || price > max { max = price } } return max } func minimumUsagePreconsumeQuota(tokenUnitPriceUSD float64, groupRatio float64) int { if tokenUnitPriceUSD == 0 || groupRatio == 0 { return 0 } if common.PreConsumedQuota <= 0 { return 0 } quota := int(float64(common.PreConsumedQuota) * tokenUnitPriceUSD / 1_000_000 * common.QuotaPerUnit * groupRatio) if quota <= 0 { return 1 } return quota } func priceDataFromDecision(decision *types.PricingDecision) types.PriceData { if decision == nil { return types.PriceData{} } return types.PriceData{ FreeModel: decision.FreeModel, ModelPrice: decision.PriceUSD, Quota: decision.Quota, GroupRatioInfo: decision.GroupRatioInfo, OtherRatios: types.CloneRatios(decision.OtherRatios), UsePrice: decision.UsePrice, PricingDecision: decision, } } func pricingNumberValue(v any) (float64, bool) { switch n := v.(type) { case float64: return n, true case float32: return float64(n), true case int: return float64(n), true case int64: return float64(n), true case int32: return float64(n), true default: return 0, false } } func samePricingLiteral(a, b any, dimensionType string) bool { af, aok := pricingNumberValue(a) bf, bok := pricingNumberValue(b) if aok && bok { return af == bf } if dimensionType == "string" { return strings.EqualFold(strings.TrimSpace(fmt.Sprintf("%v", a)), strings.TrimSpace(fmt.Sprintf("%v", b))) } return fmt.Sprintf("%v", a) == fmt.Sprintf("%v", b) } func localPricingTaskError(message string, code string) *dto.TaskError { err := fmt.Errorf("%s", message) return &dto.TaskError{ Code: code, Message: message, StatusCode: http.StatusBadRequest, LocalError: true, Error: err, } }