25'ten fazla konu seçemezsiniz Konular bir harf veya rakamla başlamalı, kısa çizgiler ('-') içerebilir ve en fazla 35 karakter uzunluğunda olabilir.
 
 
 

247 satır
8.1 KiB

  1. package model
  2. import (
  3. "testing"
  4. "time"
  5. "github.com/QuantumNous/new-api/common"
  6. "github.com/QuantumNous/new-api/constant"
  7. "github.com/QuantumNous/new-api/setting/ratio_setting"
  8. "github.com/QuantumNous/new-api/types"
  9. "github.com/glebarez/sqlite"
  10. "github.com/stretchr/testify/require"
  11. "gorm.io/gorm"
  12. )
  13. const testPricingApplyModel = "test-pricing-model-apply"
  14. const testPricingCallModel = "test-pricing-call-model"
  15. const testPricingGlobalModel = "test-pricing-global-model"
  16. func resetPricingRatios(t *testing.T) {
  17. t.Helper()
  18. require.NoError(t, ratio_setting.UpdateModelPricingRulesByJSONString(`{}`))
  19. require.NoError(t, ratio_setting.UpdateModelRatioByJSONString(`{}`))
  20. require.NoError(t, ratio_setting.UpdateCompletionRatioByJSONString(`{}`))
  21. require.NoError(t, ratio_setting.UpdateCacheRatioByJSONString(`{}`))
  22. require.NoError(t, ratio_setting.UpdateCreateCacheRatioByJSONString(`{}`))
  23. require.NoError(t, ratio_setting.UpdateModelPriceByJSONString(`{}`))
  24. }
  25. func setupPricingTest(t *testing.T) {
  26. t.Helper()
  27. resetPricingRatios(t)
  28. db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
  29. require.NoError(t, err)
  30. sqlDB, err := db.DB()
  31. require.NoError(t, err)
  32. sqlDB.SetMaxOpenConns(1)
  33. origDB := DB
  34. origUsingSQLite := common.UsingSQLite
  35. origPricingMap := pricingMap
  36. origPricingByModel := pricingByModel
  37. origVendorsList := vendorsList
  38. origSupportedEndpointMap := supportedEndpointMap
  39. origModelSupportEndpointTypes := modelSupportEndpointTypes
  40. origLastGetPricingTime := lastGetPricingTime
  41. DB = db
  42. common.UsingSQLite = true
  43. pricingMap = nil
  44. pricingByModel = nil
  45. vendorsList = nil
  46. supportedEndpointMap = nil
  47. modelSupportEndpointTypes = make(map[string][]constant.EndpointType)
  48. lastGetPricingTime = time.Time{}
  49. require.NoError(t, db.AutoMigrate(&Model{}, &Vendor{}, &Channel{}, &Ability{}))
  50. t.Cleanup(func() {
  51. DB = origDB
  52. common.UsingSQLite = origUsingSQLite
  53. pricingMap = origPricingMap
  54. pricingByModel = origPricingByModel
  55. vendorsList = origVendorsList
  56. supportedEndpointMap = origSupportedEndpointMap
  57. modelSupportEndpointTypes = origModelSupportEndpointTypes
  58. lastGetPricingTime = origLastGetPricingTime
  59. require.NoError(t, sqlDB.Close())
  60. resetPricingRatios(t)
  61. })
  62. }
  63. func TestApplyGlobalDefault_PerTokenModel(t *testing.T) {
  64. resetPricingRatios(t)
  65. t.Cleanup(func() { resetPricingRatios(t) })
  66. require.NoError(t, ratio_setting.UpdateModelRatioByJSONString(`{"`+testPricingApplyModel+`":10}`))
  67. require.NoError(t, ratio_setting.UpdateCompletionRatioByJSONString(`{"`+testPricingApplyModel+`":3}`))
  68. require.NoError(t, ratio_setting.UpdateCacheRatioByJSONString(`{"`+testPricingApplyModel+`":0.5}`))
  69. require.NoError(t, ratio_setting.UpdateCreateCacheRatioByJSONString(`{"`+testPricingApplyModel+`":0.75}`))
  70. p := &Pricing{}
  71. applyGlobalDefault(p, testPricingApplyModel)
  72. require.Equal(t, QuotaTypeByTokens, p.QuotaType)
  73. require.Equal(t, 10.0, p.ModelRatio)
  74. require.Equal(t, 3.0, p.CompletionRatio)
  75. require.Equal(t, 0.5, p.CacheRatio)
  76. require.Equal(t, 0.75, p.CacheCreationRatio)
  77. }
  78. func TestApplyGlobalDefault_PerCallModel(t *testing.T) {
  79. resetPricingRatios(t)
  80. t.Cleanup(func() { resetPricingRatios(t) })
  81. require.NoError(t, ratio_setting.UpdateModelPriceByJSONString(`{"`+testPricingCallModel+`":0.3}`))
  82. p := &Pricing{}
  83. applyGlobalDefault(p, testPricingCallModel)
  84. require.Equal(t, QuotaTypeByCall, p.QuotaType)
  85. require.Equal(t, 0.3, p.ModelPrice)
  86. require.Equal(t, 0.0, p.ModelRatio)
  87. }
  88. func TestGetPricingByModel_UsesGlobalDefaultOnly(t *testing.T) {
  89. setupPricingTest(t)
  90. require.NoError(t, ratio_setting.UpdateModelRatioByJSONString(`{"`+testPricingGlobalModel+`":10}`))
  91. require.NoError(t, ratio_setting.UpdateCompletionRatioByJSONString(`{"`+testPricingGlobalModel+`":3}`))
  92. require.NoError(t, ratio_setting.UpdateCacheRatioByJSONString(`{"`+testPricingGlobalModel+`":0.5}`))
  93. require.NoError(t, ratio_setting.UpdateCreateCacheRatioByJSONString(`{"`+testPricingGlobalModel+`":0.75}`))
  94. require.NoError(t, (&Model{
  95. ModelName: testPricingGlobalModel,
  96. Status: 1,
  97. }).Insert())
  98. require.NoError(t, (&Vendor{
  99. Id: 1,
  100. Name: "test-vendor",
  101. SortOrder: 1,
  102. }).Insert())
  103. require.NoError(t, (&Channel{
  104. Id: 1,
  105. Type: constant.ChannelTypeOpenAI,
  106. Key: "k",
  107. Name: "channel-a",
  108. Status: common.ChannelStatusEnabled,
  109. Models: testPricingGlobalModel,
  110. Group: "default",
  111. }).Insert())
  112. RefreshPricing()
  113. pricing := GetPricingByModel(testPricingGlobalModel)
  114. require.NotNil(t, pricing)
  115. require.Equal(t, QuotaTypeByTokens, pricing.QuotaType)
  116. require.Equal(t, 10.0, pricing.ModelRatio)
  117. require.Equal(t, 3.0, pricing.CompletionRatio)
  118. require.Equal(t, 0.5, pricing.CacheRatio)
  119. require.Equal(t, 0.75, pricing.CacheCreationRatio)
  120. }
  121. func TestGetPricingByModel_IncludesMatrixPricingConfig(t *testing.T) {
  122. setupPricingTest(t)
  123. const matrixModel = "doubao-seedance-2-0-260128"
  124. require.NoError(t, ratio_setting.SetPricingConfig(matrixModel, &types.PricingConfig{
  125. SchemaVersion: types.PricingSchemaVersion,
  126. Scope: types.PricingScopeModel,
  127. BillingUnit: types.BillingUnitPer1MTokens,
  128. PreconsumeStrategy: types.PreconsumeStrategyMinimum,
  129. Dimensions: []types.PricingDimension{
  130. {Key: "resolution", Source: "request.resolution", Type: "string"},
  131. {Key: "video_input", Source: "derived.video_input", Type: "boolean"},
  132. },
  133. Table: []types.PricingRow{
  134. {"resolution": "480p", "video_input": true, "price": 28.0, "source": types.PricingRowSourceManual},
  135. {"resolution": "480p", "video_input": false, "price": 46.0, "source": types.PricingRowSourceManual},
  136. },
  137. Fallback: types.PricingFallback{Strategy: types.PricingFallbackReject},
  138. }))
  139. require.NoError(t, (&Model{
  140. ModelName: matrixModel,
  141. Status: 1,
  142. }).Insert())
  143. require.NoError(t, (&Channel{
  144. Id: 1,
  145. Type: constant.ChannelTypeDoubaoVideoCompatibleAiping,
  146. Key: "k",
  147. Name: "channel-a",
  148. Status: common.ChannelStatusEnabled,
  149. Models: matrixModel,
  150. Group: "default",
  151. }).Insert())
  152. RefreshPricing()
  153. pricing := GetPricingByModel(matrixModel)
  154. require.NotNil(t, pricing)
  155. require.NotNil(t, pricing.PricingConfig)
  156. require.Equal(t, types.BillingUnitPer1MTokens, pricing.PricingConfig.BillingUnit)
  157. require.Equal(t, QuotaTypeByTokens, pricing.QuotaType)
  158. require.Equal(t, 28.0, pricing.ModelPrice)
  159. require.Equal(t, []constant.EndpointType{constant.EndpointTypeDoubaoVideo}, pricing.SupportedEndpointTypes)
  160. }
  161. func TestGetPricingByModelIncludesDisplayPricing(t *testing.T) {
  162. setupPricingTest(t)
  163. const displayModel = "display-pricing-model"
  164. require.NoError(t, ratio_setting.UpdateModelRatioByJSONString(`{"`+displayModel+`":10}`))
  165. require.NoError(t, ratio_setting.SetModelDisplayPricing(displayModel, []types.ModelDisplayPricingItem{
  166. {Specification: "768p-6s", Price: 0.33333, Unit: "second", SortOrder: 1},
  167. }))
  168. require.NoError(t, (&Model{
  169. ModelName: displayModel,
  170. Status: 1,
  171. }).Insert())
  172. require.NoError(t, (&Channel{
  173. Id: 1,
  174. Type: constant.ChannelTypeOpenAI,
  175. Key: "k",
  176. Name: "channel-a",
  177. Status: common.ChannelStatusEnabled,
  178. Models: displayModel,
  179. Group: "default",
  180. }).Insert())
  181. RefreshPricing()
  182. pricing := GetPricingByModel(displayModel)
  183. require.NotNil(t, pricing)
  184. require.Len(t, pricing.DisplayPricing, 1)
  185. require.Equal(t, "768p-6s", pricing.DisplayPricing[0].Specification)
  186. require.Equal(t, QuotaTypeByTokens, pricing.QuotaType)
  187. require.Equal(t, 10.0, pricing.ModelRatio)
  188. require.Equal(t, 0.0, pricing.ModelPrice)
  189. require.Nil(t, pricing.PricingConfig)
  190. }
  191. func TestParseModelEndpointTypes_SupportsObjectAndArray(t *testing.T) {
  192. objectEndpoints := parseModelEndpointTypes(`{
  193. "anthropic": {"path": "/v1/messages", "method": "POST"},
  194. "openai": "/v1/chat/completions"
  195. }`)
  196. require.ElementsMatch(t, []string{"anthropic", "openai"}, objectEndpoints)
  197. arrayEndpoints := parseModelEndpointTypes(`["anthropic", "openai", "anthropic"]`)
  198. require.Equal(t, []string{"anthropic", "openai"}, arrayEndpoints)
  199. }
  200. func TestParseModelEndpointTypes_IgnoresInvalidJSON(t *testing.T) {
  201. require.Nil(t, parseModelEndpointTypes(`123`))
  202. require.Nil(t, parseModelEndpointTypes(`{"anthropic": true}`))
  203. require.Nil(t, parseModelEndpointTypes(``))
  204. info, ok := common.GetDefaultEndpointInfo("anthropic")
  205. require.True(t, ok)
  206. require.Equal(t, "/v1/messages", info.Path)
  207. }