| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358 |
- package model
- import (
- "fmt"
- "math"
- "time"
- )
- type PriceCondition struct {
- InputTokenMin int64 `json:"input_token_min,omitempty"`
- InputTokenMax int64 `json:"input_token_max,omitempty"`
- OutputTokenMin int64 `json:"output_token_min,omitempty"`
- OutputTokenMax int64 `json:"output_token_max,omitempty"`
- StartTime int64 `json:"start_time,omitempty"` // Unix timestamp, 0 means no start limit
- EndTime int64 `json:"end_time,omitempty"` // Unix timestamp, 0 means no end limit
- }
- type ConditionalPrice struct {
- Condition PriceCondition `json:"condition"`
- Price Price `json:"price"`
- }
- type Price struct {
- PerRequestPrice ZeroNullFloat64 `json:"per_request_price,omitempty"`
- InputPrice ZeroNullFloat64 `json:"input_price,omitempty"`
- InputPriceUnit ZeroNullInt64 `json:"input_price_unit,omitempty"`
- ImageInputPrice ZeroNullFloat64 `json:"image_input_price,omitempty"`
- ImageInputPriceUnit ZeroNullInt64 `json:"image_input_price_unit,omitempty"`
- AudioInputPrice ZeroNullFloat64 `json:"audio_input_price,omitempty"`
- AudioInputPriceUnit ZeroNullInt64 `json:"audio_input_price_unit,omitempty"`
- OutputPrice ZeroNullFloat64 `json:"output_price,omitempty"`
- OutputPriceUnit ZeroNullInt64 `json:"output_price_unit,omitempty"`
- // when ThinkingModeOutputPrice and ReasoningTokens are not 0, OutputPrice and OutputPriceUnit
- // will be overwritten
- ThinkingModeOutputPrice ZeroNullFloat64 `json:"thinking_mode_output_price,omitempty"`
- ThinkingModeOutputPriceUnit ZeroNullInt64 `json:"thinking_mode_output_price_unit,omitempty"`
- CachedPrice ZeroNullFloat64 `json:"cached_price,omitempty"`
- CachedPriceUnit ZeroNullInt64 `json:"cached_price_unit,omitempty"`
- CacheCreationPrice ZeroNullFloat64 `json:"cache_creation_price,omitempty"`
- CacheCreationPriceUnit ZeroNullInt64 `json:"cache_creation_price_unit,omitempty"`
- WebSearchPrice ZeroNullFloat64 `json:"web_search_price,omitempty"`
- WebSearchPriceUnit ZeroNullInt64 `json:"web_search_price_unit,omitempty"`
- ConditionalPrices []ConditionalPrice `gorm:"serializer:fastjson;type:text" json:"conditional_prices,omitempty"`
- }
- func (p *Price) ValidateConditionalPrices() error {
- if len(p.ConditionalPrices) == 0 {
- return nil
- }
- for i, conditionalPrice := range p.ConditionalPrices {
- condition := conditionalPrice.Condition
- // Validate individual condition ranges
- if condition.InputTokenMin > 0 && condition.InputTokenMax > 0 {
- if condition.InputTokenMin > condition.InputTokenMax {
- return fmt.Errorf(
- "conditional price %d: input token min (%d) cannot be greater than max (%d)",
- i,
- condition.InputTokenMin,
- condition.InputTokenMax,
- )
- }
- }
- if condition.OutputTokenMin > 0 && condition.OutputTokenMax > 0 {
- if condition.OutputTokenMin > condition.OutputTokenMax {
- return fmt.Errorf(
- "conditional price %d: output token min (%d) cannot be greater than max (%d)",
- i,
- condition.OutputTokenMin,
- condition.OutputTokenMax,
- )
- }
- }
- // Validate time range
- if condition.StartTime > 0 && condition.EndTime > 0 {
- if condition.StartTime >= condition.EndTime {
- return fmt.Errorf(
- "conditional price %d: start time (%d) must be before end time (%d)",
- i,
- condition.StartTime,
- condition.EndTime,
- )
- }
- }
- // Check for overlaps with other conditions
- for j := i + 1; j < len(p.ConditionalPrices); j++ {
- otherCondition := p.ConditionalPrices[j].Condition
- // Check input token range overlap
- if hasRangeOverlap(
- condition.InputTokenMin, condition.InputTokenMax,
- otherCondition.InputTokenMin, otherCondition.InputTokenMax,
- ) {
- // If input ranges overlap, check if output ranges also overlap
- if hasRangeOverlap(
- condition.OutputTokenMin, condition.OutputTokenMax,
- otherCondition.OutputTokenMin, otherCondition.OutputTokenMax,
- ) {
- // If both token ranges overlap, check if time ranges also overlap
- // If time ranges don't overlap, conditions are still valid
- if hasTimeRangeOverlap(
- condition.StartTime, condition.EndTime,
- otherCondition.StartTime, otherCondition.EndTime,
- ) {
- return fmt.Errorf(
- "conditional prices %d and %d have overlapping conditions",
- i,
- j,
- )
- }
- }
- }
- }
- }
- // Check if conditions are sorted by input token ranges (optional ordering check)
- if err := p.validateConditionalPriceOrdering(); err != nil {
- return err
- }
- return nil
- }
- // hasRangeOverlap checks if two ranges overlap
- // Range is defined by [min, max], where 0 means unbounded
- func hasRangeOverlap(min1, max1, min2, max2 int64) bool {
- // Convert 0 to appropriate bounds for comparison
- actualMin1 := min1
- actualMax1 := max1
- actualMin2 := min2
- actualMax2 := max2
- if actualMin1 == 0 {
- actualMin1 = 0
- }
- if actualMax1 == 0 {
- actualMax1 = math.MaxInt64
- }
- if actualMin2 == 0 {
- actualMin2 = 0
- }
- if actualMax2 == 0 {
- actualMax2 = math.MaxInt64
- }
- // Check if ranges overlap: range1.max >= range2.min && range1.min <= range2.max
- return actualMax1 >= actualMin2 && actualMin1 <= actualMax2
- }
- // hasTimeRangeOverlap checks if two time ranges overlap
- // Unlike hasRangeOverlap, this uses strict inequality to allow adjacent time ranges
- // Time range is defined by [start, end], where 0 means unbounded
- func hasTimeRangeOverlap(start1, end1, start2, end2 int64) bool {
- // Convert 0 to appropriate bounds for comparison
- actualStart1 := start1
- actualEnd1 := end1
- actualStart2 := start2
- actualEnd2 := end2
- if actualStart1 == 0 {
- actualStart1 = 0
- }
- if actualEnd1 == 0 {
- actualEnd1 = math.MaxInt64
- }
- if actualStart2 == 0 {
- actualStart2 = 0
- }
- if actualEnd2 == 0 {
- actualEnd2 = math.MaxInt64
- }
- // Check if ranges overlap with strict inequality: range1.end > range2.start && range1.start < range2.end
- // This allows adjacent ranges like [t1, t2] and [t2, t3] to be considered non-overlapping
- return actualEnd1 > actualStart2 && actualStart1 < actualEnd2
- }
- // validateConditionalPriceOrdering checks if conditional prices are properly ordered
- func (p *Price) validateConditionalPriceOrdering() error {
- if len(p.ConditionalPrices) <= 1 {
- return nil
- }
- for i := range len(p.ConditionalPrices) - 1 {
- current := p.ConditionalPrices[i].Condition
- next := p.ConditionalPrices[i+1].Condition
- // Check if input token ranges are in ascending order
- // Compare the starting points of ranges
- currentInputMin := current.InputTokenMin
- nextInputMin := next.InputTokenMin
- // If current range starts after next range, it's improperly ordered
- if currentInputMin > nextInputMin {
- return fmt.Errorf("conditional prices %d and %d are not properly ordered: "+
- "current min (%d) should not be greater than next min (%d)",
- i, i+1, currentInputMin, nextInputMin)
- }
- // If they have the same starting point, check the ending points
- if currentInputMin == nextInputMin {
- currentInputMax := current.InputTokenMax
- nextInputMax := next.InputTokenMax
- // Handle unbounded ranges (0 means unbounded)
- if currentInputMax == 0 {
- currentInputMax = math.MaxInt64
- }
- if nextInputMax == 0 {
- nextInputMax = math.MaxInt64
- }
- if currentInputMax > nextInputMax {
- return fmt.Errorf("conditional prices %d and %d are not properly ordered: "+
- "ranges with same min should be ordered by max",
- i, i+1)
- }
- }
- }
- return nil
- }
- func (p *Price) SelectConditionalPrice(usage Usage) Price {
- if len(p.ConditionalPrices) == 0 {
- return *p
- }
- inputTokens := int64(usage.InputTokens)
- outputTokens := int64(usage.OutputTokens)
- currentTime := time.Now().Unix()
- for _, conditionalPrice := range p.ConditionalPrices {
- condition := conditionalPrice.Condition
- // Check time range
- if condition.StartTime > 0 && currentTime < condition.StartTime {
- continue
- }
- if condition.EndTime > 0 && currentTime > condition.EndTime {
- continue
- }
- // Check token ranges
- if condition.InputTokenMin > 0 && inputTokens < condition.InputTokenMin {
- continue
- }
- if condition.InputTokenMax > 0 && inputTokens > condition.InputTokenMax {
- continue
- }
- if condition.OutputTokenMin > 0 && outputTokens < condition.OutputTokenMin {
- continue
- }
- if condition.OutputTokenMax > 0 && outputTokens > condition.OutputTokenMax {
- continue
- }
- return conditionalPrice.Price
- }
- return *p
- }
- func (p *Price) GetInputPriceUnit() int64 {
- if p.InputPriceUnit > 0 {
- return int64(p.InputPriceUnit)
- }
- return PriceUnit
- }
- func (p *Price) GetImageInputPriceUnit() int64 {
- if p.ImageInputPriceUnit > 0 {
- return int64(p.ImageInputPriceUnit)
- }
- return PriceUnit
- }
- func (p *Price) GetAudioInputPriceUnit() int64 {
- if p.AudioInputPriceUnit > 0 {
- return int64(p.AudioInputPriceUnit)
- }
- return PriceUnit
- }
- func (p *Price) GetOutputPriceUnit() int64 {
- if p.OutputPriceUnit > 0 {
- return int64(p.OutputPriceUnit)
- }
- return PriceUnit
- }
- func (p *Price) GetCachedPriceUnit() int64 {
- if p.CachedPriceUnit > 0 {
- return int64(p.CachedPriceUnit)
- }
- return PriceUnit
- }
- func (p *Price) GetCacheCreationPriceUnit() int64 {
- if p.CacheCreationPriceUnit > 0 {
- return int64(p.CacheCreationPriceUnit)
- }
- return PriceUnit
- }
- func (p *Price) GetWebSearchPriceUnit() int64 {
- if p.WebSearchPriceUnit > 0 {
- return int64(p.WebSearchPriceUnit)
- }
- return PriceUnit
- }
- type Usage struct {
- InputTokens ZeroNullInt64 `json:"input_tokens,omitempty"`
- ImageInputTokens ZeroNullInt64 `json:"image_input_tokens,omitempty"`
- AudioInputTokens ZeroNullInt64 `json:"audio_input_tokens,omitempty"`
- OutputTokens ZeroNullInt64 `json:"output_tokens,omitempty"`
- CachedTokens ZeroNullInt64 `json:"cached_tokens,omitempty"`
- CacheCreationTokens ZeroNullInt64 `json:"cache_creation_tokens,omitempty"`
- ReasoningTokens ZeroNullInt64 `json:"reasoning_tokens,omitempty"`
- TotalTokens ZeroNullInt64 `json:"total_tokens,omitempty"`
- WebSearchCount ZeroNullInt64 `json:"web_search_count,omitempty"`
- }
- func (u *Usage) Add(other Usage) {
- u.InputTokens += other.InputTokens
- u.ImageInputTokens += other.ImageInputTokens
- u.AudioInputTokens += other.AudioInputTokens
- u.OutputTokens += other.OutputTokens
- u.CachedTokens += other.CachedTokens
- u.CacheCreationTokens += other.CacheCreationTokens
- u.TotalTokens += other.TotalTokens
- u.WebSearchCount += other.WebSearchCount
- }
|