usage.go 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  1. package model
  2. import (
  3. "fmt"
  4. "math"
  5. )
  6. type PriceCondition struct {
  7. InputTokenMin int64 `json:"input_token_min,omitempty"`
  8. InputTokenMax int64 `json:"input_token_max,omitempty"`
  9. OutputTokenMin int64 `json:"output_token_min,omitempty"`
  10. OutputTokenMax int64 `json:"output_token_max,omitempty"`
  11. }
  12. type ConditionalPrice struct {
  13. Condition PriceCondition `json:"condition"`
  14. Price Price `json:"price"`
  15. }
  16. type Price struct {
  17. PerRequestPrice ZeroNullFloat64 `json:"per_request_price,omitempty"`
  18. InputPrice ZeroNullFloat64 `json:"input_price,omitempty"`
  19. InputPriceUnit ZeroNullInt64 `json:"input_price_unit,omitempty"`
  20. ImageInputPrice ZeroNullFloat64 `json:"image_input_price,omitempty"`
  21. ImageInputPriceUnit ZeroNullInt64 `json:"image_input_price_unit,omitempty"`
  22. AudioInputPrice ZeroNullFloat64 `json:"audio_input_price,omitempty"`
  23. AudioInputPriceUnit ZeroNullInt64 `json:"audio_input_price_unit,omitempty"`
  24. OutputPrice ZeroNullFloat64 `json:"output_price,omitempty"`
  25. OutputPriceUnit ZeroNullInt64 `json:"output_price_unit,omitempty"`
  26. // when ThinkingModeOutputPrice and ReasoningTokens are not 0, OutputPrice and OutputPriceUnit
  27. // will be overwritten
  28. ThinkingModeOutputPrice ZeroNullFloat64 `json:"thinking_mode_output_price,omitempty"`
  29. ThinkingModeOutputPriceUnit ZeroNullInt64 `json:"thinking_mode_output_price_unit,omitempty"`
  30. CachedPrice ZeroNullFloat64 `json:"cached_price,omitempty"`
  31. CachedPriceUnit ZeroNullInt64 `json:"cached_price_unit,omitempty"`
  32. CacheCreationPrice ZeroNullFloat64 `json:"cache_creation_price,omitempty"`
  33. CacheCreationPriceUnit ZeroNullInt64 `json:"cache_creation_price_unit,omitempty"`
  34. WebSearchPrice ZeroNullFloat64 `json:"web_search_price,omitempty"`
  35. WebSearchPriceUnit ZeroNullInt64 `json:"web_search_price_unit,omitempty"`
  36. ConditionalPrices []ConditionalPrice `gorm:"serializer:fastjson;type:text" json:"conditional_prices,omitempty"`
  37. }
  38. func (p *Price) ValidateConditionalPrices() error {
  39. if len(p.ConditionalPrices) == 0 {
  40. return nil
  41. }
  42. for i, conditionalPrice := range p.ConditionalPrices {
  43. condition := conditionalPrice.Condition
  44. // Validate individual condition ranges
  45. if condition.InputTokenMin > 0 && condition.InputTokenMax > 0 {
  46. if condition.InputTokenMin > condition.InputTokenMax {
  47. return fmt.Errorf(
  48. "conditional price %d: input token min (%d) cannot be greater than max (%d)",
  49. i,
  50. condition.InputTokenMin,
  51. condition.InputTokenMax,
  52. )
  53. }
  54. }
  55. if condition.OutputTokenMin > 0 && condition.OutputTokenMax > 0 {
  56. if condition.OutputTokenMin > condition.OutputTokenMax {
  57. return fmt.Errorf(
  58. "conditional price %d: output token min (%d) cannot be greater than max (%d)",
  59. i,
  60. condition.OutputTokenMin,
  61. condition.OutputTokenMax,
  62. )
  63. }
  64. }
  65. // Check for overlaps with other conditions
  66. for j := i + 1; j < len(p.ConditionalPrices); j++ {
  67. otherCondition := p.ConditionalPrices[j].Condition
  68. // Check input token range overlap
  69. if hasRangeOverlap(
  70. condition.InputTokenMin, condition.InputTokenMax,
  71. otherCondition.InputTokenMin, otherCondition.InputTokenMax,
  72. ) {
  73. // If input ranges overlap, check if output ranges also overlap
  74. if hasRangeOverlap(
  75. condition.OutputTokenMin, condition.OutputTokenMax,
  76. otherCondition.OutputTokenMin, otherCondition.OutputTokenMax,
  77. ) {
  78. return fmt.Errorf(
  79. "conditional prices %d and %d have overlapping conditions",
  80. i,
  81. j,
  82. )
  83. }
  84. }
  85. }
  86. }
  87. // Check if conditions are sorted by input token ranges (optional ordering check)
  88. if err := p.validateConditionalPriceOrdering(); err != nil {
  89. return err
  90. }
  91. return nil
  92. }
  93. // hasRangeOverlap checks if two ranges overlap
  94. // Range is defined by [min, max], where 0 means unbounded
  95. func hasRangeOverlap(min1, max1, min2, max2 int64) bool {
  96. // Convert 0 to appropriate bounds for comparison
  97. actualMin1 := min1
  98. actualMax1 := max1
  99. actualMin2 := min2
  100. actualMax2 := max2
  101. if actualMin1 == 0 {
  102. actualMin1 = 0
  103. }
  104. if actualMax1 == 0 {
  105. actualMax1 = math.MaxInt64
  106. }
  107. if actualMin2 == 0 {
  108. actualMin2 = 0
  109. }
  110. if actualMax2 == 0 {
  111. actualMax2 = math.MaxInt64
  112. }
  113. // Check if ranges overlap: range1.max >= range2.min && range1.min <= range2.max
  114. return actualMax1 >= actualMin2 && actualMin1 <= actualMax2
  115. }
  116. // validateConditionalPriceOrdering checks if conditional prices are properly ordered
  117. func (p *Price) validateConditionalPriceOrdering() error {
  118. if len(p.ConditionalPrices) <= 1 {
  119. return nil
  120. }
  121. for i := range len(p.ConditionalPrices) - 1 {
  122. current := p.ConditionalPrices[i].Condition
  123. next := p.ConditionalPrices[i+1].Condition
  124. // Check if input token ranges are in ascending order
  125. // Compare the starting points of ranges
  126. currentInputMin := current.InputTokenMin
  127. nextInputMin := next.InputTokenMin
  128. // If current range starts after next range, it's improperly ordered
  129. if currentInputMin > nextInputMin {
  130. return fmt.Errorf("conditional prices %d and %d are not properly ordered: "+
  131. "current min (%d) should not be greater than next min (%d)",
  132. i, i+1, currentInputMin, nextInputMin)
  133. }
  134. // If they have the same starting point, check the ending points
  135. if currentInputMin == nextInputMin {
  136. currentInputMax := current.InputTokenMax
  137. nextInputMax := next.InputTokenMax
  138. // Handle unbounded ranges (0 means unbounded)
  139. if currentInputMax == 0 {
  140. currentInputMax = math.MaxInt64
  141. }
  142. if nextInputMax == 0 {
  143. nextInputMax = math.MaxInt64
  144. }
  145. if currentInputMax > nextInputMax {
  146. return fmt.Errorf("conditional prices %d and %d are not properly ordered: "+
  147. "ranges with same min should be ordered by max",
  148. i, i+1)
  149. }
  150. }
  151. }
  152. return nil
  153. }
  154. func (p *Price) SelectConditionalPrice(usage Usage) Price {
  155. if len(p.ConditionalPrices) == 0 {
  156. return *p
  157. }
  158. inputTokens := int64(usage.InputTokens)
  159. outputTokens := int64(usage.OutputTokens)
  160. for _, conditionalPrice := range p.ConditionalPrices {
  161. condition := conditionalPrice.Condition
  162. if condition.InputTokenMin > 0 && inputTokens < condition.InputTokenMin {
  163. continue
  164. }
  165. if condition.InputTokenMax > 0 && inputTokens > condition.InputTokenMax {
  166. continue
  167. }
  168. if condition.OutputTokenMin > 0 && outputTokens < condition.OutputTokenMin {
  169. continue
  170. }
  171. if condition.OutputTokenMax > 0 && outputTokens > condition.OutputTokenMax {
  172. continue
  173. }
  174. return conditionalPrice.Price
  175. }
  176. return *p
  177. }
  178. func (p *Price) GetInputPriceUnit() int64 {
  179. if p.InputPriceUnit > 0 {
  180. return int64(p.InputPriceUnit)
  181. }
  182. return PriceUnit
  183. }
  184. func (p *Price) GetImageInputPriceUnit() int64 {
  185. if p.ImageInputPriceUnit > 0 {
  186. return int64(p.ImageInputPriceUnit)
  187. }
  188. return PriceUnit
  189. }
  190. func (p *Price) GetAudioInputPriceUnit() int64 {
  191. if p.AudioInputPriceUnit > 0 {
  192. return int64(p.AudioInputPriceUnit)
  193. }
  194. return PriceUnit
  195. }
  196. func (p *Price) GetOutputPriceUnit() int64 {
  197. if p.OutputPriceUnit > 0 {
  198. return int64(p.OutputPriceUnit)
  199. }
  200. return PriceUnit
  201. }
  202. func (p *Price) GetCachedPriceUnit() int64 {
  203. if p.CachedPriceUnit > 0 {
  204. return int64(p.CachedPriceUnit)
  205. }
  206. return PriceUnit
  207. }
  208. func (p *Price) GetCacheCreationPriceUnit() int64 {
  209. if p.CacheCreationPriceUnit > 0 {
  210. return int64(p.CacheCreationPriceUnit)
  211. }
  212. return PriceUnit
  213. }
  214. func (p *Price) GetWebSearchPriceUnit() int64 {
  215. if p.WebSearchPriceUnit > 0 {
  216. return int64(p.WebSearchPriceUnit)
  217. }
  218. return PriceUnit
  219. }
  220. type Usage struct {
  221. InputTokens ZeroNullInt64 `json:"input_tokens,omitempty"`
  222. ImageInputTokens ZeroNullInt64 `json:"image_input_tokens,omitempty"`
  223. AudioInputTokens ZeroNullInt64 `json:"audio_input_tokens,omitempty"`
  224. OutputTokens ZeroNullInt64 `json:"output_tokens,omitempty"`
  225. CachedTokens ZeroNullInt64 `json:"cached_tokens,omitempty"`
  226. CacheCreationTokens ZeroNullInt64 `json:"cache_creation_tokens,omitempty"`
  227. ReasoningTokens ZeroNullInt64 `json:"reasoning_tokens,omitempty"`
  228. TotalTokens ZeroNullInt64 `json:"total_tokens,omitempty"`
  229. WebSearchCount ZeroNullInt64 `json:"web_search_count,omitempty"`
  230. }
  231. func (u *Usage) Add(other Usage) {
  232. u.InputTokens += other.InputTokens
  233. u.ImageInputTokens += other.ImageInputTokens
  234. u.AudioInputTokens += other.AudioInputTokens
  235. u.OutputTokens += other.OutputTokens
  236. u.CachedTokens += other.CachedTokens
  237. u.CacheCreationTokens += other.CacheCreationTokens
  238. u.TotalTokens += other.TotalTokens
  239. u.WebSearchCount += other.WebSearchCount
  240. }