fix(billing): correct tiered billing settlement and edge cases
- quota.go: add missing SettleBilling call in PostWssConsumeQuota - text_quota.go: gate InjectTieredBillingInfo on tieredBillingApplied bool instead of tieredResult != nil, so fallback billing still logs metadata - price.go: remove quotaBeforeGroup == 0 from freeModel condition to avoid bypassing settlement for output-only expressions - tiered_settle.go: split cc/cc1h subtraction using UsageSemantic to distinguish OpenAI vs Claude cache creation token formats - pricing.go: only set BillingMode when a non-empty expression exists - useModelPricingEditorState.js: only write billing_mode when finalBillingExpr is non-empty
This commit is contained in:
+2
-2
@@ -323,8 +323,8 @@ func updatePricing() {
|
|||||||
pricing.AudioCompletionRatio = &audioCompletionRatio
|
pricing.AudioCompletionRatio = &audioCompletionRatio
|
||||||
}
|
}
|
||||||
if billingMode := billing_setting.GetBillingMode(model); billingMode == "tiered_expr" {
|
if billingMode := billing_setting.GetBillingMode(model); billingMode == "tiered_expr" {
|
||||||
pricing.BillingMode = billingMode
|
if expr, ok := billing_setting.GetBillingExpr(model); ok && expr != "" {
|
||||||
if expr, ok := billing_setting.GetBillingExpr(model); ok {
|
pricing.BillingMode = billingMode
|
||||||
pricing.BillingExpr = expr
|
pricing.BillingExpr = expr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -269,7 +269,7 @@ func modelPriceHelperTiered(c *gin.Context, info *relaycommon.RelayInfo, promptT
|
|||||||
|
|
||||||
freeModel := false
|
freeModel := false
|
||||||
if !operation_setting.GetQuotaSetting().EnableFreeModelPreConsume {
|
if !operation_setting.GetQuotaSetting().EnableFreeModelPreConsume {
|
||||||
if groupRatioInfo.GroupRatio == 0 || quotaBeforeGroup == 0 {
|
if groupRatioInfo.GroupRatio == 0 {
|
||||||
preConsumedQuota = 0
|
preConsumedQuota = 0
|
||||||
freeModel = true
|
freeModel = true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -226,6 +226,10 @@ func PostWssConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, mod
|
|||||||
model.UpdateChannelUsedQuota(relayInfo.ChannelId, quota)
|
model.UpdateChannelUsedQuota(relayInfo.ChannelId, quota)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := SettleBilling(ctx, relayInfo, quota); err != nil {
|
||||||
|
logger.LogError(ctx, "error settling billing: "+err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
logModel := modelName
|
logModel := modelName
|
||||||
if extraContent != "" {
|
if extraContent != "" {
|
||||||
logContent += ", " + extraContent
|
logContent += ", " + extraContent
|
||||||
|
|||||||
@@ -330,6 +330,7 @@ func PostTextConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, us
|
|||||||
summary := calculateTextQuotaSummary(ctx, relayInfo, usage)
|
summary := calculateTextQuotaSummary(ctx, relayInfo, usage)
|
||||||
|
|
||||||
var tieredResult *billingexpr.TieredResult
|
var tieredResult *billingexpr.TieredResult
|
||||||
|
tieredBillingApplied := false
|
||||||
if originUsage != nil {
|
if originUsage != nil {
|
||||||
var tieredUsedVars map[string]bool
|
var tieredUsedVars map[string]bool
|
||||||
if snap := relayInfo.TieredBillingSnapshot; snap != nil {
|
if snap := relayInfo.TieredBillingSnapshot; snap != nil {
|
||||||
@@ -337,6 +338,7 @@ func PostTextConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, us
|
|||||||
}
|
}
|
||||||
tieredOk, tieredQuota, tieredRes := TryTieredSettle(relayInfo, BuildTieredTokenParams(usage, summary.IsClaudeUsageSemantic, tieredUsedVars))
|
tieredOk, tieredQuota, tieredRes := TryTieredSettle(relayInfo, BuildTieredTokenParams(usage, summary.IsClaudeUsageSemantic, tieredUsedVars))
|
||||||
if tieredOk {
|
if tieredOk {
|
||||||
|
tieredBillingApplied = true
|
||||||
tieredResult = tieredRes
|
tieredResult = tieredRes
|
||||||
summary.Quota = composeTieredTextQuota(relayInfo, summary, tieredQuota, tieredRes)
|
summary.Quota = composeTieredTextQuota(relayInfo, summary, tieredQuota, tieredRes)
|
||||||
}
|
}
|
||||||
@@ -451,7 +453,7 @@ func PostTextConsumeQuota(ctx *gin.Context, relayInfo *relaycommon.RelayInfo, us
|
|||||||
// prompt/cache fields here, otherwise old upstream payloads may be double-counted.
|
// prompt/cache fields here, otherwise old upstream payloads may be double-counted.
|
||||||
other["input_tokens_total"] = usage.InputTokens
|
other["input_tokens_total"] = usage.InputTokens
|
||||||
}
|
}
|
||||||
if tieredResult != nil {
|
if tieredBillingApplied {
|
||||||
InjectTieredBillingInfo(other, relayInfo, tieredResult)
|
InjectTieredBillingInfo(other, relayInfo, tieredResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,8 +22,14 @@ func BuildTieredTokenParams(usage *dto.Usage, isClaudeUsageSemantic bool, usedVa
|
|||||||
p := float64(usage.PromptTokens)
|
p := float64(usage.PromptTokens)
|
||||||
c := float64(usage.CompletionTokens)
|
c := float64(usage.CompletionTokens)
|
||||||
cr := float64(usage.PromptTokensDetails.CachedTokens)
|
cr := float64(usage.PromptTokensDetails.CachedTokens)
|
||||||
ccTotal := float64(usage.PromptTokensDetails.CachedCreationTokens)
|
cc5m := float64(usage.PromptTokensDetails.CachedCreationTokens)
|
||||||
cc1h := float64(usage.ClaudeCacheCreation1hTokens)
|
cc1h := float64(0)
|
||||||
|
|
||||||
|
if usage.UsageSemantic == "anthropic" {
|
||||||
|
cc1h = float64(usage.ClaudeCacheCreation1hTokens)
|
||||||
|
cc5m = float64(usage.ClaudeCacheCreation5mTokens)
|
||||||
|
}
|
||||||
|
|
||||||
img := float64(usage.PromptTokensDetails.ImageTokens)
|
img := float64(usage.PromptTokensDetails.ImageTokens)
|
||||||
ai := float64(usage.PromptTokensDetails.AudioTokens)
|
ai := float64(usage.PromptTokensDetails.AudioTokens)
|
||||||
imgO := float64(usage.CompletionTokenDetails.ImageTokens)
|
imgO := float64(usage.CompletionTokenDetails.ImageTokens)
|
||||||
@@ -33,8 +39,11 @@ func BuildTieredTokenParams(usage *dto.Usage, isClaudeUsageSemantic bool, usedVa
|
|||||||
if usedVars["cr"] {
|
if usedVars["cr"] {
|
||||||
p -= cr
|
p -= cr
|
||||||
}
|
}
|
||||||
if usedVars["cc"] || usedVars["cc1h"] {
|
if usedVars["cc"] {
|
||||||
p -= ccTotal
|
p -= cc5m
|
||||||
|
}
|
||||||
|
if usedVars["cc1h"] {
|
||||||
|
p -= cc1h
|
||||||
}
|
}
|
||||||
if usedVars["img"] {
|
if usedVars["img"] {
|
||||||
p -= img
|
p -= img
|
||||||
@@ -61,7 +70,7 @@ func BuildTieredTokenParams(usage *dto.Usage, isClaudeUsageSemantic bool, usedVa
|
|||||||
P: p,
|
P: p,
|
||||||
C: c,
|
C: c,
|
||||||
CR: cr,
|
CR: cr,
|
||||||
CC: ccTotal - cc1h,
|
CC: cc5m,
|
||||||
CC1h: cc1h,
|
CC1h: cc1h,
|
||||||
Img: img,
|
Img: img,
|
||||||
ImgO: imgO,
|
ImgO: imgO,
|
||||||
|
|||||||
@@ -1041,12 +1041,12 @@ export function useModelPricingEditorState({
|
|||||||
|
|
||||||
for (const model of models) {
|
for (const model of models) {
|
||||||
if (model.billingMode === 'tiered_expr') {
|
if (model.billingMode === 'tiered_expr') {
|
||||||
tieredOutput['billing_setting.billing_mode'][model.name] = 'tiered_expr';
|
|
||||||
const finalBillingExpr = combineBillingExpr(
|
const finalBillingExpr = combineBillingExpr(
|
||||||
model.billingExpr,
|
model.billingExpr,
|
||||||
model.requestRuleExpr,
|
model.requestRuleExpr,
|
||||||
);
|
);
|
||||||
if (finalBillingExpr) {
|
if (finalBillingExpr) {
|
||||||
|
tieredOutput['billing_setting.billing_mode'][model.name] = 'tiered_expr';
|
||||||
tieredOutput['billing_setting.billing_expr'][model.name] = finalBillingExpr;
|
tieredOutput['billing_setting.billing_expr'][model.name] = finalBillingExpr;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user