log_info_generate.go 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. package service
  2. import (
  3. "strings"
  4. "github.com/QuantumNous/new-api/common"
  5. "github.com/QuantumNous/new-api/constant"
  6. "github.com/QuantumNous/new-api/dto"
  7. "github.com/QuantumNous/new-api/pkg/billingexpr"
  8. relaycommon "github.com/QuantumNous/new-api/relay/common"
  9. "github.com/QuantumNous/new-api/types"
  10. "github.com/gin-gonic/gin"
  11. )
  12. func appendRequestPath(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, other map[string]interface{}) {
  13. if other == nil {
  14. return
  15. }
  16. if ctx != nil && ctx.Request != nil && ctx.Request.URL != nil {
  17. if path := ctx.Request.URL.Path; path != "" {
  18. other["request_path"] = path
  19. return
  20. }
  21. }
  22. if relayInfo != nil && relayInfo.RequestURLPath != "" {
  23. path := relayInfo.RequestURLPath
  24. if idx := strings.Index(path, "?"); idx != -1 {
  25. path = path[:idx]
  26. }
  27. other["request_path"] = path
  28. }
  29. }
  30. func GenerateTextOtherInfo(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, modelRatio, groupRatio, completionRatio float64,
  31. cacheTokens int, cacheRatio float64, modelPrice float64, userGroupRatio float64) map[string]interface{} {
  32. other := make(map[string]interface{})
  33. other["model_ratio"] = modelRatio
  34. other["group_ratio"] = groupRatio
  35. other["completion_ratio"] = completionRatio
  36. other["cache_tokens"] = cacheTokens
  37. other["cache_ratio"] = cacheRatio
  38. other["model_price"] = modelPrice
  39. other["user_group_ratio"] = userGroupRatio
  40. other["frt"] = float64(relayInfo.FirstResponseTime.UnixMilli() - relayInfo.StartTime.UnixMilli())
  41. if relayInfo.ReasoningEffort != "" {
  42. other["reasoning_effort"] = relayInfo.ReasoningEffort
  43. }
  44. if relayInfo.IsModelMapped {
  45. other["is_model_mapped"] = true
  46. other["upstream_model_name"] = relayInfo.UpstreamModelName
  47. }
  48. isSystemPromptOverwritten := common.GetContextKeyBool(ctx, constant.ContextKeySystemPromptOverride)
  49. if isSystemPromptOverwritten {
  50. other["is_system_prompt_overwritten"] = true
  51. }
  52. adminInfo := make(map[string]interface{})
  53. adminInfo["use_channel"] = ctx.GetStringSlice("use_channel")
  54. isMultiKey := common.GetContextKeyBool(ctx, constant.ContextKeyChannelIsMultiKey)
  55. if isMultiKey {
  56. adminInfo["is_multi_key"] = true
  57. adminInfo["multi_key_index"] = common.GetContextKeyInt(ctx, constant.ContextKeyChannelMultiKeyIndex)
  58. }
  59. isLocalCountTokens := common.GetContextKeyBool(ctx, constant.ContextKeyLocalCountTokens)
  60. if isLocalCountTokens {
  61. adminInfo["local_count_tokens"] = isLocalCountTokens
  62. }
  63. AppendChannelAffinityAdminInfo(ctx, adminInfo)
  64. other["admin_info"] = adminInfo
  65. appendRequestPath(ctx, relayInfo, other)
  66. appendRequestConversionChain(relayInfo, other)
  67. appendBillingInfo(relayInfo, other)
  68. return other
  69. }
  70. func appendBillingInfo(relayInfo *relaycommon.RelayInfo, other map[string]interface{}) {
  71. if relayInfo == nil || other == nil {
  72. return
  73. }
  74. // billing_source: "wallet" or "subscription"
  75. if relayInfo.BillingSource != "" {
  76. other["billing_source"] = relayInfo.BillingSource
  77. }
  78. if relayInfo.UserSetting.BillingPreference != "" {
  79. other["billing_preference"] = relayInfo.UserSetting.BillingPreference
  80. }
  81. if relayInfo.BillingSource == "subscription" {
  82. if relayInfo.SubscriptionId != 0 {
  83. other["subscription_id"] = relayInfo.SubscriptionId
  84. }
  85. if relayInfo.SubscriptionPreConsumed > 0 {
  86. other["subscription_pre_consumed"] = relayInfo.SubscriptionPreConsumed
  87. }
  88. // post_delta: settlement delta applied after actual usage is known (can be negative for refund)
  89. if relayInfo.SubscriptionPostDelta != 0 {
  90. other["subscription_post_delta"] = relayInfo.SubscriptionPostDelta
  91. }
  92. if relayInfo.SubscriptionPlanId != 0 {
  93. other["subscription_plan_id"] = relayInfo.SubscriptionPlanId
  94. }
  95. if relayInfo.SubscriptionPlanTitle != "" {
  96. other["subscription_plan_title"] = relayInfo.SubscriptionPlanTitle
  97. }
  98. // Compute "this request" subscription consumed + remaining
  99. consumed := relayInfo.SubscriptionPreConsumed + relayInfo.SubscriptionPostDelta
  100. usedFinal := relayInfo.SubscriptionAmountUsedAfterPreConsume + relayInfo.SubscriptionPostDelta
  101. if consumed < 0 {
  102. consumed = 0
  103. }
  104. if usedFinal < 0 {
  105. usedFinal = 0
  106. }
  107. if relayInfo.SubscriptionAmountTotal > 0 {
  108. remain := relayInfo.SubscriptionAmountTotal - usedFinal
  109. if remain < 0 {
  110. remain = 0
  111. }
  112. other["subscription_total"] = relayInfo.SubscriptionAmountTotal
  113. other["subscription_used"] = usedFinal
  114. other["subscription_remain"] = remain
  115. }
  116. if consumed > 0 {
  117. other["subscription_consumed"] = consumed
  118. }
  119. // Wallet quota is not deducted when billed from subscription.
  120. other["wallet_quota_deducted"] = 0
  121. }
  122. }
  123. func appendRequestConversionChain(relayInfo *relaycommon.RelayInfo, other map[string]interface{}) {
  124. if relayInfo == nil || other == nil {
  125. return
  126. }
  127. if len(relayInfo.RequestConversionChain) == 0 {
  128. return
  129. }
  130. chain := make([]string, 0, len(relayInfo.RequestConversionChain))
  131. for _, f := range relayInfo.RequestConversionChain {
  132. switch f {
  133. case types.RelayFormatOpenAI:
  134. chain = append(chain, "OpenAI Compatible")
  135. case types.RelayFormatClaude:
  136. chain = append(chain, "Claude Messages")
  137. case types.RelayFormatGemini:
  138. chain = append(chain, "Google Gemini")
  139. case types.RelayFormatOpenAIResponses:
  140. chain = append(chain, "OpenAI Responses")
  141. default:
  142. chain = append(chain, string(f))
  143. }
  144. }
  145. if len(chain) == 0 {
  146. return
  147. }
  148. other["request_conversion"] = chain
  149. }
  150. func GenerateWssOtherInfo(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, usage *dto.RealtimeUsage, modelRatio, groupRatio, completionRatio, audioRatio, audioCompletionRatio, modelPrice, userGroupRatio float64) map[string]interface{} {
  151. info := GenerateTextOtherInfo(ctx, relayInfo, modelRatio, groupRatio, completionRatio, 0, 0.0, modelPrice, userGroupRatio)
  152. info["ws"] = true
  153. info["audio_input"] = usage.InputTokenDetails.AudioTokens
  154. info["audio_output"] = usage.OutputTokenDetails.AudioTokens
  155. info["text_input"] = usage.InputTokenDetails.TextTokens
  156. info["text_output"] = usage.OutputTokenDetails.TextTokens
  157. info["audio_ratio"] = audioRatio
  158. info["audio_completion_ratio"] = audioCompletionRatio
  159. return info
  160. }
  161. func GenerateAudioOtherInfo(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, usage *dto.Usage, modelRatio, groupRatio, completionRatio, audioRatio, audioCompletionRatio, modelPrice, userGroupRatio float64) map[string]interface{} {
  162. info := GenerateTextOtherInfo(ctx, relayInfo, modelRatio, groupRatio, completionRatio, 0, 0.0, modelPrice, userGroupRatio)
  163. info["audio"] = true
  164. info["audio_input"] = usage.PromptTokensDetails.AudioTokens
  165. info["audio_output"] = usage.CompletionTokenDetails.AudioTokens
  166. info["text_input"] = usage.PromptTokensDetails.TextTokens
  167. info["text_output"] = usage.CompletionTokenDetails.TextTokens
  168. info["audio_ratio"] = audioRatio
  169. info["audio_completion_ratio"] = audioCompletionRatio
  170. return info
  171. }
  172. func GenerateClaudeOtherInfo(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, modelRatio, groupRatio, completionRatio float64,
  173. cacheTokens int, cacheRatio float64,
  174. cacheCreationTokens int, cacheCreationRatio float64,
  175. cacheCreationTokens5m int, cacheCreationRatio5m float64,
  176. cacheCreationTokens1h int, cacheCreationRatio1h float64,
  177. modelPrice float64, userGroupRatio float64) map[string]interface{} {
  178. info := GenerateTextOtherInfo(ctx, relayInfo, modelRatio, groupRatio, completionRatio, cacheTokens, cacheRatio, modelPrice, userGroupRatio)
  179. info["claude"] = true
  180. info["cache_creation_tokens"] = cacheCreationTokens
  181. info["cache_creation_ratio"] = cacheCreationRatio
  182. if cacheCreationTokens5m != 0 {
  183. info["cache_creation_tokens_5m"] = cacheCreationTokens5m
  184. info["cache_creation_ratio_5m"] = cacheCreationRatio5m
  185. }
  186. if cacheCreationTokens1h != 0 {
  187. info["cache_creation_tokens_1h"] = cacheCreationTokens1h
  188. info["cache_creation_ratio_1h"] = cacheCreationRatio1h
  189. }
  190. return info
  191. }
  192. func GenerateMjOtherInfo(relayInfo *relaycommon.RelayInfo, priceData types.PriceData) map[string]interface{} {
  193. other := make(map[string]interface{})
  194. other["model_price"] = priceData.ModelPrice
  195. other["group_ratio"] = priceData.GroupRatioInfo.GroupRatio
  196. if priceData.GroupRatioInfo.HasSpecialRatio {
  197. other["user_group_ratio"] = priceData.GroupRatioInfo.GroupSpecialRatio
  198. }
  199. appendRequestPath(nil, relayInfo, other)
  200. return other
  201. }
  202. func GenerateTieredOtherInfo(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, result *billingexpr.TieredResult) map[string]interface{} {
  203. other := make(map[string]interface{})
  204. other["billing_mode"] = "tiered_expr"
  205. snap := relayInfo.TieredBillingSnapshot
  206. if snap != nil {
  207. other["group_ratio"] = snap.GroupRatio
  208. other["expr_hash"] = snap.ExprHash
  209. other["estimated_prompt_tokens"] = snap.EstimatedPromptTokens
  210. other["estimated_completion_tokens"] = snap.EstimatedCompletionTokens
  211. other["estimated_quota_before_group"] = snap.EstimatedQuotaBeforeGroup
  212. other["estimated_quota_after_group"] = snap.EstimatedQuotaAfterGroup
  213. other["estimated_tier"] = snap.EstimatedTier
  214. }
  215. if result != nil {
  216. other["actual_quota_before_group"] = result.ActualQuotaBeforeGroup
  217. other["actual_quota_after_group"] = result.ActualQuotaAfterGroup
  218. other["matched_tier"] = result.MatchedTier
  219. other["crossed_tier"] = result.CrossedTier
  220. }
  221. other["frt"] = float64(relayInfo.FirstResponseTime.UnixMilli() - relayInfo.StartTime.UnixMilli())
  222. if relayInfo.IsModelMapped {
  223. other["is_model_mapped"] = true
  224. other["upstream_model_name"] = relayInfo.UpstreamModelName
  225. }
  226. adminInfo := make(map[string]interface{})
  227. adminInfo["use_channel"] = ctx.GetStringSlice("use_channel")
  228. AppendChannelAffinityAdminInfo(ctx, adminInfo)
  229. other["admin_info"] = adminInfo
  230. appendRequestPath(ctx, relayInfo, other)
  231. appendRequestConversionChain(relayInfo, other)
  232. appendBillingInfo(relayInfo, other)
  233. return other
  234. }