fix: 支持零价系统渠道模型分发 (#303)
* docs(spec): 模型计费 - 记录零价免费模型设计 * fix(billing): 模型定价 - 支持零价免费模型 * fix(config): 系统模型目录 - 支持零价模型
This commit is contained in:
@@ -615,7 +615,7 @@ func (r *Repository) SettleBillingOrder(id string, providerRequestID string) err
|
||||
if order.Status == model.BillingStatusRefunded {
|
||||
return errors.New("billing order already refunded")
|
||||
}
|
||||
if order.BillingMode == "token" {
|
||||
if order.BillingMode == "token" && !zeroPricedTokenOrder(order) {
|
||||
usage, err := billingUsage(tx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -717,6 +717,13 @@ func (r *Repository) SettleBillingOrder(id string, providerRequestID string) err
|
||||
})
|
||||
}
|
||||
|
||||
func zeroPricedTokenOrder(order model.BillingOrder) bool {
|
||||
return order.BillingMode == "token" &&
|
||||
order.InputTokenPriceMicrocredits == 0 &&
|
||||
order.OutputTokenPriceMicrocredits == 0 &&
|
||||
order.CachedTokenPriceMicrocredits == 0
|
||||
}
|
||||
|
||||
func (r *Repository) RefundBillingOrder(id string, errorText string) error {
|
||||
return r.db.Transaction(func(tx *gorm.DB) error {
|
||||
var order model.BillingOrder
|
||||
|
||||
@@ -473,12 +473,6 @@ func validateChannelModelTierPricing(capability string, protocol model.ChannelIn
|
||||
if !input.PriceConfigured {
|
||||
return nil
|
||||
}
|
||||
if billingMode == "token" && input.InputTokenPriceMicrocredits == 0 && input.OutputTokenPriceMicrocredits == 0 && input.CachedTokenPriceMicrocredits == 0 {
|
||||
return BadAuthRequest("Token 计费至少需要配置一项价格")
|
||||
}
|
||||
if billingMode == "token" && capability == "video" && input.OutputTokenPriceMicrocredits == 0 {
|
||||
return BadAuthRequest("火山方舟视频 Token 计费需要配置每百万视频 Token 价格")
|
||||
}
|
||||
const maxTokenPriceMicrocredits = int64(1_000_000) * CreditScale
|
||||
if input.InputTokenPriceMicrocredits > maxTokenPriceMicrocredits || input.OutputTokenPriceMicrocredits > maxTokenPriceMicrocredits || input.CachedTokenPriceMicrocredits > maxTokenPriceMicrocredits {
|
||||
return BadAuthRequest("Token 每百万用量价格不能超过 1,000,000 积分")
|
||||
|
||||
@@ -191,11 +191,11 @@ func publicLogicalModel(cached cachedLogicalModel, available bool) PublicLogical
|
||||
Description: item.Description, Capability: item.Capability, SortOrder: item.SortOrder,
|
||||
PricePolicy: item.PricePolicy, PricingMode: pricingMode, DisplayPrice: displayPrice,
|
||||
PriceLabel: priceLabel, BillingMode: item.BillingMode,
|
||||
UnitPriceMicrocredits: item.UnitPriceMicrocredits,
|
||||
InputPriceMicrocredits: item.InputPriceMicrocredits,
|
||||
UnitPriceMicrocredits: item.UnitPriceMicrocredits,
|
||||
InputPriceMicrocredits: item.InputPriceMicrocredits,
|
||||
OutputPriceMicrocredits: item.OutputPriceMicrocredits,
|
||||
CachedPriceMicrocredits: item.CachedPriceMicrocredits,
|
||||
PriceTiers: priceTiers, LegacyModelIDs: decodeLegacyModelIDs(item.LegacyModelIDsJSON),
|
||||
PriceTiers: priceTiers, LegacyModelIDs: decodeLegacyModelIDs(item.LegacyModelIDsJSON),
|
||||
CapabilitySpec: productSpec, CapabilityProfiles: profiles,
|
||||
DefaultOptions: cached.Defaults, Available: available,
|
||||
}
|
||||
@@ -693,9 +693,6 @@ func (s *Service) logicalModelBundle(actor *model.User, id string, req LogicalMo
|
||||
if !supportsLogicalModelTokenBilling(capability, enabledRouteProtocols) {
|
||||
return nil, nil, nil, false, BadAuthRequest("Token 计费仅支持文本前台模型,或全部启用供应线路均为火山方舟视频协议的视频前台模型")
|
||||
}
|
||||
if capability == "video" && req.OutputPriceMicrocredits <= 0 {
|
||||
return nil, nil, nil, false, BadAuthRequest("火山方舟视频 Token 计费需要配置每百万视频 Token 价格")
|
||||
}
|
||||
}
|
||||
// 停用必须始终可执行,便于管理员立即阻止失效线路继续对外服务;重新启用时再强校验结构能力和计费可用性。
|
||||
if req.Enabled {
|
||||
|
||||
@@ -144,7 +144,7 @@ func (s *Service) sanitizeChannelModel(cm *model.ChannelModel) PublicChannelMode
|
||||
// 转换为公开的价格档
|
||||
publicTiers := make([]PublicChannelModelPriceTier, 0, len(priceTiers))
|
||||
for _, tier := range priceTiers {
|
||||
if !tier.Enabled || !ValidatePriceTierPrice(&tier, cm.Capability, cm.Protocol) {
|
||||
if !tier.Enabled || !tier.PriceConfigured || !ValidatePriceTierPrice(&tier, cm.Capability, cm.Protocol) {
|
||||
continue
|
||||
}
|
||||
publicTiers = append(publicTiers, PublicChannelModelPriceTier{
|
||||
|
||||
@@ -7,20 +7,20 @@ import "infinite-canvas/backend/internal/model"
|
||||
func ValidateChannelModelPrice(billingMode string, capability string, protocol model.ChannelInterfaceType, unitPrice, inputPrice, outputPrice, cachedPrice int64) bool {
|
||||
switch billingMode {
|
||||
case "fixed_request":
|
||||
// 固定价格模式:必须配置单价且大于 0
|
||||
return unitPrice > 0
|
||||
// 固定价格模式:0 表示免费,负数无效。
|
||||
return unitPrice >= 0
|
||||
case "per_second":
|
||||
// 按秒计费:必须配置单价且大于 0
|
||||
return unitPrice > 0
|
||||
// 按秒计费:0 表示免费,负数无效。
|
||||
return unitPrice >= 0
|
||||
case "token":
|
||||
if capability == "video" {
|
||||
return protocol == model.ChannelInterfaceVolcengineArkVideo && outputPrice > 0
|
||||
return protocol == model.ChannelInterfaceVolcengineArkVideo && inputPrice >= 0 && outputPrice >= 0 && cachedPrice >= 0
|
||||
}
|
||||
if capability != "" && capability != "text" {
|
||||
return false
|
||||
}
|
||||
// 文本模型:至少配置一项价格
|
||||
return inputPrice > 0 || outputPrice > 0 || cachedPrice > 0
|
||||
// 文本模型:所有 Token 价格为 0 时表示免费。
|
||||
return inputPrice >= 0 && outputPrice >= 0 && cachedPrice >= 0
|
||||
default:
|
||||
return false
|
||||
}
|
||||
@@ -58,7 +58,7 @@ func HasValidPrice(channelModel *model.ChannelModel) bool {
|
||||
// 如果有价格档,检查价格档
|
||||
if len(channelModel.PriceTiers) > 0 {
|
||||
for _, tier := range channelModel.PriceTiers {
|
||||
if tier.Enabled && ValidatePriceTierPrice(&tier, channelModel.Capability, channelModel.Protocol) {
|
||||
if tier.Enabled && tier.PriceConfigured && ValidatePriceTierPrice(&tier, channelModel.Capability, channelModel.Protocol) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
@@ -66,7 +66,7 @@ func HasValidPrice(channelModel *model.ChannelModel) bool {
|
||||
}
|
||||
|
||||
// 否则检查模型级别的价格
|
||||
return ValidateChannelModelPrice(channelModel.BillingMode, channelModel.Capability, channelModel.Protocol, channelModel.UnitPriceMicrocredits, channelModel.InputTokenPriceMicrocredits, channelModel.OutputTokenPriceMicrocredits, channelModel.CachedTokenPriceMicrocredits)
|
||||
return channelModel.PriceConfigured && ValidateChannelModelPrice(channelModel.BillingMode, channelModel.Capability, channelModel.Protocol, channelModel.UnitPriceMicrocredits, channelModel.InputTokenPriceMicrocredits, channelModel.OutputTokenPriceMicrocredits, channelModel.CachedTokenPriceMicrocredits)
|
||||
}
|
||||
|
||||
// ValidateLogicalModelPrice 校验前台模型的价格配置
|
||||
|
||||
Reference in New Issue
Block a user