fix(billing): surface quota saturation events for admin auditing
Thread int32 saturation clamps from tiered settlement and video task recompute into the consume/task logs under admin_info, so oversized or malformed billing inputs stay auditable. Clamp negative audio duration before token conversion and gate the saturation UI markers on admin.
This commit is contained in:
@@ -105,7 +105,7 @@ func OpenaiTTSHandler(c *gin.Context, resp *http.Response, info *relaycommon.Rel
|
||||
} else if duration > 0 {
|
||||
// 计算 token: ceil(duration) / 60.0 * 1000,即每分钟 1000 tokens。
|
||||
// duration 解析自上游返回的音频元数据,饱和转换防止 int 回绕。
|
||||
completionTokens := common.QuotaFromFloat(math.Round(math.Ceil(duration) / 60.0 * 1000))
|
||||
completionTokens := common.QuotaRound(math.Ceil(duration) / 60.0 * 1000)
|
||||
usage.CompletionTokens = completionTokens
|
||||
usage.CompletionTokenDetails.AudioTokens = completionTokens
|
||||
}
|
||||
|
||||
@@ -163,6 +163,11 @@ type RelayInfo struct {
|
||||
|
||||
PriceData types.PriceData
|
||||
|
||||
// QuotaClamp is set (non-nil) when a quota conversion saturated at the
|
||||
// int32 bound (or NaN fallback) while computing this request's charge.
|
||||
// It is surfaced onto the consume/task log's admin_info for auditing.
|
||||
QuotaClamp *common.QuotaClamp
|
||||
|
||||
// TieredBillingSnapshot is a frozen snapshot of tiered billing rules
|
||||
// captured at pre-consume time. Non-nil only when billing mode is "tiered_expr".
|
||||
TieredBillingSnapshot *billingexpr.BillingSnapshot
|
||||
|
||||
+18
-2
@@ -202,7 +202,9 @@ func RelayTaskSubmit(c *gin.Context, info *relaycommon.RelayInfo) (*TaskSubmitRe
|
||||
quotaWithRatios *= ra
|
||||
}
|
||||
}
|
||||
info.PriceData.Quota = common.QuotaFromFloat(quotaWithRatios)
|
||||
quota, clamp := common.QuotaFromFloatChecked(quotaWithRatios)
|
||||
info.PriceData.Quota = quota
|
||||
noteTaskQuotaClamp(info, clamp)
|
||||
}
|
||||
|
||||
// 7. 预扣费(仅首次 — 重试时 info.Billing 已存在,跳过)
|
||||
@@ -278,7 +280,21 @@ func recalcQuotaFromRatios(info *relaycommon.RelayInfo, ratios map[string]float6
|
||||
result *= ra
|
||||
}
|
||||
}
|
||||
return common.QuotaFromFloat(result)
|
||||
quota, clamp := common.QuotaFromFloatChecked(result)
|
||||
noteTaskQuotaClamp(info, clamp)
|
||||
return quota
|
||||
}
|
||||
|
||||
// noteTaskQuotaClamp records the first quota saturation event onto the task's
|
||||
// RelayInfo so LogTaskConsumption can surface it on the submit log's
|
||||
// admin_info. First non-nil clamp wins.
|
||||
func noteTaskQuotaClamp(info *relaycommon.RelayInfo, clamp *common.QuotaClamp) {
|
||||
if clamp == nil || info == nil {
|
||||
return
|
||||
}
|
||||
if info.QuotaClamp == nil {
|
||||
info.QuotaClamp = clamp
|
||||
}
|
||||
}
|
||||
|
||||
var fetchRespBuilders = map[int]func(c *gin.Context) (respBody []byte, taskResp *dto.TaskError){
|
||||
|
||||
Reference in New Issue
Block a user