fix: settle Responses cached token usage (#6892)
This commit is contained in:
@@ -113,6 +113,29 @@ func usageFromOpenAIBillingUsage(billingUsage *dto.BillingUsage) *dto.Usage {
|
||||
if usage.TotalTokens == 0 {
|
||||
usage.TotalTokens = usage.PromptTokens + usage.CompletionTokens
|
||||
}
|
||||
if inputDetails := usage.InputTokensDetails; inputDetails != nil {
|
||||
if usage.PromptTokensDetails.CachedTokens == 0 && inputDetails.CachedTokens > 0 {
|
||||
usage.PromptTokensDetails.CachedTokens = inputDetails.CachedTokens
|
||||
}
|
||||
if usage.PromptTokensDetails.CachedCreationTokens == 0 && inputDetails.CachedCreationTokens > 0 {
|
||||
usage.PromptTokensDetails.CachedCreationTokens = inputDetails.CachedCreationTokens
|
||||
}
|
||||
if usage.PromptTokensDetails.CacheWriteTokens == 0 && inputDetails.CacheWriteTokens > 0 {
|
||||
usage.PromptTokensDetails.CacheWriteTokens = inputDetails.CacheWriteTokens
|
||||
}
|
||||
if usage.PromptTokensDetails.TextTokens == 0 && inputDetails.TextTokens > 0 {
|
||||
usage.PromptTokensDetails.TextTokens = inputDetails.TextTokens
|
||||
}
|
||||
if usage.PromptTokensDetails.ImageTokens == 0 && inputDetails.ImageTokens > 0 {
|
||||
usage.PromptTokensDetails.ImageTokens = inputDetails.ImageTokens
|
||||
}
|
||||
if usage.PromptTokensDetails.AudioTokens == 0 && inputDetails.AudioTokens > 0 {
|
||||
usage.PromptTokensDetails.AudioTokens = inputDetails.AudioTokens
|
||||
}
|
||||
}
|
||||
if usage.PromptTokensDetails.CachedTokens == 0 && usage.PromptCacheHitTokens > 0 {
|
||||
usage.PromptTokensDetails.CachedTokens = usage.PromptCacheHitTokens
|
||||
}
|
||||
usage.UsageSemantic = dto.BillingUsageSemanticOpenAI
|
||||
usage.UsageSource = billingUsage.Source
|
||||
usage.BillingUsage = dto.CloneBillingUsage(billingUsage)
|
||||
|
||||
@@ -283,6 +283,92 @@ func TestCalculateTextQuotaSummaryUsesOpenAIBillingUsageBeforeTopLevelUsage(t *t
|
||||
require.Equal(t, 98, summary.Quota)
|
||||
}
|
||||
|
||||
func TestCalculateTextQuotaSummaryUsesOpenAIResponsesInputTokenDetails(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
ctx, _ := gin.CreateTestContext(httptest.NewRecorder())
|
||||
relayInfo := &relaycommon.RelayInfo{
|
||||
RelayFormat: types.RelayFormatOpenAI,
|
||||
OriginModelName: "gpt-4o",
|
||||
PriceData: hosttypes.PriceData{
|
||||
ModelRatio: 1,
|
||||
CompletionRatio: 2,
|
||||
CacheRatio: 0.25,
|
||||
GroupRatioInfo: hosttypes.GroupRatioInfo{GroupRatio: 1},
|
||||
},
|
||||
StartTime: time.Now(),
|
||||
}
|
||||
|
||||
responsesUsage := &dto.Usage{
|
||||
InputTokens: 100,
|
||||
OutputTokens: 10,
|
||||
TotalTokens: 110,
|
||||
InputTokensDetails: &dto.InputTokenDetails{
|
||||
CachedTokens: 40,
|
||||
},
|
||||
}
|
||||
convertedUsage := &dto.Usage{
|
||||
PromptTokens: 100,
|
||||
CompletionTokens: 10,
|
||||
TotalTokens: 110,
|
||||
PromptTokensDetails: dto.InputTokenDetails{
|
||||
CachedTokens: 40,
|
||||
},
|
||||
BillingUsage: dto.NewOpenAIResponsesBillingUsage(responsesUsage),
|
||||
}
|
||||
|
||||
effectiveUsage := effectiveBillingUsage(convertedUsage)
|
||||
require.Equal(t, 40, effectiveUsage.PromptTokensDetails.CachedTokens)
|
||||
require.Zero(t, convertedUsage.BillingUsage.OpenAIUsage.PromptTokensDetails.CachedTokens)
|
||||
|
||||
summary := calculateTextQuotaSummary(ctx, relayInfo, effectiveUsage)
|
||||
require.Equal(t, 40, summary.CacheTokens)
|
||||
// 60 uncached input + 40*0.25 cached input + 10*2 output = 90.
|
||||
require.Equal(t, 90, summary.Quota)
|
||||
}
|
||||
|
||||
func TestUsageFromOpenAIBillingUsageNormalizesCacheDetailsWithoutOverwritingCanonicalValues(t *testing.T) {
|
||||
responsesUsage := &dto.Usage{
|
||||
InputTokens: 100,
|
||||
OutputTokens: 10,
|
||||
PromptCacheHitTokens: 55,
|
||||
PromptTokensDetails: dto.InputTokenDetails{
|
||||
CachedTokens: 8,
|
||||
TextTokens: 12,
|
||||
},
|
||||
InputTokensDetails: &dto.InputTokenDetails{
|
||||
CachedTokens: 40,
|
||||
CachedCreationTokens: 5,
|
||||
CacheWriteTokens: 6,
|
||||
TextTokens: 60,
|
||||
ImageTokens: 7,
|
||||
AudioTokens: 9,
|
||||
},
|
||||
}
|
||||
|
||||
billingUsage := dto.NewOpenAIResponsesBillingUsage(responsesUsage)
|
||||
usage := effectiveBillingUsage(&dto.Usage{BillingUsage: billingUsage})
|
||||
|
||||
require.Equal(t, 8, usage.PromptTokensDetails.CachedTokens)
|
||||
require.Equal(t, 5, usage.PromptTokensDetails.CachedCreationTokens)
|
||||
require.Equal(t, 6, usage.PromptTokensDetails.CacheWriteTokens)
|
||||
require.Equal(t, 12, usage.PromptTokensDetails.TextTokens)
|
||||
require.Equal(t, 7, usage.PromptTokensDetails.ImageTokens)
|
||||
require.Equal(t, 9, usage.PromptTokensDetails.AudioTokens)
|
||||
require.Zero(t, billingUsage.OpenAIUsage.PromptTokensDetails.CachedCreationTokens)
|
||||
}
|
||||
|
||||
func TestUsageFromOpenAIBillingUsageFallsBackToPromptCacheHitTokens(t *testing.T) {
|
||||
usage := effectiveBillingUsage(&dto.Usage{
|
||||
BillingUsage: dto.NewOpenAIChatBillingUsage(&dto.Usage{
|
||||
PromptTokens: 100,
|
||||
CompletionTokens: 10,
|
||||
PromptCacheHitTokens: 35,
|
||||
}),
|
||||
})
|
||||
|
||||
require.Equal(t, 35, usage.PromptTokensDetails.CachedTokens)
|
||||
}
|
||||
|
||||
func TestUsageBillingPathForLog(t *testing.T) {
|
||||
require.Equal(t, usageBillingPathAnthropic, usageBillingPathForLog(true, &dto.Usage{
|
||||
BillingUsage: dto.NewClaudeMessagesBillingUsage(&dto.ClaudeUsage{InputTokens: 1}),
|
||||
|
||||
Reference in New Issue
Block a user