若干更新迭代
This commit is contained in:
1 parent
deb9cc5bcf
commit
4ac786aafb
64 files changed
+3251
-291
No files matched your search
@@ -30,6 +30,9 @@ func DefaultBillingPriceRules() []PriceRule {
|
||||
{ID: "base-seedream-5-0-pro-layers", Provider: "seedream", Capability: "image.generate", ReqKey: Seedream50ProModel, Unit: UnitImage, StandardUnitPriceFen: 15, Conditions: Conditions{"layerDecomposition": true}, Priority: 10, Note: "图层拆分:≤261万像素 ¥0.15/张,>261万像素 ¥0.30/张;最多预留底图加16个图层,完成后按每张实际像素结算。", Source: catalogSource("https://docs.volcengine.com/docs/82379/1544106?lang=zh"), Dimensions: seedreamDimensions()},
|
||||
{ID: "base-bailian-wan27-i2v-720p", Provider: "bailian", Capability: "video.generate", ReqKey: "wan2.7-i2v-2026-04-25", VariantKey: "resolution=720p", Unit: UnitVideoSecond, StandardUnitPriceFen: 60, Source: catalogSource("https://help.aliyun.com/zh/model-studio/wan2-7-i2v")},
|
||||
{ID: "base-bailian-wan27-i2v-1080p", Provider: "bailian", Capability: "video.generate", ReqKey: "wan2.7-i2v-2026-04-25", VariantKey: "resolution=1080p", Unit: UnitVideoSecond, StandardUnitPriceFen: 100, Source: catalogSource("https://help.aliyun.com/zh/model-studio/wan2-7-i2v")},
|
||||
{ID: "base-bailian-wan30-video-480p", Provider: "bailian", Capability: "video.generate", ReqKey: BailianWan30VideoModel, VariantKey: "resolution=480p", Unit: UnitVideoSecond, StandardUnitPriceFen: 30, Note: "北京地域官方原价 ¥0.30/秒;官网限时折扣未计入标准成本,图片输入不另收费。", Source: wan30CatalogSource()},
|
||||
{ID: "base-bailian-wan30-video-720p", Provider: "bailian", Capability: "video.generate", ReqKey: BailianWan30VideoModel, VariantKey: "resolution=720p", Unit: UnitVideoSecond, StandardUnitPriceFen: 60, Note: "北京地域官方原价 ¥0.60/秒;官网限时折扣未计入标准成本,图片输入不另收费。", Source: wan30CatalogSource()},
|
||||
{ID: "base-bailian-wan30-video-1080p", Provider: "bailian", Capability: "video.generate", ReqKey: BailianWan30VideoModel, VariantKey: "resolution=1080p", Unit: UnitVideoSecond, StandardUnitPriceFen: 120, Note: "北京地域官方原价 ¥1.20/秒;官网限时折扣未计入标准成本,图片输入不另收费。", Source: wan30CatalogSource()},
|
||||
{ID: "base-seedance-2-0-480p", Provider: "seedance", Capability: "video.generate", ReqKey: "doubao-seedance-2-0-260128", VariantKey: "resolution=480p", Unit: UnitVideoSecond, StandardUnitPriceFen: 46, Source: catalogSource("https://www.volcengine.com/docs/82379/1544106?lang=zh")},
|
||||
{ID: "base-seedance-2-0-720p", Provider: "seedance", Capability: "video.generate", ReqKey: "doubao-seedance-2-0-260128", VariantKey: "resolution=720p", Unit: UnitVideoSecond, StandardUnitPriceFen: 99, Source: catalogSource("https://www.volcengine.com/docs/82379/1544106?lang=zh")},
|
||||
{ID: "base-seedance-2-0-1080p", Provider: "seedance", Capability: "video.generate", ReqKey: "doubao-seedance-2-0-260128", VariantKey: "resolution=1080p", Unit: UnitVideoSecond, StandardUnitPriceFen: 248, Source: catalogSource("https://www.volcengine.com/docs/82379/1544106?lang=zh")},
|
||||
@@ -51,6 +54,10 @@ func minimaxCatalogSource() map[string]any {
|
||||
return map[string]any{"url": "https://platform.minimaxi.com/docs/guides/pricing-paygo#视频", "observedAt": "2026-09-04"}
|
||||
}
|
||||
|
||||
func wan30CatalogSource() map[string]any {
|
||||
return map[string]any{"url": "https://help.aliyun.com/zh/model-studio/model-pricing", "observedAt": "2026-10-01", "basis": "北京地域官方原价;限时7折未计入标准成本;仅输出视频按实际秒数计费(本接入仅支持图片输入)"}
|
||||
}
|
||||
|
||||
func seedreamDimensions() []ParameterDimension {
|
||||
return []ParameterDimension{{
|
||||
Key: "size", Label: "分辨率", BaselineValue: "1.5K", DefaultValue: "1.5K",
|
||||
|
||||
@@ -14,6 +14,9 @@ func TestDefaultBillingPriceRulesFreezeOfficialCatalogMatchKeys(t *testing.T) {
|
||||
"base-seedream-5-0-pro-layers": false,
|
||||
"base-bailian-wan27-i2v-720p": false,
|
||||
"base-bailian-wan27-i2v-1080p": false,
|
||||
"base-bailian-wan30-video-480p": false,
|
||||
"base-bailian-wan30-video-720p": false,
|
||||
"base-bailian-wan30-video-1080p": false,
|
||||
"base-seedance-2-0-480p": false,
|
||||
"base-seedance-2-0-720p": false,
|
||||
"base-seedance-2-0-1080p": false,
|
||||
|
||||
@@ -112,6 +112,7 @@ type AccountConfigStore interface {
|
||||
// Billing HTTP module. Implementations can execute overview reads concurrently.
|
||||
type ReadService interface {
|
||||
Overview(context.Context, string, string) (Overview, error)
|
||||
Wallet(context.Context, string) (Wallet, error)
|
||||
AdminOverview(context.Context) (AdminOverview, error)
|
||||
ListPrices(context.Context) ([]PriceRule, error)
|
||||
GetPrice(context.Context, string) (*PriceRule, error)
|
||||
|
||||
@@ -24,8 +24,7 @@ func MinimaxH3VideoPriceFenPerSecond(resolution string) int64 {
|
||||
}
|
||||
|
||||
// CalculateMinimaxH3ActualAmountFen follows the official pay-as-you-go H3
|
||||
// formula. The first five input images are free; this first product release
|
||||
// accepts at most one, while keeping the calculator correct for future modes.
|
||||
// formula. The first five input images are free.
|
||||
func CalculateMinimaxH3ActualAmountFen(input MinimaxH3ActualAmountInput) (int64, error) {
|
||||
if input.OutputSeconds <= 0 {
|
||||
return 0, errors.New("MiniMax H3 output seconds must be positive")
|
||||
|
||||
@@ -69,6 +69,9 @@ func (s *Service) Overview(ctx context.Context, organizationID, accountID string
|
||||
}
|
||||
return Overview{Wallet: wallet, Ledger: ledger, Summary: Summarize(ledger), Personal: Summarize(personal)}, nil
|
||||
}
|
||||
func (s *Service) Wallet(ctx context.Context, organizationID string) (Wallet, error) {
|
||||
return s.store.BillingWallet(ctx, organizationID)
|
||||
}
|
||||
func (s *Service) Quote(ctx context.Context, command QuoteCommand) (*Quote, error) {
|
||||
if !s.enabled {
|
||||
return nil, nil
|
||||
@@ -114,6 +117,22 @@ func (s *Service) Quote(ctx context.Context, command QuoteCommand) (*Quote, erro
|
||||
return quote, nil
|
||||
}
|
||||
if command.Provider == "minimax" && IsMinimaxH3BillingModel(command.ReqKey) {
|
||||
inputImageCount := 0
|
||||
if count, ok := number(parameters["referenceImageCount"]); ok {
|
||||
inputImageCount = int(math.Ceil(count))
|
||||
}
|
||||
extraInputPriceFen := int64(max(0, inputImageCount-5) * 20)
|
||||
quote.AmountFen = int64(math.Ceil((float64(quote.StandardUnitPriceFen)*quote.Quantity + float64(extraInputPriceFen)) * quote.MarkupMultiplier))
|
||||
if quote.Parameters == nil {
|
||||
quote.Parameters = Parameters{}
|
||||
}
|
||||
quote.Parameters["inputImageCount"] = float64(inputImageCount)
|
||||
quote.Parameters["inputImagePriceFen"] = float64(extraInputPriceFen)
|
||||
quote.ReservedAmountFen = quote.AmountFen
|
||||
quote.SettlementStatus = "pending"
|
||||
return quote, nil
|
||||
}
|
||||
if command.Provider == "bailian" && command.ReqKey == BailianWan30VideoModel {
|
||||
quote.ReservedAmountFen = quote.AmountFen
|
||||
quote.SettlementStatus = "pending"
|
||||
return quote, nil
|
||||
|
||||
@@ -157,6 +157,41 @@ func TestServiceQuoteReservesMinimaxH3OfficialRateWithMarkup(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestServiceQuoteReservesMinimaxH3ExtraReferenceImages(t *testing.T) {
|
||||
store := "eStoreStub{rules: []PriceRule{{
|
||||
ID: "minimax-h3-2k", Provider: "minimax", Capability: "video.generate", ReqKey: MinimaxH3Model,
|
||||
VariantKey: "resolution=2k", Unit: UnitVideoSecond, StandardUnitPriceFen: 80, MarkupMultiplier: 1.2, Enabled: true,
|
||||
}}}
|
||||
materials := make([]any, 9)
|
||||
for i := range materials {
|
||||
materials[i] = map[string]any{"type": "image", "url": "https://cdn.test/ref.png"}
|
||||
}
|
||||
quote, err := NewService(store, nil).Quote(context.Background(), QuoteCommand{
|
||||
Provider: "minimax", Capability: "video.generate", ReqKey: MinimaxH3Model,
|
||||
Payload: map[string]any{"settings": map[string]any{"duration": 5, "resolution": "2K"}, "materials": materials},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if quote.AmountFen != 576 || quote.ReservedAmountFen != 576 || quote.Parameters["inputImageCount"] != float64(9) || quote.Parameters["inputImagePriceFen"] != float64(80) {
|
||||
t.Fatalf("quote=%#v", quote)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServiceQuoteReservesIndependentWan30Price(t *testing.T) {
|
||||
store := "eStoreStub{rules: DefaultBillingPriceRules()}
|
||||
quote, err := NewService(store, nil).Quote(context.Background(), QuoteCommand{
|
||||
Provider: "bailian", Capability: "video.generate", ReqKey: BailianWan30VideoModel,
|
||||
Payload: map[string]any{"settings": map[string]any{"duration": 5, "resolution": "1080P"}},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if quote.PriceRuleID != "base-bailian-wan30-video-1080p" || quote.AmountFen != 720 || quote.ReservedAmountFen != 720 || quote.SettlementStatus != "pending" {
|
||||
t.Fatalf("quote=%#v", quote)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServiceQuoteAppliesSeedreamSizeAndReferenceInputPricing(t *testing.T) {
|
||||
store := "eStoreStub{rules: []PriceRule{{
|
||||
ID: "seedream-5", Provider: "seedream", Capability: "image.generate", ReqKey: Seedream50ProModel,
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package billing
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math"
|
||||
)
|
||||
|
||||
const BailianWan30VideoModel = "wan3.0-video"
|
||||
|
||||
// CalculateBailianWan30ActualAmountFen bills successful output duration. This
|
||||
// integration accepts image inputs only, so there are no input video seconds.
|
||||
func CalculateBailianWan30ActualAmountFen(outputSeconds float64, standardUnitPriceFen int64, markupMultiplier float64) (int64, error) {
|
||||
if math.IsNaN(outputSeconds) || math.IsInf(outputSeconds, 0) || outputSeconds <= 0 {
|
||||
return 0, errors.New("Wan 3.0 output seconds must be positive")
|
||||
}
|
||||
if standardUnitPriceFen <= 0 || math.IsNaN(markupMultiplier) || math.IsInf(markupMultiplier, 0) || markupMultiplier < 1 {
|
||||
return 0, errors.New("Wan 3.0 price or markup is invalid")
|
||||
}
|
||||
return int64(math.Ceil(outputSeconds * float64(standardUnitPriceFen) * markupMultiplier)), nil
|
||||
}
|
||||
Reference in new issue
Block a user