log_info_generate.go 8.7 KB

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