Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.
 
 
 

266 rader
8.8 KiB

  1. package helper
  2. import (
  3. "fmt"
  4. "github.com/QuantumNous/new-api/common"
  5. "github.com/QuantumNous/new-api/constant"
  6. "github.com/QuantumNous/new-api/logger"
  7. "github.com/QuantumNous/new-api/model"
  8. relaycommon "github.com/QuantumNous/new-api/relay/common"
  9. "github.com/QuantumNous/new-api/setting/operation_setting"
  10. "github.com/QuantumNous/new-api/setting/ratio_setting"
  11. "github.com/QuantumNous/new-api/types"
  12. "github.com/gin-gonic/gin"
  13. )
  14. // HandleGroupRatio checks for "auto_group" in the context and updates the group ratio and relayInfo.UsingGroup if present
  15. func HandleGroupRatio(ctx *gin.Context, relayInfo *relaycommon.RelayInfo) types.GroupRatioInfo {
  16. groupRatioInfo := types.GroupRatioInfo{
  17. GroupRatio: 1.0, // default ratio
  18. GroupSpecialRatio: -1,
  19. }
  20. // check auto group
  21. autoGroup, exists := ctx.Get("auto_group")
  22. if exists {
  23. logger.LogDebug(ctx, fmt.Sprintf("final group: %s", autoGroup))
  24. relayInfo.UsingGroup = autoGroup.(string)
  25. }
  26. // check user group special ratio
  27. userGroupRatio, ok := ratio_setting.GetGroupGroupRatio(relayInfo.UserGroup, relayInfo.UsingGroup)
  28. if ok {
  29. // user group special ratio
  30. groupRatioInfo.GroupSpecialRatio = userGroupRatio
  31. groupRatioInfo.GroupRatio = userGroupRatio
  32. groupRatioInfo.HasSpecialRatio = true
  33. } else {
  34. // normal group ratio
  35. groupRatioInfo.GroupRatio = ratio_setting.GetGroupRatio(relayInfo.UsingGroup)
  36. }
  37. return groupRatioInfo
  38. }
  39. func ModelPriceHelper(c *gin.Context, info *relaycommon.RelayInfo, promptTokens int, meta *types.TokenCountMeta) (types.PriceData, error) {
  40. var modelPrice float64
  41. var usePrice bool
  42. var modelRatio float64
  43. var completionRatio float64
  44. var channelPricingFound bool
  45. var cacheRatio, cacheCreationRatio, imageRatio, audioRatio, audioCompletionRatio float64
  46. // 尝试获取渠道定价(优先于全局定价)
  47. // ChannelMeta 在 InitChannelMeta 之前为 nil,但 Distribute 已将 channelId 写入 context
  48. channelId := 0
  49. if info != nil && info.ChannelMeta != nil && info.ChannelId > 0 {
  50. channelId = info.ChannelId
  51. } else {
  52. channelId = common.GetContextKeyInt(c, constant.ContextKeyChannelId)
  53. }
  54. if channelId > 0 {
  55. cp, found := model.GetEffectivePricing(info.OriginModelName, channelId)
  56. if found && cp != nil {
  57. modelRatio = cp.ModelRatio
  58. completionRatio = cp.CompletionRatio
  59. modelPrice = cp.ModelPrice
  60. usePrice = cp.QuotaType == model.QuotaTypeByCall
  61. channelPricingFound = true
  62. // 渠道定价的扩展比率(非零值直接使用)
  63. cacheRatio = cp.CacheRatio
  64. cacheCreationRatio = cp.CacheCreationRatio
  65. imageRatio = cp.ImageRatio
  66. audioRatio = cp.AudioRatio
  67. audioCompletionRatio = cp.AudioCompletionRatio
  68. }
  69. }
  70. // 如果没有渠道定价,使用全局定价
  71. if !channelPricingFound {
  72. modelPrice, usePrice = ratio_setting.GetModelPrice(info.OriginModelName, false)
  73. }
  74. groupRatioInfo := HandleGroupRatio(c, info)
  75. var preConsumedQuota int
  76. var freeModel bool
  77. if !usePrice {
  78. preConsumedTokens := common.Max(promptTokens, common.PreConsumedQuota)
  79. if meta.MaxTokens != 0 {
  80. preConsumedTokens += meta.MaxTokens
  81. }
  82. // 如果没有找到渠道定价,获取全局 modelRatio 和 completionRatio
  83. if !channelPricingFound {
  84. var success bool
  85. var matchName string
  86. modelRatio, success, matchName = ratio_setting.GetModelRatio(info.OriginModelName)
  87. if !success {
  88. acceptUnsetRatio := false
  89. if info.UserSetting.AcceptUnsetRatioModel {
  90. acceptUnsetRatio = true
  91. }
  92. if !acceptUnsetRatio {
  93. return types.PriceData{}, fmt.Errorf("模型 %s 倍率或价格未配置,请联系管理员设置或开启自用模式;Model %s ratio or price not set, please set or start self-use mode", matchName, matchName)
  94. }
  95. }
  96. completionRatio = ratio_setting.GetCompletionRatio(info.OriginModelName)
  97. }
  98. ratio := modelRatio * groupRatioInfo.GroupRatio
  99. preConsumedQuota = int(float64(preConsumedTokens) * ratio)
  100. } else {
  101. if meta.ImagePriceRatio != 0 {
  102. modelPrice = modelPrice * meta.ImagePriceRatio
  103. }
  104. preConsumedQuota = int(modelPrice * common.QuotaPerUnit * groupRatioInfo.GroupRatio)
  105. }
  106. // 全局比率作为回退(仅当渠道未设置、值为 0 时生效)
  107. // 必须放在 usePrice 判断之外,因为 UpdatePriceDataForChannelPricing 可能改变 UsePrice
  108. if cacheRatio == 0 {
  109. cacheRatio, _ = ratio_setting.GetCacheRatio(info.OriginModelName)
  110. }
  111. if cacheCreationRatio == 0 {
  112. cacheCreationRatio, _ = ratio_setting.GetCreateCacheRatio(info.OriginModelName)
  113. }
  114. if imageRatio == 0 {
  115. imageRatio, _ = ratio_setting.GetImageRatio(info.OriginModelName)
  116. }
  117. if audioRatio == 0 {
  118. audioRatio = ratio_setting.GetAudioRatio(info.OriginModelName)
  119. }
  120. if audioCompletionRatio == 0 {
  121. audioCompletionRatio = ratio_setting.GetAudioCompletionRatio(info.OriginModelName)
  122. }
  123. // check if free model pre-consume is disabled
  124. if !operation_setting.GetQuotaSetting().EnableFreeModelPreConsume {
  125. // if model price or ratio is 0, do not pre-consume quota
  126. if groupRatioInfo.GroupRatio == 0 {
  127. preConsumedQuota = 0
  128. freeModel = true
  129. } else if usePrice {
  130. if modelPrice == 0 {
  131. preConsumedQuota = 0
  132. freeModel = true
  133. }
  134. } else {
  135. if modelRatio == 0 {
  136. preConsumedQuota = 0
  137. freeModel = true
  138. }
  139. }
  140. }
  141. priceData := types.PriceData{
  142. FreeModel: freeModel,
  143. ModelPrice: modelPrice,
  144. ModelRatio: modelRatio,
  145. CompletionRatio: completionRatio,
  146. GroupRatioInfo: groupRatioInfo,
  147. UsePrice: usePrice,
  148. QuotaToPreConsume: preConsumedQuota,
  149. CacheRatio: cacheRatio,
  150. ImageRatio: imageRatio,
  151. AudioRatio: audioRatio,
  152. AudioCompletionRatio: audioCompletionRatio,
  153. CacheCreationRatio: cacheCreationRatio,
  154. CacheCreation5mRatio: cacheCreationRatio,
  155. CacheCreation1hRatio: cacheCreationRatio * types.ClaudeCacheCreation1hMultiplier,
  156. }
  157. if common.DebugEnabled {
  158. println(fmt.Sprintf("model_price_helper result: %s", priceData.ToSetting()))
  159. }
  160. info.PriceData = priceData
  161. return priceData, nil
  162. }
  163. // ModelPriceHelperPerCall 按次计费的 PriceHelper (MJ、Task)
  164. func ModelPriceHelperPerCall(c *gin.Context, info *relaycommon.RelayInfo) types.PriceData {
  165. groupRatioInfo := HandleGroupRatio(c, info)
  166. modelPrice, success := ratio_setting.GetModelPrice(info.OriginModelName, true)
  167. // 如果没有配置价格,则使用默认价格
  168. if !success {
  169. defaultPrice, ok := ratio_setting.GetDefaultModelPriceMap()[info.OriginModelName]
  170. if !ok {
  171. modelPrice = 0.1
  172. } else {
  173. modelPrice = defaultPrice
  174. }
  175. }
  176. quota := int(modelPrice * common.QuotaPerUnit * groupRatioInfo.GroupRatio)
  177. // 免费模型检测(与 ModelPriceHelper 对齐)
  178. freeModel := false
  179. if !operation_setting.GetQuotaSetting().EnableFreeModelPreConsume {
  180. if groupRatioInfo.GroupRatio == 0 || modelPrice == 0 {
  181. quota = 0
  182. freeModel = true
  183. }
  184. }
  185. priceData := types.PriceData{
  186. FreeModel: freeModel,
  187. ModelPrice: modelPrice,
  188. Quota: quota,
  189. GroupRatioInfo: groupRatioInfo,
  190. }
  191. return priceData
  192. }
  193. func ContainPriceOrRatio(modelName string) bool {
  194. _, ok := ratio_setting.GetModelPrice(modelName, false)
  195. if ok {
  196. return true
  197. }
  198. _, ok, _ = ratio_setting.GetModelRatio(modelName)
  199. if ok {
  200. return true
  201. }
  202. return false
  203. }
  204. // UpdatePriceDataForChannelPricing 在渠道选择后更新 PriceData 以使用渠道定价
  205. // ModelPriceHelper 在渠道选择之前被调用,此时 ChannelMeta 为 nil,导致渠道定价无法使用。
  206. // 需要在渠道选择后调用此函数更新价格数据,用于后续的日志记录和计费计算。
  207. func UpdatePriceDataForChannelPricing(c *gin.Context, info *relaycommon.RelayInfo, channelId int) {
  208. if info == nil || channelId <= 0 {
  209. return
  210. }
  211. cp, found := model.GetEffectivePricing(info.OriginModelName, channelId)
  212. if !found {
  213. return
  214. }
  215. info.PriceData.ModelRatio = cp.ModelRatio
  216. info.PriceData.CompletionRatio = cp.CompletionRatio
  217. info.PriceData.UsePrice = cp.QuotaType == model.QuotaTypeByCall
  218. if info.PriceData.UsePrice {
  219. info.PriceData.ModelPrice = cp.ModelPrice
  220. } else {
  221. info.PriceData.ModelPrice = -1
  222. }
  223. info.PriceData.ApplyChannelPricingRatios(cp.CacheRatio, cp.CacheCreationRatio, cp.ImageRatio, cp.AudioRatio, cp.AudioCompletionRatio)
  224. if info.PriceData.UsePrice {
  225. info.PriceData.QuotaToPreConsume = int(
  226. cp.ModelPrice * common.QuotaPerUnit * info.PriceData.GroupRatioInfo.GroupRatio)
  227. } else {
  228. estimateTokens := info.GetEstimatePromptTokens()
  229. if estimateTokens > 0 {
  230. ratio := cp.ModelRatio * info.PriceData.GroupRatioInfo.GroupRatio
  231. info.PriceData.QuotaToPreConsume = int(float64(estimateTokens) * ratio)
  232. }
  233. }
  234. if common.DebugEnabled {
  235. println(fmt.Sprintf("[ChannelPricing] updatePriceData: model=%s channel=%d modelRatio=%.4f completionRatio=%.4f cacheRatio=%.4f imageRatio=%.4f audioRatio=%.4f",
  236. info.OriginModelName, channelId, cp.ModelRatio, cp.CompletionRatio, cp.CacheRatio, cp.ImageRatio, cp.AudioRatio))
  237. }
  238. }