Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions relay/common/relay_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"strings"

"github.com/gin-gonic/gin"
"github.com/samber/lo"
)

type HasPrompt interface {
Expand Down Expand Up @@ -128,6 +129,34 @@ func ValidateMultipartDirect(c *gin.Context, info *RelayInfo) *dto.TaskError {
action = constant.TaskActionGenerate
}
info.Action = action
model := form.Value["model"][0]
if strings.HasPrefix(model, "sora-2") {
seconds := 4
size := "720x1280"
if ss, ok := form.Value["seconds"]; ok {
sInt := common.String2Int(ss[0])
if sInt > seconds {
seconds = common.String2Int(ss[0])
}
}
Comment on lines +136 to +141

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Clarify seconds override logic.

The current logic only updates seconds if the form value is greater than the default (4). This means a user cannot explicitly request a shorter duration (e.g., 3 seconds) even if valid.

If the intent is to enforce a minimum of 4 seconds, consider:

 		if ss, ok := form.Value["seconds"]; ok {
 			sInt := common.String2Int(ss[0])
-			if sInt > seconds {
+			if sInt >= 4 {
 				seconds = common.String2Int(ss[0])
+			} else {
+				return createTaskError(fmt.Errorf("seconds must be at least 4"), "invalid_seconds", http.StatusBadRequest, true)
 			}
 		}

Or, if any positive value should be allowed, simply:

 		if ss, ok := form.Value["seconds"]; ok {
 			sInt := common.String2Int(ss[0])
-			if sInt > seconds {
-				seconds = common.String2Int(ss[0])
+			if sInt > 0 {
+				seconds = sInt
 			}
 		}

if s, ok := form.Value["size"]; ok {
size = s[0]
}

if model == "sora-2" && !lo.Contains([]string{"720x1280", "1280x720"}, size) {
return createTaskError(fmt.Errorf("sora-2 size is invalid"), "invalid_size", http.StatusBadRequest, true)
}
if model == "sora-2-pro" && !lo.Contains([]string{"720x1280", "1280x720", "1792x1024", "1024x1792"}, size) {
return createTaskError(fmt.Errorf("sora-2 size is invalid"), "invalid_size", http.StatusBadRequest, true)
}
Comment on lines +146 to +151

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Differentiate error messages per model.

Both sora-2 and sora-2-pro return the same error message "sora-2 size is invalid". For clarity, specify which model's size constraint was violated.

Apply this diff:

 		if model == "sora-2" && !lo.Contains([]string{"720x1280", "1280x720"}, size) {
-			return createTaskError(fmt.Errorf("sora-2 size is invalid"), "invalid_size", http.StatusBadRequest, true)
+			return createTaskError(fmt.Errorf("sora-2 size must be 720x1280 or 1280x720"), "invalid_size", http.StatusBadRequest, true)
 		}
 		if model == "sora-2-pro" && !lo.Contains([]string{"720x1280", "1280x720", "1792x1024", "1024x1792"}, size) {
-			return createTaskError(fmt.Errorf("sora-2 size is invalid"), "invalid_size", http.StatusBadRequest, true)
+			return createTaskError(fmt.Errorf("sora-2-pro size must be one of: 720x1280, 1280x720, 1792x1024, 1024x1792"), "invalid_size", http.StatusBadRequest, true)
 		}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if model == "sora-2" && !lo.Contains([]string{"720x1280", "1280x720"}, size) {
return createTaskError(fmt.Errorf("sora-2 size is invalid"), "invalid_size", http.StatusBadRequest, true)
}
if model == "sora-2-pro" && !lo.Contains([]string{"720x1280", "1280x720", "1792x1024", "1024x1792"}, size) {
return createTaskError(fmt.Errorf("sora-2 size is invalid"), "invalid_size", http.StatusBadRequest, true)
}
if model == "sora-2" && !lo.Contains([]string{"720x1280", "1280x720"}, size) {
return createTaskError(fmt.Errorf("sora-2 size must be 720x1288 or 1280x720"), "invalid_size", http.StatusBadRequest, true)
}
if model == "sora-2-pro" && !lo.Contains([]string{"720x1280", "1280x720", "1792x1024", "1024x1792"}, size) {
return createTaskError(fmt.Errorf("sora-2-pro size must be one of: 720x1280, 1280x720, 1792x1024, 1024x1792"), "invalid_size", http.StatusBadRequest, true)
}
🤖 Prompt for AI Agents
In relay/common/relay_utils.go around lines 146 to 151, the error message for
both checks currently says "sora-2 size is invalid"; change the message to
reflect the actual model that failed validation (e.g., use "sora-2 size is
invalid" for the sora-2 branch and "sora-2-pro size is invalid" for the
sora-2-pro branch, or interpolate model into the fmt.Errorf call) so the
returned error clearly identifies which model's size constraint was violated;
keep the same error code, http status and retriable flag when calling
createTaskError.

info.PriceData.OtherRatios = map[string]float64{
"seconds": float64(seconds),
"size": 1,
}
if lo.Contains([]string{"1792x1024", "1024x1792"}, size) {
info.PriceData.OtherRatios["size"] = 1.666667
}
}
Comment on lines +133 to +159

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify maximum duration limits for sora models.

The code validates size but not the upper bound for seconds. Ensure that OpenAI's sora-2 family models have documented maximum duration limits and enforce them to prevent overcharging or API errors.

Run the following query to check the official documentation:


🌐 Web query:

What are the maximum video duration limits for OpenAI sora-2 and sora-2-pro models?

💡 Result:

  • sora-2 (API): maximum clip length = 12 seconds. [1][2]
  • sora-2-pro (API): maximum clip length = 12 seconds. [1][2]

Note: the Sora product/app/interface can generate videos up to 20 seconds in some UIs, but the API model parameters for sora-2 / sora-2‑pro accept seconds values of 4, 8, or 12. [3][1]

Sources:
[1] OpenAI Sora prompting guide (API parameters: seconds = 4, 8, 12).
[2] Sora-2 prompting docs / community guide (model size/seconds options).
[3] OpenAI Help Center — Generating videos on Sora (notes Sora videos up to 20 seconds).


Enforce maximum duration limit for Sora models
After parsing seconds in relay/common/relay_utils.go, validate that it’s one of {4, 8, 12} (max 12 s); return an error if it exceeds 12 s.

🤖 Prompt for AI Agents
In relay/common/relay_utils.go around lines 133 to 159, the parsed `seconds` for
sora models must be restricted to the set {4, 8, 12} with a maximum of 12;
modify the parsing logic so after reading `seconds` from form.Value you convert
it once to an int, clamp or validate that it is one of the allowed values (4, 8,
or 12) and if it exceeds 12 or is not in the allowed set return
createTaskError(fmt.Errorf("sora-2 seconds is invalid"), "invalid_seconds",
http.StatusBadRequest, true); ensure you use the parsed int to set
info.PriceData.OtherRatios["seconds"] (as float64) and avoid calling String2Int
twice.


return nil
}
Expand Down
2 changes: 1 addition & 1 deletion relay/helper/price.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ func ModelPriceHelperPerCall(c *gin.Context, info *relaycommon.RelayInfo) types.
modelPrice, success := ratio_setting.GetModelPrice(info.OriginModelName, true)
// 如果没有配置价格,则使用默认价格
if !success {
defaultPrice, ok := ratio_setting.GetDefaultModelRatioMap()[info.OriginModelName]
defaultPrice, ok := ratio_setting.GetDefaultModelPriceMap()[info.OriginModelName]
if !ok {
modelPrice = 0.1
} else {
Expand Down
20 changes: 19 additions & 1 deletion relay/relay_task.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func RelayTaskSubmit(c *gin.Context, info *relaycommon.RelayInfo) (taskErr *dto.
}
modelPrice, success := ratio_setting.GetModelPrice(modelName, true)
if !success {
defaultPrice, ok := ratio_setting.GetDefaultModelRatioMap()[modelName]
defaultPrice, ok := ratio_setting.GetDefaultModelPriceMap()[modelName]
if !ok {
modelPrice = 0.1
} else {
Expand All @@ -70,6 +70,13 @@ func RelayTaskSubmit(c *gin.Context, info *relaycommon.RelayInfo) (taskErr *dto.
} else {
ratio = modelPrice * groupRatio
}
if len(info.PriceData.OtherRatios) > 0 {
for _, ra := range info.PriceData.OtherRatios {
if 1.0 != ra {
ratio *= ra
}
}
}
userQuota, err := model.GetUserQuota(info.UserId, false)
if err != nil {
taskErr = service.TaskErrorWrapper(err, "get_user_quota_failed", http.StatusInternalServerError)
Expand Down Expand Up @@ -143,6 +150,17 @@ func RelayTaskSubmit(c *gin.Context, info *relaycommon.RelayInfo) (taskErr *dto.
gRatio = userGroupRatio
}
logContent := fmt.Sprintf("模型固定价格 %.2f,分组倍率 %.2f,操作 %s", modelPrice, gRatio, info.Action)
if len(info.PriceData.OtherRatios) > 0 {
var contents []string
for key, ra := range info.PriceData.OtherRatios {
if 1.0 != ra {
contents = append(contents, fmt.Sprintf("%s: %.2f", key, ra))
}
}
if len(contents) > 0 {
logContent = fmt.Sprintf("%s, 计算参数:%s", logContent, strings.Join(contents, ", "))
}
}
other := make(map[string]interface{})
other["model_price"] = modelPrice
other["group_ratio"] = groupRatio
Expand Down
6 changes: 6 additions & 0 deletions setting/ratio_setting/model_ratio.go
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,8 @@ var defaultModelPrice = map[string]float64{
"mj_upscale": 0.05,
"swap_face": 0.05,
"mj_upload": 0.05,
"sora-2": 0.3,
"sora-2-pro": 0.5,
}

var defaultAudioRatio = map[string]float64{
Expand Down Expand Up @@ -452,6 +454,10 @@ func GetDefaultModelRatioMap() map[string]float64 {
return defaultModelRatio
}

func GetDefaultModelPriceMap() map[string]float64 {
return defaultModelPrice
}

func GetDefaultImageRatioMap() map[string]float64 {
return defaultImageRatio
}
Expand Down
1 change: 1 addition & 0 deletions types/price_data.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ type PriceData struct {
ImageRatio float64
AudioRatio float64
AudioCompletionRatio float64
OtherRatios map[string]float64
UsePrice bool
ShouldPreConsumedQuota int
GroupRatioInfo GroupRatioInfo
Expand Down