Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 
 

383 строки
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|video_input)$`)
  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. // Keep in sync with matrixUsageCapabilities in relay/helper/matrix_usage_capability.go
  141. switch modelName {
  142. case "seedance-2", "doubao-video", "doubao-video-seedance",
  143. "doubao-seedance-2-0-260128", "doubao-seedance-2-0-fast-260128",
  144. "Doubao-Seedance-2.0", "Doubao-Seedance-2.0-fast",
  145. "kling-v1", "kling-v1-6", "kling-v2-6", "kling-v3",
  146. "kling-video-o1", "kling-v3-omni":
  147. return true
  148. default:
  149. return false
  150. }
  151. }
  152. func isAllowedBillingUnit(unit string) bool {
  153. return unit == types.BillingUnitPerCall || unit == types.BillingUnitPer1MTokens
  154. }
  155. func isAllowedPreconsumeStrategy(strategy string) bool {
  156. return strategy == types.PreconsumeStrategyExact || strategy == types.PreconsumeStrategyMinimum
  157. }
  158. func validateFallback(fallback types.PricingFallback) error {
  159. switch fallback.Strategy {
  160. case types.PricingFallbackReject, types.PricingFallbackMax:
  161. return nil
  162. case types.PricingFallbackDefault:
  163. if fallback.DefaultPrice < 0 {
  164. return fmt.Errorf("fallback.default_price must be >= 0")
  165. }
  166. return nil
  167. default:
  168. return fmt.Errorf("fallback.strategy must be reject, max, or default")
  169. }
  170. }
  171. func validateDimensions(dimensions []types.PricingDimension) error {
  172. seen := map[string]bool{}
  173. for _, dim := range dimensions {
  174. if !dimensionKeyRE.MatchString(dim.Key) {
  175. return fmt.Errorf("dimension key %q is invalid", dim.Key)
  176. }
  177. if reservedDimKeys[dim.Key] {
  178. return fmt.Errorf("dimension key %q is reserved", dim.Key)
  179. }
  180. if seen[dim.Key] {
  181. return fmt.Errorf("dimension key %q is duplicated", dim.Key)
  182. }
  183. seen[dim.Key] = true
  184. if dim.Type != "string" && dim.Type != "number" && dim.Type != "boolean" {
  185. return fmt.Errorf("dimension %q type must be string, number, or boolean", dim.Key)
  186. }
  187. if !requestSourceRE.MatchString(dim.Source) && !derivedSourceRE.MatchString(dim.Source) {
  188. return fmt.Errorf("dimension %q source is not allowed", dim.Key)
  189. }
  190. if dim.Optional {
  191. if dim.Default == nil {
  192. return fmt.Errorf("dimension %q default required when optional=true", dim.Key)
  193. }
  194. if !valueMatchesType(dim.Default, dim.Type) {
  195. return fmt.Errorf("dimension %q default type must match %s", dim.Key, dim.Type)
  196. }
  197. } else if dim.Default != nil {
  198. return fmt.Errorf("dimension %q default must be null when optional=false", dim.Key)
  199. }
  200. }
  201. return nil
  202. }
  203. func validateRows(dimensions []types.PricingDimension, rows []types.PricingRow) error {
  204. for i, row := range rows {
  205. if err := validateRowFields(dimensions, row); err != nil {
  206. return fmt.Errorf("row %d: %w", i, err)
  207. }
  208. }
  209. if err := validateUniqueRows(dimensions, rows); err != nil {
  210. return err
  211. }
  212. if err := validateAmbiguousRows(dimensions, rows); err != nil {
  213. return err
  214. }
  215. return nil
  216. }
  217. func validateRowFields(dimensions []types.PricingDimension, row types.PricingRow) error {
  218. allowed := map[string]bool{"price": true, "source": true}
  219. for _, dim := range dimensions {
  220. allowed[dim.Key] = true
  221. }
  222. for key := range row {
  223. if !allowed[key] {
  224. return fmt.Errorf("unexpected field %q", key)
  225. }
  226. }
  227. for _, dim := range dimensions {
  228. v, ok := row[dim.Key]
  229. if !ok {
  230. return fmt.Errorf("missing dimension %q", dim.Key)
  231. }
  232. if s, ok := v.(string); ok && s == "*" {
  233. continue
  234. }
  235. if !valueMatchesType(v, dim.Type) {
  236. return fmt.Errorf("dimension %q value type must match %s or wildcard", dim.Key, dim.Type)
  237. }
  238. }
  239. price, ok := numberValue(row["price"])
  240. if !ok || price < 0 {
  241. return fmt.Errorf("price must be >= 0")
  242. }
  243. source, ok := row["source"].(string)
  244. if !ok || (source != types.PricingRowSourceManual && source != types.PricingRowSourceGenerated) {
  245. return fmt.Errorf("source must be manual or generated")
  246. }
  247. return nil
  248. }
  249. func validateUniqueRows(dimensions []types.PricingDimension, rows []types.PricingRow) error {
  250. seen := map[string]bool{}
  251. for _, row := range rows {
  252. key := rowSignature(dimensions, row)
  253. if seen[key] {
  254. return fmt.Errorf("duplicate pricing row %s", key)
  255. }
  256. seen[key] = true
  257. }
  258. return nil
  259. }
  260. func validateAmbiguousRows(dimensions []types.PricingDimension, rows []types.PricingRow) error {
  261. for i := 0; i < len(rows); i++ {
  262. for j := i + 1; j < len(rows); j++ {
  263. if wildcardCount(dimensions, rows[i]) != wildcardCount(dimensions, rows[j]) {
  264. continue
  265. }
  266. if rowsIntersect(dimensions, rows[i], rows[j]) {
  267. return fmt.Errorf("ambiguous rows can both match with same wildcard count")
  268. }
  269. }
  270. }
  271. return nil
  272. }
  273. func rowSignature(dimensions []types.PricingDimension, row types.PricingRow) string {
  274. parts := make([]string, 0, len(dimensions))
  275. for _, dim := range dimensions {
  276. parts = append(parts, dim.Key+"="+literalValue(row[dim.Key]))
  277. }
  278. sort.Strings(parts)
  279. return strings.Join(parts, "|")
  280. }
  281. func wildcardCount(dimensions []types.PricingDimension, row types.PricingRow) int {
  282. count := 0
  283. for _, dim := range dimensions {
  284. if s, ok := row[dim.Key].(string); ok && s == "*" {
  285. count++
  286. }
  287. }
  288. return count
  289. }
  290. func rowsIntersect(dimensions []types.PricingDimension, a, b types.PricingRow) bool {
  291. for _, dim := range dimensions {
  292. av := a[dim.Key]
  293. bv := b[dim.Key]
  294. as, awild := av.(string)
  295. bs, bwild := bv.(string)
  296. if awild && as == "*" || bwild && bs == "*" {
  297. continue
  298. }
  299. if !sameLiteralValue(av, bv) {
  300. return false
  301. }
  302. }
  303. return true
  304. }
  305. func valueMatchesType(v any, typ string) bool {
  306. switch typ {
  307. case "string":
  308. _, ok := v.(string)
  309. return ok
  310. case "number":
  311. _, ok := numberValue(v)
  312. return ok
  313. case "boolean":
  314. _, ok := v.(bool)
  315. return ok
  316. default:
  317. return false
  318. }
  319. }
  320. func numberValue(v any) (float64, bool) {
  321. switch n := v.(type) {
  322. case float64:
  323. return n, !math.IsNaN(n) && !math.IsInf(n, 0)
  324. case float32:
  325. f := float64(n)
  326. return f, !math.IsNaN(f) && !math.IsInf(f, 0)
  327. case int:
  328. return float64(n), true
  329. case int64:
  330. return float64(n), true
  331. case int32:
  332. return float64(n), true
  333. case jsonNumber:
  334. f, err := strconv.ParseFloat(string(n), 64)
  335. return f, err == nil && !math.IsNaN(f) && !math.IsInf(f, 0)
  336. default:
  337. return 0, false
  338. }
  339. }
  340. type jsonNumber string
  341. func literalValue(v any) string {
  342. if f, ok := numberValue(v); ok {
  343. return strconv.FormatFloat(f, 'f', -1, 64)
  344. }
  345. return fmt.Sprintf("%v", v)
  346. }
  347. func sameLiteralValue(a, b any) bool {
  348. af, aok := numberValue(a)
  349. bf, bok := numberValue(b)
  350. if aok && bok {
  351. return af == bf
  352. }
  353. return fmt.Sprintf("%v", a) == fmt.Sprintf("%v", b)
  354. }