usage_log.go 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. package service
  2. import (
  3. "fmt"
  4. "strings"
  5. "time"
  6. )
  7. const (
  8. BillingTypeBalance int8 = 0 // 钱包余额
  9. BillingTypeSubscription int8 = 1 // 订阅套餐
  10. )
  11. type RequestType int16
  12. const (
  13. RequestTypeUnknown RequestType = 0
  14. RequestTypeSync RequestType = 1
  15. RequestTypeStream RequestType = 2
  16. RequestTypeWSV2 RequestType = 3
  17. RequestTypeCyberBlocked RequestType = 4 // cyber_policy 命中(透传但被上游安全策略拒绝)
  18. RequestTypeLive RequestType = 5
  19. )
  20. func (t RequestType) IsValid() bool {
  21. switch t {
  22. case RequestTypeUnknown, RequestTypeSync, RequestTypeStream, RequestTypeWSV2, RequestTypeCyberBlocked, RequestTypeLive:
  23. return true
  24. default:
  25. return false
  26. }
  27. }
  28. func (t RequestType) Normalize() RequestType {
  29. if t.IsValid() {
  30. return t
  31. }
  32. return RequestTypeUnknown
  33. }
  34. func (t RequestType) String() string {
  35. switch t.Normalize() {
  36. case RequestTypeSync:
  37. return "sync"
  38. case RequestTypeStream:
  39. return "stream"
  40. case RequestTypeWSV2:
  41. return "ws_v2"
  42. case RequestTypeCyberBlocked:
  43. return "cyber"
  44. case RequestTypeLive:
  45. return "live"
  46. default:
  47. return "unknown"
  48. }
  49. }
  50. func RequestTypeFromInt16(v int16) RequestType {
  51. return RequestType(v).Normalize()
  52. }
  53. func ParseUsageRequestType(value string) (RequestType, error) {
  54. switch strings.ToLower(strings.TrimSpace(value)) {
  55. case "unknown":
  56. return RequestTypeUnknown, nil
  57. case "sync":
  58. return RequestTypeSync, nil
  59. case "stream":
  60. return RequestTypeStream, nil
  61. case "ws_v2":
  62. return RequestTypeWSV2, nil
  63. case "cyber":
  64. return RequestTypeCyberBlocked, nil
  65. case "live":
  66. return RequestTypeLive, nil
  67. default:
  68. return RequestTypeUnknown, fmt.Errorf("invalid request_type, allowed values: unknown, sync, stream, ws_v2, cyber, live")
  69. }
  70. }
  71. func RequestTypeFromLegacy(stream bool, openAIWSMode bool) RequestType {
  72. if openAIWSMode {
  73. return RequestTypeWSV2
  74. }
  75. if stream {
  76. return RequestTypeStream
  77. }
  78. return RequestTypeSync
  79. }
  80. func ApplyLegacyRequestFields(requestType RequestType, fallbackStream bool, fallbackOpenAIWSMode bool) (stream bool, openAIWSMode bool) {
  81. switch requestType.Normalize() {
  82. case RequestTypeSync:
  83. return false, false
  84. case RequestTypeStream:
  85. return true, false
  86. case RequestTypeWSV2:
  87. return true, true
  88. default:
  89. return fallbackStream, fallbackOpenAIWSMode
  90. }
  91. }
  92. type UsageLog struct {
  93. ID int64
  94. UserID int64
  95. APIKeyID int64
  96. AccountID int64
  97. RequestID string
  98. Model string
  99. // RequestedModel is the client-requested model name recorded for stable user/admin display.
  100. // Empty should be treated as Model for backward compatibility with historical rows.
  101. RequestedModel string
  102. // UpstreamModel is the actual model sent to the upstream provider after mapping.
  103. // Nil means no mapping was applied (requested model was used as-is).
  104. UpstreamModel *string
  105. // UpstreamResponseModel is the model declared by the successful upstream
  106. // response before client-facing model rewrites or protocol conversion.
  107. UpstreamResponseModel *string
  108. // UpstreamModelMismatch is nil when no upstream model was observed. Otherwise
  109. // it compares UpstreamResponseModel with the actual model sent upstream.
  110. UpstreamModelMismatch *bool
  111. // ChannelID 渠道 ID
  112. ChannelID *int64
  113. // ModelMappingChain 模型映射链,如 "a→b→c"
  114. ModelMappingChain *string
  115. // BillingTier 计费层级标签(per_request/image 模式)
  116. BillingTier *string
  117. // BillingMode 计费模式:token/image
  118. BillingMode *string
  119. // ServiceTier records the billable request tier, e.g. OpenAI "priority" / "flex"
  120. // or Anthropic "fast".
  121. ServiceTier *string
  122. // ReasoningEffort is the effective effort recorded for this request after
  123. // group policy rewriting and model-family remapping (e.g. max -> xhigh).
  124. // OpenAI: "low" / "medium" / "high" / "xhigh"; Claude: "low" / "medium" / "high" / "max".
  125. // Nil means not provided / not applicable.
  126. ReasoningEffort *string
  127. // RequestedReasoningEffort is the client-requested effort before mapping.
  128. // Nil means historical rows, or that no explicit/suffix-derived effort was observed.
  129. RequestedReasoningEffort *string
  130. // InboundEndpoint is the client-facing API endpoint path, e.g. /v1/chat/completions.
  131. InboundEndpoint *string
  132. // UpstreamEndpoint is the normalized upstream endpoint path, e.g. /v1/responses.
  133. UpstreamEndpoint *string
  134. GroupID *int64
  135. SubscriptionID *int64
  136. InputTokens int
  137. OutputTokens int
  138. CacheCreationTokens int
  139. CacheReadTokens int
  140. CacheCreation5mTokens int `gorm:"column:cache_creation_5m_tokens"`
  141. CacheCreation1hTokens int `gorm:"column:cache_creation_1h_tokens"`
  142. ImageInputTokens int
  143. ImageInputCost float64
  144. ImageOutputTokens int
  145. ImageOutputCost float64
  146. InputCost float64
  147. OutputCost float64
  148. CacheCreationCost float64
  149. CacheReadCost float64
  150. TotalCost float64
  151. ActualCost float64
  152. RateMultiplier float64
  153. LongContextBillingApplied bool
  154. // AccountRateMultiplier 账号计费倍率快照(nil 表示历史数据,按 1.0 处理)
  155. AccountRateMultiplier *float64
  156. // AccountStatsCost 账号统计定价预计算费用(nil = 使用默认公式 total_cost × account_rate_multiplier)
  157. AccountStatsCost *float64
  158. BillingType int8
  159. RequestType RequestType
  160. Stream bool
  161. OpenAIWSMode bool
  162. NativeCompactionV2 bool
  163. DurationMs *int
  164. FirstTokenMs *int
  165. UserAgent *string
  166. IPAddress *string
  167. // SessionID is the explicit client-provided request correlation identifier
  168. // (e.g. the session_id / X-Session-Id headers). Nil when the client sent no
  169. // valid session header. It is never derived from prompt_cache_key or content.
  170. SessionID *string
  171. // UpstreamRequestID 是直接上游在响应头中声明的请求标识,只读账户
  172. // extra.upstream_request_id_header 指定的头;账户未指定头名、WS 轮次
  173. // 与上游没有该头的路径为 nil。
  174. UpstreamRequestID *string
  175. // Cache TTL Override 标记(管理员强制替换了缓存 TTL 计费)
  176. CacheTTLOverridden bool
  177. // 图片生成字段
  178. ImageCount int
  179. ImageSize *string
  180. ImageInputSize *string
  181. ImageOutputSize *string
  182. ImageSizeSource *string
  183. ImageSizeBreakdown map[string]int
  184. MediaType *string
  185. // 视频生成字段(Grok 视频按秒计费;video_count>0 的行不要求 image_size)
  186. VideoCount int
  187. VideoResolution *string
  188. VideoDurationSeconds *int
  189. CreatedAt time.Time
  190. User *User
  191. APIKey *APIKey
  192. Account *Account
  193. Group *Group
  194. Subscription *UserSubscription
  195. }
  196. func (u *UsageLog) TotalTokens() int {
  197. return u.InputTokens + u.OutputTokens + u.CacheCreationTokens + u.CacheReadTokens
  198. }
  199. func (u *UsageLog) EffectiveRequestType() RequestType {
  200. if u == nil {
  201. return RequestTypeUnknown
  202. }
  203. if normalized := u.RequestType.Normalize(); normalized != RequestTypeUnknown {
  204. return normalized
  205. }
  206. return RequestTypeFromLegacy(u.Stream, u.OpenAIWSMode)
  207. }
  208. func (u *UsageLog) SyncRequestTypeAndLegacyFields() {
  209. if u == nil {
  210. return
  211. }
  212. requestType := u.EffectiveRequestType()
  213. u.RequestType = requestType
  214. u.Stream, u.OpenAIWSMode = ApplyLegacyRequestFields(requestType, u.Stream, u.OpenAIWSMode)
  215. }