usage.go 10 KB

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