You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

298 regels
9.6 KiB

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