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

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