Просмотр исходного кода

feat(task-pricing): add matrix lookup decisions

master
fengsilin 3 недель назад
Родитель
Сommit
8f2b014366
7 измененных файлов: 560 добавлений и 10 удалений
  1. +6
    -0
      relay/common/relay_info.go
  2. +70
    -6
      relay/helper/price.go
  3. +59
    -1
      relay/helper/price_test.go
  4. +287
    -0
      relay/helper/pricing_lookup.go
  5. +119
    -0
      relay/helper/pricing_lookup_test.go
  6. +14
    -2
      relay/mjproxy_handler.go
  7. +5
    -1
      relay/relay_task.go

+ 6
- 0
relay/common/relay_info.go Просмотреть файл

@@ -155,6 +155,12 @@ type RelayInfo struct {


PriceData types.PriceData PriceData types.PriceData


PricingConfigSnapshot *types.PricingConfig
PricingConfigSnapshotLoaded bool
PricingDecisionFrozen *types.PricingDecision
OriginPricing *types.OriginPricingSnapshot
RequireMatrixUsageBilling bool

Request dto.Request Request dto.Request


// RequestConversionChain records request format conversions in order, e.g. // RequestConversionChain records request format conversions in order, e.g.


+ 70
- 6
relay/helper/price.go Просмотреть файл

@@ -2,8 +2,10 @@ package helper


import ( import (
"fmt" "fmt"
"net/http"


"github.com/QuantumNous/new-api/common" "github.com/QuantumNous/new-api/common"
"github.com/QuantumNous/new-api/dto"
"github.com/QuantumNous/new-api/logger" "github.com/QuantumNous/new-api/logger"
relaycommon "github.com/QuantumNous/new-api/relay/common" relaycommon "github.com/QuantumNous/new-api/relay/common"
"github.com/QuantumNous/new-api/setting/operation_setting" "github.com/QuantumNous/new-api/setting/operation_setting"
@@ -124,9 +126,57 @@ func ModelPriceHelper(c *gin.Context, info *relaycommon.RelayInfo, promptTokens
} }


// ModelPriceHelperPerCall 按次计费的 PriceHelper (MJ、Task) // ModelPriceHelperPerCall 按次计费的 PriceHelper (MJ、Task)
func ModelPriceHelperPerCall(c *gin.Context, info *relaycommon.RelayInfo) types.PriceData {
func ModelPriceHelperPerCall(c *gin.Context, info *relaycommon.RelayInfo) (types.PriceData, *dto.TaskError) {
groupRatioInfo := HandleGroupRatio(c, info) groupRatioInfo := HandleGroupRatio(c, info)


if info.PricingDecisionFrozen != nil {
priceData := priceDataFromDecision(info.PricingDecisionFrozen)
info.PriceData = priceData
return priceData, nil
}

if info.OriginPricing != nil {
var (
decision *types.PricingDecision
taskErr *dto.TaskError
)
if info.OriginPricing.BillingMode == types.BillingModeMatrix {
decision, taskErr = buildRemixMatrixDecision(info.OriginPricing, groupRatioInfo)
} else {
decision, taskErr = buildRemixLegacyDecision(info.OriginPricing, groupRatioInfo)
}
if taskErr != nil {
return types.PriceData{}, taskErr
}
info.PricingDecisionFrozen = decision
priceData := priceDataFromDecision(decision)
info.PriceData = priceData
return priceData, nil
}

if info.PricingConfigSnapshotLoaded && info.PricingConfigSnapshot != nil {
resolver, err := NewDimensionResolver(c)
if err != nil {
return types.PriceData{}, pricingDimensionTaskError(err)
}
resolved, err := resolver.ResolveDimensions(info.PricingConfigSnapshot.Dimensions)
if err != nil {
return types.PriceData{}, pricingDimensionTaskError(err)
}
result, taskErr := lookupPricingConfig(info.PricingConfigSnapshot, resolved)
if taskErr != nil {
return types.PriceData{}, taskErr
}
decision := buildMatchedDecision(info.PricingConfigSnapshot, result, groupRatioInfo)
if result.Mode != types.PricingModeMatched {
decision = buildFallbackDecision(info.PricingConfigSnapshot, result, groupRatioInfo)
}
info.PricingDecisionFrozen = decision
priceData := priceDataFromDecision(decision)
info.PriceData = priceData
return priceData, nil
}

modelPrice, success := ratio_setting.GetModelPrice(info.OriginModelName, true) modelPrice, success := ratio_setting.GetModelPrice(info.OriginModelName, true)
if !success { if !success {
defaultPrice, ok := ratio_setting.GetDefaultModelPriceMap()[info.OriginModelName] defaultPrice, ok := ratio_setting.GetDefaultModelPriceMap()[info.OriginModelName]
@@ -147,12 +197,26 @@ func ModelPriceHelperPerCall(c *gin.Context, info *relaycommon.RelayInfo) types.
} }


priceData := types.PriceData{ priceData := types.PriceData{
FreeModel: freeModel,
ModelPrice: modelPrice,
Quota: quota,
GroupRatioInfo: groupRatioInfo,
FreeModel: freeModel,
ModelPrice: modelPrice,
Quota: quota,
GroupRatioInfo: groupRatioInfo,
}
decision := buildLegacyDecision(modelPrice, groupRatioInfo, freeModel, quota)
priceData.PricingDecision = decision
info.PricingDecisionFrozen = decision
info.PriceData = priceData
return priceData, nil
}

func pricingDimensionTaskError(err error) *dto.TaskError {
return &dto.TaskError{
Code: "pricing_dimension_resolve_failed",
Message: err.Error(),
StatusCode: http.StatusBadRequest,
LocalError: true,
Error: err,
} }
return priceData
} }


func ContainPriceOrRatio(modelName string) bool { func ContainPriceOrRatio(modelName string) bool {


+ 59
- 1
relay/helper/price_test.go Просмотреть файл

@@ -116,7 +116,6 @@ func TestGlobalRatiosFilledWhenUseRatioMode(t *testing.T) {
assert.Equal(t, 2.0, priceData.ImageRatio) assert.Equal(t, 2.0, priceData.ImageRatio)
} }



func TestHandleGroupRatio_DefaultGroup(t *testing.T) { func TestHandleGroupRatio_DefaultGroup(t *testing.T) {
setupUsePriceSwitchTest(t) setupUsePriceSwitchTest(t)
c := buildTestContext(t) c := buildTestContext(t)
@@ -305,3 +304,62 @@ func TestModelPriceHelper_FreeGroup(t *testing.T) {
// cleanup // cleanup
require.NoError(t, ratio_setting.UpdateGroupRatioByJSONString(`{"default":1,"vip":1,"svip":1}`)) require.NoError(t, ratio_setting.UpdateGroupRatioByJSONString(`{"default":1,"vip":1,"svip":1}`))
} }

func TestModelPriceHelperPerCall_MatrixMatchFreezesDecision(t *testing.T) {
setupUsePriceSwitchTest(t)
c := newPricingJSONContext(`{"resolution":"720P","duration":10}`)
info := &relaycommon.RelayInfo{
OriginModelName: "hailuo-video",
UsingGroup: "default",
UserGroup: "default",
PricingConfigSnapshotLoaded: true,
PricingConfigSnapshot: testPricingLookupConfig(types.PricingFallbackReject),
}

priceData, taskErr := ModelPriceHelperPerCall(c, info)
require.Nil(t, taskErr)
require.Equal(t, int(0.08*common.QuotaPerUnit), priceData.Quota)
require.NotNil(t, priceData.PricingDecision)
require.Same(t, info.PricingDecisionFrozen, priceData.PricingDecision)
require.Equal(t, types.BillingModeMatrix, priceData.PricingDecision.BillingMode)

info.PricingConfigSnapshot.Table[0]["price"] = 1.5
next, taskErr := ModelPriceHelperPerCall(c, info)
require.Nil(t, taskErr)
require.Equal(t, priceData.Quota, next.Quota)
}

func TestModelPriceHelperPerCall_MatrixReject(t *testing.T) {
setupUsePriceSwitchTest(t)
c := newPricingJSONContext(`{"resolution":"1080P","duration":15}`)
info := &relaycommon.RelayInfo{
OriginModelName: "hailuo-video",
UsingGroup: "default",
UserGroup: "default",
PricingConfigSnapshotLoaded: true,
PricingConfigSnapshot: testPricingLookupConfig(types.PricingFallbackReject),
}

priceData, taskErr := ModelPriceHelperPerCall(c, info)
require.NotNil(t, taskErr)
require.Equal(t, "pricing_no_match", taskErr.Code)
require.Nil(t, info.PricingDecisionFrozen)
require.Zero(t, priceData.Quota)
}

func TestModelPriceHelperPerCall_LegacyDecision(t *testing.T) {
setupUsePriceSwitchTest(t)
c := buildTestContext(t)
info := &relaycommon.RelayInfo{
OriginModelName: testModelUsePriceSwitch,
UsingGroup: "default",
UserGroup: "default",
}

priceData, taskErr := ModelPriceHelperPerCall(c, info)
require.Nil(t, taskErr)
require.Equal(t, int(0.5*common.QuotaPerUnit), priceData.Quota)
require.NotNil(t, priceData.PricingDecision)
require.Equal(t, types.PricingModeLegacy, priceData.PricingDecision.Mode)
require.Same(t, info.PricingDecisionFrozen, priceData.PricingDecision)
}

+ 287
- 0
relay/helper/pricing_lookup.go Просмотреть файл

@@ -0,0 +1,287 @@
package helper

import (
"fmt"
"net/http"

"github.com/QuantumNous/new-api/common"
"github.com/QuantumNous/new-api/dto"
"github.com/QuantumNous/new-api/types"
)

type pricingLookupResult struct {
Mode types.PricingMode
PriceUSD float64
Snapshot map[string]any
}

func lookupPricingConfig(cfg *types.PricingConfig, resolved *ResolvedDimensionValues) (*pricingLookupResult, *dto.TaskError) {
if cfg == nil {
return nil, localPricingTaskError("pricing config is required", "pricing_config_required")
}

var best types.PricingRow
bestWildcardCount := len(cfg.Dimensions) + 1
bestCount := 0

for _, row := range cfg.Table {
wildcards, ok := rowMatchesDimensions(cfg.Dimensions, row, resolved.Values)
if !ok {
continue
}
if wildcards < bestWildcardCount {
best = row
bestWildcardCount = wildcards
bestCount = 1
continue
}
if wildcards == bestWildcardCount {
bestCount++
}
}

if best != nil && bestCount == 1 {
price, _ := pricingNumberValue(best["price"])
return &pricingLookupResult{
Mode: types.PricingModeMatched,
PriceUSD: price,
Snapshot: buildPricingSnapshot(cfg, resolved, price, types.PricingModeMatched, ""),
}, nil
}

if bestCount > 1 {
return fallbackPricingConfig(cfg, resolved)
}
return fallbackPricingConfig(cfg, resolved)
}

func buildMatchedDecision(cfg *types.PricingConfig, result *pricingLookupResult, group types.GroupRatioInfo) *types.PricingDecision {
return buildMatrixDecision(cfg, result, group)
}

func buildFallbackDecision(cfg *types.PricingConfig, result *pricingLookupResult, group types.GroupRatioInfo) *types.PricingDecision {
return buildMatrixDecision(cfg, result, group)
}

func buildLegacyDecision(modelPrice float64, group types.GroupRatioInfo, free bool, quota int) *types.PricingDecision {
return &types.PricingDecision{
Mode: types.PricingModeLegacy,
PriceUSD: modelPrice,
Quota: quota,
GroupRatioInfo: group,
FreeModel: free,
LegacyModelPrice: modelPrice,
UsePrice: true,
PerCallBilling: true,
Finalized: true,
}
}

func buildRemixMatrixDecision(origin *types.OriginPricingSnapshot, group types.GroupRatioInfo) (*types.PricingDecision, *dto.TaskError) {
if origin == nil {
return nil, localPricingTaskError("origin pricing snapshot is required", "invalid_pricing_snapshot")
}
billingUnit := origin.BillingUnit
if billingUnit == "" {
billingUnit = types.BillingUnitPerCall
}
result := &pricingLookupResult{
Mode: types.PricingModeRemix,
PriceUSD: origin.PriceUSD,
Snapshot: types.CloneMapAny(origin.Snapshot),
}
cfg := &types.PricingConfig{BillingUnit: billingUnit}
decision := buildMatrixDecision(cfg, result, group)
decision.Mode = types.PricingModeRemix
decision.TokenUnitPriceUSD = origin.TokenUnitPriceUSD
decision.OtherRatios = types.CloneRatios(origin.OtherRatios)
decision.PerCallBilling = origin.PerCallBilling
if billingUnit == types.BillingUnitPer1MTokens {
decision.PriceUSD = 0
decision.Quota = minimumUsagePreconsumeQuota(origin.TokenUnitPriceUSD, group.GroupRatio)
}
return decision, nil
}

func buildRemixLegacyDecision(origin *types.OriginPricingSnapshot, group types.GroupRatioInfo) (*types.PricingDecision, *dto.TaskError) {
if origin == nil {
return nil, localPricingTaskError("origin legacy pricing snapshot is required", "invalid_legacy_pricing_snapshot")
}
quota := int(origin.PriceUSD * common.QuotaPerUnit * group.GroupRatio)
return &types.PricingDecision{
Mode: types.PricingModeRemix,
PriceUSD: origin.PriceUSD,
Quota: quota,
GroupRatioInfo: group,
LegacyModelPrice: origin.PriceUSD,
OtherRatios: types.CloneRatios(origin.OtherRatios),
UsePrice: true,
PerCallBilling: origin.PerCallBilling,
Finalized: true,
}, nil
}

func buildMatrixDecision(cfg *types.PricingConfig, result *pricingLookupResult, group types.GroupRatioInfo) *types.PricingDecision {
billingUnit := cfg.BillingUnit
if billingUnit == "" {
billingUnit = types.BillingUnitPerCall
}

decision := &types.PricingDecision{
Mode: result.Mode,
BillingMode: types.BillingModeMatrix,
BillingUnit: billingUnit,
GroupRatioInfo: group,
Snapshot: types.CloneMapAny(result.Snapshot),
UsePrice: true,
Finalized: true,
}

if billingUnit == types.BillingUnitPer1MTokens {
decision.TokenUnitPriceUSD = result.PriceUSD
decision.Quota = minimumUsagePreconsumeQuota(result.PriceUSD, group.GroupRatio)
decision.PerCallBilling = false
return decision
}

decision.PriceUSD = result.PriceUSD
decision.Quota = int(result.PriceUSD * common.QuotaPerUnit * group.GroupRatio)
decision.PerCallBilling = true
if group.GroupRatio == 0 || result.PriceUSD == 0 {
decision.FreeModel = true
decision.Quota = 0
}
return decision
}

func fallbackPricingConfig(cfg *types.PricingConfig, resolved *ResolvedDimensionValues) (*pricingLookupResult, *dto.TaskError) {
switch cfg.Fallback.Strategy {
case types.PricingFallbackReject:
return nil, localPricingTaskError("pricing dimensions did not match any row", "pricing_no_match")
case types.PricingFallbackMax:
price := maxPricingTablePrice(cfg.Table)
return &pricingLookupResult{
Mode: types.PricingModeFallbackMax,
PriceUSD: price,
Snapshot: buildPricingSnapshot(cfg, resolved, price, types.PricingModeFallbackMax, cfg.Fallback.Strategy),
}, nil
case types.PricingFallbackDefault:
price := cfg.Fallback.DefaultPrice
return &pricingLookupResult{
Mode: types.PricingModeFallbackDefault,
PriceUSD: price,
Snapshot: buildPricingSnapshot(cfg, resolved, price, types.PricingModeFallbackDefault, cfg.Fallback.Strategy),
}, nil
default:
return nil, localPricingTaskError(fmt.Sprintf("unsupported fallback strategy %q", cfg.Fallback.Strategy), "invalid_pricing_config")
}
}

func buildPricingSnapshot(cfg *types.PricingConfig, resolved *ResolvedDimensionValues, price float64, mode types.PricingMode, fallbackStrategy string) map[string]any {
snapshot := make(map[string]any, len(cfg.Dimensions)+4)
for _, dim := range cfg.Dimensions {
snapshot[dim.Key] = resolved.Values[dim.Key]
}
snapshot["price"] = price
snapshot["billing_unit"] = cfg.BillingUnit
snapshot["pricing_mode"] = string(mode)
if fallbackStrategy != "" {
snapshot["fallback_strategy"] = fallbackStrategy
}
return snapshot
}

func rowMatchesDimensions(dimensions []types.PricingDimension, row types.PricingRow, values map[string]any) (int, bool) {
wildcards := 0
for _, dim := range dimensions {
rv := row[dim.Key]
if s, ok := rv.(string); ok && s == "*" {
wildcards++
continue
}
if !samePricingLiteral(rv, values[dim.Key]) {
return 0, false
}
}
return wildcards, true
}

func maxPricingTablePrice(rows []types.PricingRow) float64 {
var max float64
for i, row := range rows {
price, ok := pricingNumberValue(row["price"])
if !ok {
continue
}
if i == 0 || price > max {
max = price
}
}
return max
}

func minimumUsagePreconsumeQuota(tokenUnitPriceUSD float64, groupRatio float64) int {
if tokenUnitPriceUSD == 0 || groupRatio == 0 {
return 0
}
if common.PreConsumedQuota <= 0 {
return 0
}
quota := int(float64(common.PreConsumedQuota) * tokenUnitPriceUSD / 1_000_000 * common.QuotaPerUnit * groupRatio)
if quota <= 0 {
return 1
}
return quota
}

func priceDataFromDecision(decision *types.PricingDecision) types.PriceData {
if decision == nil {
return types.PriceData{}
}
return types.PriceData{
FreeModel: decision.FreeModel,
ModelPrice: decision.PriceUSD,
Quota: decision.Quota,
GroupRatioInfo: decision.GroupRatioInfo,
OtherRatios: types.CloneRatios(decision.OtherRatios),
UsePrice: decision.UsePrice,
PricingDecision: decision,
}
}

func pricingNumberValue(v any) (float64, bool) {
switch n := v.(type) {
case float64:
return n, true
case float32:
return float64(n), true
case int:
return float64(n), true
case int64:
return float64(n), true
case int32:
return float64(n), true
default:
return 0, false
}
}

func samePricingLiteral(a, b any) bool {
af, aok := pricingNumberValue(a)
bf, bok := pricingNumberValue(b)
if aok && bok {
return af == bf
}
return fmt.Sprintf("%v", a) == fmt.Sprintf("%v", b)
}

func localPricingTaskError(message string, code string) *dto.TaskError {
err := fmt.Errorf("%s", message)
return &dto.TaskError{
Code: code,
Message: message,
StatusCode: http.StatusBadRequest,
LocalError: true,
Error: err,
}
}

+ 119
- 0
relay/helper/pricing_lookup_test.go Просмотреть файл

@@ -0,0 +1,119 @@
package helper

import (
"testing"

"github.com/QuantumNous/new-api/common"
"github.com/QuantumNous/new-api/types"
"github.com/stretchr/testify/require"
)

func TestPricingLookup_MatchSpecificity(t *testing.T) {
cfg := testPricingLookupConfig(types.PricingFallbackReject)
resolved := &ResolvedDimensionValues{Values: map[string]any{"resolution": "720P", "duration": 10.0}}

result, taskErr := lookupPricingConfig(cfg, resolved)
require.Nil(t, taskErr)
require.Equal(t, types.PricingModeMatched, result.Mode)
require.Equal(t, 0.08, result.PriceUSD)
require.Equal(t, "720P", result.Snapshot["resolution"])
require.Equal(t, 10.0, result.Snapshot["duration"])
}

func TestPricingLookup_WildcardFallbackMatch(t *testing.T) {
cfg := testPricingLookupConfig(types.PricingFallbackReject)
resolved := &ResolvedDimensionValues{Values: map[string]any{"resolution": "480P", "duration": 10.0}}

result, taskErr := lookupPricingConfig(cfg, resolved)
require.Nil(t, taskErr)
require.Equal(t, 0.05, result.PriceUSD)
}

func TestPricingLookup_RejectFallback(t *testing.T) {
cfg := testPricingLookupConfig(types.PricingFallbackReject)
resolved := &ResolvedDimensionValues{Values: map[string]any{"resolution": "1080P", "duration": 15.0}}

result, taskErr := lookupPricingConfig(cfg, resolved)
require.Nil(t, result)
require.NotNil(t, taskErr)
require.Equal(t, "pricing_no_match", taskErr.Code)
}

func TestPricingLookup_MaxFallback(t *testing.T) {
cfg := testPricingLookupConfig(types.PricingFallbackMax)
resolved := &ResolvedDimensionValues{Values: map[string]any{"resolution": "1080P", "duration": 15.0}}

result, taskErr := lookupPricingConfig(cfg, resolved)
require.Nil(t, taskErr)
require.Equal(t, types.PricingModeFallbackMax, result.Mode)
require.Equal(t, 0.1, result.PriceUSD)
require.Equal(t, types.PricingFallbackMax, result.Snapshot["fallback_strategy"])
}

func TestPricingLookup_DefaultFallback(t *testing.T) {
cfg := testPricingLookupConfig(types.PricingFallbackDefault)
cfg.Fallback.DefaultPrice = 0.2
resolved := &ResolvedDimensionValues{Values: map[string]any{"resolution": "1080P", "duration": 15.0}}

result, taskErr := lookupPricingConfig(cfg, resolved)
require.Nil(t, taskErr)
require.Equal(t, types.PricingModeFallbackDefault, result.Mode)
require.Equal(t, 0.2, result.PriceUSD)
}

func TestPricingLookup_MissingDimensionSnapshotMarshalsNull(t *testing.T) {
cfg := testPricingLookupConfig(types.PricingFallbackDefault)
cfg.Fallback.DefaultPrice = 0.2
resolved := &ResolvedDimensionValues{
Values: map[string]any{"resolution": "1080P", "duration": nil},
Missing: map[string]bool{"duration": true},
}

result, taskErr := lookupPricingConfig(cfg, resolved)
require.Nil(t, taskErr)
body, err := common.Marshal(result.Snapshot)
require.NoError(t, err)
require.Contains(t, string(body), `"duration":null`)
}

func TestPricingLookup_DecisionQuota(t *testing.T) {
cfg := testPricingLookupConfig(types.PricingFallbackReject)
result := &pricingLookupResult{Mode: types.PricingModeMatched, PriceUSD: 0.1, Snapshot: map[string]any{"price": 0.1}}

decision := buildMatchedDecision(cfg, result, types.GroupRatioInfo{GroupRatio: 0.5})
require.Equal(t, types.BillingModeMatrix, decision.BillingMode)
require.Equal(t, types.BillingUnitPerCall, decision.BillingUnit)
require.Equal(t, int(0.1*common.QuotaPerUnit*0.5), decision.Quota)
require.True(t, decision.PerCallBilling)
}

func TestPricingLookup_UsageDecisionMinimumPreconsume(t *testing.T) {
cfg := testPricingLookupConfig(types.PricingFallbackReject)
cfg.BillingUnit = types.BillingUnitPer1MTokens
cfg.PreconsumeStrategy = types.PreconsumeStrategyMinimum
result := &pricingLookupResult{Mode: types.PricingModeMatched, PriceUSD: 2.0, Snapshot: map[string]any{"price": 2.0}}

decision := buildMatchedDecision(cfg, result, types.GroupRatioInfo{GroupRatio: 1})
require.Equal(t, 2.0, decision.TokenUnitPriceUSD)
require.Equal(t, minimumUsagePreconsumeQuota(2.0, 1), decision.Quota)
require.False(t, decision.PerCallBilling)
}

func testPricingLookupConfig(fallback string) *types.PricingConfig {
return &types.PricingConfig{
SchemaVersion: types.PricingSchemaVersion,
Scope: types.PricingScopeModel,
BillingUnit: types.BillingUnitPerCall,
PreconsumeStrategy: types.PreconsumeStrategyExact,
Dimensions: []types.PricingDimension{
{Key: "resolution", Source: "request.resolution", Type: "string"},
{Key: "duration", Source: "request.duration", Type: "number"},
},
Table: []types.PricingRow{
{"resolution": "720P", "duration": 10.0, "price": 0.08, "source": types.PricingRowSourceManual},
{"resolution": "720P", "duration": "*", "price": 0.1, "source": types.PricingRowSourceManual},
{"resolution": "*", "duration": 10.0, "price": 0.05, "source": types.PricingRowSourceManual},
},
Fallback: types.PricingFallback{Strategy: fallback},
}
}

+ 14
- 2
relay/mjproxy_handler.go Просмотреть файл

@@ -193,7 +193,13 @@ func RelaySwapFace(c *gin.Context, info *relaycommon.RelayInfo) *dto.MidjourneyR
} }
modelName := service.CovertMjpActionToModelName(constant.MjActionSwapFace) modelName := service.CovertMjpActionToModelName(constant.MjActionSwapFace)


priceData := helper.ModelPriceHelperPerCall(c, info)
priceData, taskErr := helper.ModelPriceHelperPerCall(c, info)
if taskErr != nil {
return &dto.MidjourneyResponse{
Code: 4,
Description: taskErr.Message,
}
}


userQuota, err := model.GetUserQuota(info.UserId, false) userQuota, err := model.GetUserQuota(info.UserId, false)
if err != nil { if err != nil {
@@ -494,7 +500,13 @@ func RelayMidjourneySubmit(c *gin.Context, relayInfo *relaycommon.RelayInfo) *dt


modelName := service.CovertMjpActionToModelName(midjRequest.Action) modelName := service.CovertMjpActionToModelName(midjRequest.Action)


priceData := helper.ModelPriceHelperPerCall(c, relayInfo)
priceData, taskErr := helper.ModelPriceHelperPerCall(c, relayInfo)
if taskErr != nil {
return &dto.MidjourneyResponse{
Code: 4,
Description: taskErr.Message,
}
}


userQuota, err := model.GetUserQuota(relayInfo.UserId, false) userQuota, err := model.GetUserQuota(relayInfo.UserId, false)
if err != nil { if err != nil {


+ 5
- 1
relay/relay_task.go Просмотреть файл

@@ -176,7 +176,11 @@ func RelayTaskSubmit(c *gin.Context, info *relaycommon.RelayInfo) (*TaskSubmitRe


// 4. 价格计算:基础模型价格 // 4. 价格计算:基础模型价格
info.OriginModelName = modelName info.OriginModelName = modelName
info.PriceData = helper.ModelPriceHelperPerCall(c, info)
priceData, pricingErr := helper.ModelPriceHelperPerCall(c, info)
if pricingErr != nil {
return nil, pricingErr
}
info.PriceData = priceData


// 5. 计费估算:让适配器根据用户请求提供 OtherRatios(时长、分辨率等) // 5. 计费估算:让适配器根据用户请求提供 OtherRatios(时长、分辨率等)
// 必须在 ModelPriceHelperPerCall 之后调用(它会重建 PriceData)。 // 必须在 ModelPriceHelperPerCall 之后调用(它会重建 PriceData)。


Загрузка…
Отмена
Сохранить