You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

195 lines
5.1 KiB

  1. package types
  2. import (
  3. "errors"
  4. "fmt"
  5. "github.com/QuantumNous/new-api/common"
  6. )
  7. const (
  8. PricingSchemaVersion = 1
  9. PricingScopeModel = "model"
  10. BillingUnitPerCall = "per_call"
  11. BillingUnitPer1MTokens = "per_1m_tokens"
  12. PreconsumeStrategyExact = "exact"
  13. PreconsumeStrategyMinimum = "minimum"
  14. PricingFallbackReject = "reject"
  15. PricingFallbackMax = "max"
  16. PricingFallbackDefault = "default"
  17. PricingRowSourceManual = "manual"
  18. PricingRowSourceGenerated = "generated"
  19. BillingModeMatrix = "matrix"
  20. )
  21. type PricingConfig struct {
  22. SchemaVersion int `json:"schema_version"`
  23. Scope string `json:"scope"`
  24. BillingUnit string `json:"billing_unit,omitempty"`
  25. PreconsumeStrategy string `json:"preconsume_strategy,omitempty"`
  26. Dimensions []PricingDimension `json:"dimensions"`
  27. Table []PricingRow `json:"table"`
  28. Fallback PricingFallback `json:"fallback"`
  29. GeneratorSnapshot map[string]any `json:"generator_snapshot,omitempty"`
  30. }
  31. type PricingDimension struct {
  32. Key string `json:"key"`
  33. Source string `json:"source"`
  34. Type string `json:"type"`
  35. Optional bool `json:"optional,omitempty"`
  36. Default any `json:"default"`
  37. }
  38. type PricingRow map[string]any
  39. type PricingFallback struct {
  40. Strategy string `json:"strategy"`
  41. DefaultPrice float64 `json:"default_price,omitempty"`
  42. }
  43. type OriginPricingSnapshot struct {
  44. BillingMode string
  45. BillingUnit string
  46. PriceUSD float64
  47. TokenUnitPriceUSD float64
  48. Snapshot map[string]any
  49. OtherRatios map[string]float64
  50. PerCallBilling bool
  51. }
  52. func ClonePricingConfig(src *PricingConfig) *PricingConfig {
  53. if src == nil {
  54. return nil
  55. }
  56. return &PricingConfig{
  57. SchemaVersion: src.SchemaVersion,
  58. Scope: src.Scope,
  59. BillingUnit: src.BillingUnit,
  60. PreconsumeStrategy: src.PreconsumeStrategy,
  61. Dimensions: append([]PricingDimension(nil), src.Dimensions...),
  62. Table: ClonePricingRows(src.Table),
  63. Fallback: src.Fallback,
  64. GeneratorSnapshot: CloneMapAny(src.GeneratorSnapshot),
  65. }
  66. }
  67. func ClonePricingRows(src []PricingRow) []PricingRow {
  68. if src == nil {
  69. return nil
  70. }
  71. dst := make([]PricingRow, len(src))
  72. for i := range src {
  73. dst[i] = PricingRow(CloneMapAny(map[string]any(src[i])))
  74. }
  75. return dst
  76. }
  77. func CloneMapAny(src map[string]any) map[string]any {
  78. if src == nil {
  79. return nil
  80. }
  81. bytes, err := common.Marshal(src)
  82. if err != nil {
  83. dst := make(map[string]any, len(src))
  84. for k, v := range src {
  85. dst[k] = v
  86. }
  87. return dst
  88. }
  89. var dst map[string]any
  90. if err := common.Unmarshal(bytes, &dst); err != nil {
  91. panic(fmt.Sprintf("clone map failed after successful marshal: %v", err))
  92. }
  93. return dst
  94. }
  95. func CloneRatios(src map[string]float64) map[string]float64 {
  96. if src == nil {
  97. return nil
  98. }
  99. dst := make(map[string]float64, len(src))
  100. for k, v := range src {
  101. dst[k] = v
  102. }
  103. return dst
  104. }
  105. func NormalizePricingConfigDefaults(cfg *PricingConfig) {
  106. if cfg == nil {
  107. return
  108. }
  109. if cfg.BillingUnit == "" {
  110. cfg.BillingUnit = BillingUnitPerCall
  111. }
  112. if cfg.PreconsumeStrategy == "" {
  113. cfg.PreconsumeStrategy = PreconsumeStrategyExact
  114. }
  115. }
  116. func ResolveMatrixPricingSnapshot(billingUnit string, snapshot map[string]any) (price float64, resolvedBillingUnit string, pricingMode string, err error) {
  117. if snapshot == nil {
  118. return 0, "", "", errors.New("pricing snapshot is required")
  119. }
  120. rawPrice, ok := snapshot["price"]
  121. if !ok {
  122. return 0, "", "", errors.New("pricing snapshot missing price")
  123. }
  124. price, ok = snapshotNumber(rawPrice)
  125. if !ok {
  126. return 0, "", "", errors.New("pricing snapshot price must be numeric")
  127. }
  128. rawMode, ok := snapshot["pricing_mode"].(string)
  129. if !ok || rawMode == "" {
  130. return 0, "", "", errors.New("pricing snapshot pricing_mode must be string")
  131. }
  132. switch PricingMode(rawMode) {
  133. case PricingModeMatched, PricingModeFallbackMax, PricingModeFallbackDefault:
  134. pricingMode = rawMode
  135. default:
  136. return 0, "", "", fmt.Errorf("unsupported pricing_mode %q", rawMode)
  137. }
  138. snapshotUnit, _ := snapshot["billing_unit"].(string)
  139. if snapshotUnit != "" && snapshotUnit != BillingUnitPerCall && snapshotUnit != BillingUnitPer1MTokens {
  140. return 0, "", "", fmt.Errorf("unsupported snapshot billing_unit %q", snapshotUnit)
  141. }
  142. if billingUnit != "" && billingUnit != BillingUnitPerCall && billingUnit != BillingUnitPer1MTokens {
  143. return 0, "", "", fmt.Errorf("unsupported billing_unit %q", billingUnit)
  144. }
  145. switch {
  146. case billingUnit != "" && snapshotUnit != "" && billingUnit != snapshotUnit:
  147. return 0, "", "", fmt.Errorf("billing_unit %q does not match snapshot billing_unit %q", billingUnit, snapshotUnit)
  148. case billingUnit != "":
  149. resolvedBillingUnit = billingUnit
  150. case snapshotUnit != "":
  151. resolvedBillingUnit = snapshotUnit
  152. default:
  153. resolvedBillingUnit = BillingUnitPerCall
  154. }
  155. return price, resolvedBillingUnit, pricingMode, nil
  156. }
  157. func snapshotNumber(v any) (float64, bool) {
  158. switch n := v.(type) {
  159. case float64:
  160. return n, true
  161. case float32:
  162. return float64(n), true
  163. case int:
  164. return float64(n), true
  165. case int64:
  166. return float64(n), true
  167. case int32:
  168. return float64(n), true
  169. default:
  170. return 0, false
  171. }
  172. }