usage.go 7.9 KB

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