Non puoi selezionare più di 25 argomenti Gli argomenti devono iniziare con una lettera o un numero, possono includere trattini ('-') e possono essere lunghi fino a 35 caratteri.
 
 
 

378 righe
10 KiB

  1. package ratio_setting
  2. import (
  3. "fmt"
  4. "math"
  5. "regexp"
  6. "sort"
  7. "strconv"
  8. "strings"
  9. "github.com/QuantumNous/new-api/common"
  10. "github.com/QuantumNous/new-api/types"
  11. )
  12. const ModelPricingRulesOptionKey = "ModelPricingRules"
  13. var (
  14. pricingConfigMap = types.NewRWMap[string, *types.PricingConfig]()
  15. dimensionKeyRE = regexp.MustCompile(`^[a-z][a-z0-9_]{0,31}$`)
  16. requestSourceRE = regexp.MustCompile(`^request\.[a-zA-Z_][a-zA-Z0-9_.]*$`)
  17. derivedSourceRE = regexp.MustCompile(`^derived\.(megapixels|aspect_ratio|input_mode)$`)
  18. reservedDimKeys = map[string]bool{
  19. "price": true,
  20. "source": true,
  21. "__fallback__": true,
  22. "price_formula": true,
  23. "pricing_mode": true,
  24. "fallback_strategy": true,
  25. "schema_version": true,
  26. "scope": true,
  27. "billing_unit": true,
  28. "preconsume_strategy": true,
  29. }
  30. )
  31. func GetPricingConfig(model string) *types.PricingConfig {
  32. cfg, ok := pricingConfigMap.Get(model)
  33. if !ok {
  34. return nil
  35. }
  36. return types.ClonePricingConfig(cfg)
  37. }
  38. func GetPricingConfigCopy() map[string]*types.PricingConfig {
  39. src := pricingConfigMap.ReadAll()
  40. dst := make(map[string]*types.PricingConfig, len(src))
  41. for k, v := range src {
  42. dst[k] = types.ClonePricingConfig(v)
  43. }
  44. return dst
  45. }
  46. func SetPricingConfig(model string, cfg *types.PricingConfig) error {
  47. if strings.TrimSpace(model) == "" {
  48. return fmt.Errorf("model name is required")
  49. }
  50. clone := types.ClonePricingConfig(cfg)
  51. if err := ValidatePricingConfig(model, clone); err != nil {
  52. return err
  53. }
  54. pricingConfigMap.Set(model, clone)
  55. InvalidateExposedDataCache()
  56. return nil
  57. }
  58. func DeletePricingConfig(model string) error {
  59. all := pricingConfigMap.ReadAll()
  60. delete(all, model)
  61. pricingConfigMap.Clear()
  62. pricingConfigMap.AddAll(all)
  63. InvalidateExposedDataCache()
  64. return nil
  65. }
  66. func ModelPricingRules2JSONString() string {
  67. bytes, err := common.Marshal(GetPricingConfigCopy())
  68. if err != nil {
  69. return "{}"
  70. }
  71. return string(bytes)
  72. }
  73. func UpdateModelPricingRulesByJSONString(jsonStr string) error {
  74. if strings.TrimSpace(jsonStr) == "" {
  75. jsonStr = "{}"
  76. }
  77. raw := map[string]*types.PricingConfig{}
  78. if err := common.UnmarshalJsonStr(jsonStr, &raw); err != nil {
  79. return err
  80. }
  81. next := make(map[string]*types.PricingConfig, len(raw))
  82. for model, cfg := range raw {
  83. clone := types.ClonePricingConfig(cfg)
  84. if err := ValidatePricingConfig(model, clone); err != nil {
  85. return fmt.Errorf("model %s: %w", model, err)
  86. }
  87. next[model] = clone
  88. }
  89. pricingConfigMap.Clear()
  90. pricingConfigMap.AddAll(next)
  91. InvalidateExposedDataCache()
  92. return nil
  93. }
  94. func ValidatePricingConfig(model string, cfg *types.PricingConfig) error {
  95. if cfg == nil {
  96. return fmt.Errorf("pricing config is required")
  97. }
  98. if cfg.SchemaVersion != types.PricingSchemaVersion {
  99. return fmt.Errorf("schema_version must be %d", types.PricingSchemaVersion)
  100. }
  101. if cfg.Scope != types.PricingScopeModel {
  102. return fmt.Errorf("scope must be %q", types.PricingScopeModel)
  103. }
  104. types.NormalizePricingConfigDefaults(cfg)
  105. if !isAllowedBillingUnit(cfg.BillingUnit) {
  106. return fmt.Errorf("billing_unit must be per_call or per_1m_tokens")
  107. }
  108. if !isAllowedPreconsumeStrategy(cfg.PreconsumeStrategy) {
  109. return fmt.Errorf("preconsume_strategy must be exact or minimum")
  110. }
  111. if cfg.BillingUnit == types.BillingUnitPerCall && cfg.PreconsumeStrategy != types.PreconsumeStrategyExact {
  112. return fmt.Errorf("billing_unit per_call requires preconsume_strategy exact")
  113. }
  114. if cfg.BillingUnit == types.BillingUnitPer1MTokens {
  115. if cfg.PreconsumeStrategy != types.PreconsumeStrategyMinimum {
  116. return fmt.Errorf("billing_unit per_1m_tokens requires preconsume_strategy minimum")
  117. }
  118. if !SupportsAnyMatrixUsageBillingModel(model) {
  119. return fmt.Errorf("model %s does not support matrix usage billing", model)
  120. }
  121. }
  122. if len(cfg.Dimensions) == 0 {
  123. return fmt.Errorf("dimensions must not be empty")
  124. }
  125. if len(cfg.Table) == 0 {
  126. return fmt.Errorf("table must not be empty")
  127. }
  128. if err := validateFallback(cfg.Fallback); err != nil {
  129. return err
  130. }
  131. if err := validateDimensions(cfg.Dimensions); err != nil {
  132. return err
  133. }
  134. if err := validateRows(cfg.Dimensions, cfg.Table); err != nil {
  135. return err
  136. }
  137. return nil
  138. }
  139. func SupportsAnyMatrixUsageBillingModel(modelName string) bool {
  140. switch modelName {
  141. case "seedance-2", "doubao-video", "doubao-video-seedance":
  142. return true
  143. default:
  144. return false
  145. }
  146. }
  147. func isAllowedBillingUnit(unit string) bool {
  148. return unit == types.BillingUnitPerCall || unit == types.BillingUnitPer1MTokens
  149. }
  150. func isAllowedPreconsumeStrategy(strategy string) bool {
  151. return strategy == types.PreconsumeStrategyExact || strategy == types.PreconsumeStrategyMinimum
  152. }
  153. func validateFallback(fallback types.PricingFallback) error {
  154. switch fallback.Strategy {
  155. case types.PricingFallbackReject, types.PricingFallbackMax:
  156. return nil
  157. case types.PricingFallbackDefault:
  158. if fallback.DefaultPrice < 0 {
  159. return fmt.Errorf("fallback.default_price must be >= 0")
  160. }
  161. return nil
  162. default:
  163. return fmt.Errorf("fallback.strategy must be reject, max, or default")
  164. }
  165. }
  166. func validateDimensions(dimensions []types.PricingDimension) error {
  167. seen := map[string]bool{}
  168. for _, dim := range dimensions {
  169. if !dimensionKeyRE.MatchString(dim.Key) {
  170. return fmt.Errorf("dimension key %q is invalid", dim.Key)
  171. }
  172. if reservedDimKeys[dim.Key] {
  173. return fmt.Errorf("dimension key %q is reserved", dim.Key)
  174. }
  175. if seen[dim.Key] {
  176. return fmt.Errorf("dimension key %q is duplicated", dim.Key)
  177. }
  178. seen[dim.Key] = true
  179. if dim.Type != "string" && dim.Type != "number" && dim.Type != "boolean" {
  180. return fmt.Errorf("dimension %q type must be string, number, or boolean", dim.Key)
  181. }
  182. if !requestSourceRE.MatchString(dim.Source) && !derivedSourceRE.MatchString(dim.Source) {
  183. return fmt.Errorf("dimension %q source is not allowed", dim.Key)
  184. }
  185. if dim.Optional {
  186. if dim.Default == nil {
  187. return fmt.Errorf("dimension %q default required when optional=true", dim.Key)
  188. }
  189. if !valueMatchesType(dim.Default, dim.Type) {
  190. return fmt.Errorf("dimension %q default type must match %s", dim.Key, dim.Type)
  191. }
  192. } else if dim.Default != nil {
  193. return fmt.Errorf("dimension %q default must be null when optional=false", dim.Key)
  194. }
  195. }
  196. return nil
  197. }
  198. func validateRows(dimensions []types.PricingDimension, rows []types.PricingRow) error {
  199. for i, row := range rows {
  200. if err := validateRowFields(dimensions, row); err != nil {
  201. return fmt.Errorf("row %d: %w", i, err)
  202. }
  203. }
  204. if err := validateUniqueRows(dimensions, rows); err != nil {
  205. return err
  206. }
  207. if err := validateAmbiguousRows(dimensions, rows); err != nil {
  208. return err
  209. }
  210. return nil
  211. }
  212. func validateRowFields(dimensions []types.PricingDimension, row types.PricingRow) error {
  213. allowed := map[string]bool{"price": true, "source": true}
  214. for _, dim := range dimensions {
  215. allowed[dim.Key] = true
  216. }
  217. for key := range row {
  218. if !allowed[key] {
  219. return fmt.Errorf("unexpected field %q", key)
  220. }
  221. }
  222. for _, dim := range dimensions {
  223. v, ok := row[dim.Key]
  224. if !ok {
  225. return fmt.Errorf("missing dimension %q", dim.Key)
  226. }
  227. if s, ok := v.(string); ok && s == "*" {
  228. continue
  229. }
  230. if !valueMatchesType(v, dim.Type) {
  231. return fmt.Errorf("dimension %q value type must match %s or wildcard", dim.Key, dim.Type)
  232. }
  233. }
  234. price, ok := numberValue(row["price"])
  235. if !ok || price < 0 {
  236. return fmt.Errorf("price must be >= 0")
  237. }
  238. source, ok := row["source"].(string)
  239. if !ok || (source != types.PricingRowSourceManual && source != types.PricingRowSourceGenerated) {
  240. return fmt.Errorf("source must be manual or generated")
  241. }
  242. return nil
  243. }
  244. func validateUniqueRows(dimensions []types.PricingDimension, rows []types.PricingRow) error {
  245. seen := map[string]bool{}
  246. for _, row := range rows {
  247. key := rowSignature(dimensions, row)
  248. if seen[key] {
  249. return fmt.Errorf("duplicate pricing row %s", key)
  250. }
  251. seen[key] = true
  252. }
  253. return nil
  254. }
  255. func validateAmbiguousRows(dimensions []types.PricingDimension, rows []types.PricingRow) error {
  256. for i := 0; i < len(rows); i++ {
  257. for j := i + 1; j < len(rows); j++ {
  258. if wildcardCount(dimensions, rows[i]) != wildcardCount(dimensions, rows[j]) {
  259. continue
  260. }
  261. if rowsIntersect(dimensions, rows[i], rows[j]) {
  262. return fmt.Errorf("ambiguous rows can both match with same wildcard count")
  263. }
  264. }
  265. }
  266. return nil
  267. }
  268. func rowSignature(dimensions []types.PricingDimension, row types.PricingRow) string {
  269. parts := make([]string, 0, len(dimensions))
  270. for _, dim := range dimensions {
  271. parts = append(parts, dim.Key+"="+literalValue(row[dim.Key]))
  272. }
  273. sort.Strings(parts)
  274. return strings.Join(parts, "|")
  275. }
  276. func wildcardCount(dimensions []types.PricingDimension, row types.PricingRow) int {
  277. count := 0
  278. for _, dim := range dimensions {
  279. if s, ok := row[dim.Key].(string); ok && s == "*" {
  280. count++
  281. }
  282. }
  283. return count
  284. }
  285. func rowsIntersect(dimensions []types.PricingDimension, a, b types.PricingRow) bool {
  286. for _, dim := range dimensions {
  287. av := a[dim.Key]
  288. bv := b[dim.Key]
  289. as, awild := av.(string)
  290. bs, bwild := bv.(string)
  291. if awild && as == "*" || bwild && bs == "*" {
  292. continue
  293. }
  294. if !sameLiteralValue(av, bv) {
  295. return false
  296. }
  297. }
  298. return true
  299. }
  300. func valueMatchesType(v any, typ string) bool {
  301. switch typ {
  302. case "string":
  303. _, ok := v.(string)
  304. return ok
  305. case "number":
  306. _, ok := numberValue(v)
  307. return ok
  308. case "boolean":
  309. _, ok := v.(bool)
  310. return ok
  311. default:
  312. return false
  313. }
  314. }
  315. func numberValue(v any) (float64, bool) {
  316. switch n := v.(type) {
  317. case float64:
  318. return n, !math.IsNaN(n) && !math.IsInf(n, 0)
  319. case float32:
  320. f := float64(n)
  321. return f, !math.IsNaN(f) && !math.IsInf(f, 0)
  322. case int:
  323. return float64(n), true
  324. case int64:
  325. return float64(n), true
  326. case int32:
  327. return float64(n), true
  328. case jsonNumber:
  329. f, err := strconv.ParseFloat(string(n), 64)
  330. return f, err == nil && !math.IsNaN(f) && !math.IsInf(f, 0)
  331. default:
  332. return 0, false
  333. }
  334. }
  335. type jsonNumber string
  336. func literalValue(v any) string {
  337. if f, ok := numberValue(v); ok {
  338. return strconv.FormatFloat(f, 'f', -1, 64)
  339. }
  340. return fmt.Sprintf("%v", v)
  341. }
  342. func sameLiteralValue(a, b any) bool {
  343. af, aok := numberValue(a)
  344. bf, bok := numberValue(b)
  345. if aok && bok {
  346. return af == bf
  347. }
  348. return fmt.Sprintf("%v", a) == fmt.Sprintf("%v", b)
  349. }