Skip to content

feat: sora 增加参数校验与计费 - #2006

Merged
Calcium-Ion merged 1 commit into
QuantumNous:mainfrom
xyfacai:feat/sora-price
Oct 11, 2025
Merged

feat: sora 增加参数校验与计费#2006
Calcium-Ion merged 1 commit into
QuantumNous:mainfrom
xyfacai:feat/sora-price

Conversation

@xyfacai

@xyfacai xyfacai commented Oct 11, 2025

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • New Features

    • Added support for sora-2 and sora-2-pro models, including default duration (4s) and size (720x1280) with form-based overrides.
    • Validates requested sizes per model, returning a clear error for invalid dimensions.
    • Pricing now adjusts based on additional parameters (e.g., duration/size), enabling more accurate per-request costs.
  • Chores

    • Updated default pricing for models, including new entries for sora-2 and sora-2-pro.
    • Expanded logs to include calculated pricing parameters for better traceability.

@coderabbitai

coderabbitai Bot commented Oct 11, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

Introduces sora-2/sora-2-pro handling in multipart validation, augments PriceData with OtherRatios, switches default pricing lookups from ratio to price maps, applies multi-parameter price adjustments during task submission, and exposes default model price map with new entries for sora models.

Changes

Cohort / File(s) Summary
Sora multipart validation
relay/common/relay_utils.go
Adds sora-2 family handling in ValidateMultipartDirect: parses seconds and size, validates allowed resolutions per model, updates PriceData.OtherRatios (seconds, size), and applies size weighting for 1792x1024/1024x1792.
Price data structure
types/price_data.go
Extends PriceData with field OtherRatios map[string]float64.
Default price map exposure
setting/ratio_setting/model_ratio.go
Adds GetDefaultModelPriceMap(); updates defaultModelPrice with "sora-2" (0.3) and "sora-2-pro" (0.5).
Per-call pricing helper
relay/helper/price.go
ModelPriceHelperPerCall now falls back to GetDefaultModelPriceMap() instead of GetDefaultModelRatioMap(); retains 0.1 only if default absent.
Relay task pricing and logging
relay/relay_task.go
Replaces default ratio lookup with price map; multiplies base ratio by all non-1.0 entries in PriceData.OtherRatios; extends logs to include these parameters; updates map calls accordingly.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant C as Client
  participant R as Relay (ValidateMultipartDirect)
  participant V as Validator
  participant P as PriceData

  C->>R: multipart/form-data (model, seconds?, size?)
  R->>V: Parse form fields
  V->>V: If model startsWith "sora-2"
  alt sora-2/sora-2-pro
    V->>V: Set defaults (seconds=4, size=720x1280)
    V->>V: Override from form if present
    V->>V: Validate size against allowed set
    alt invalid size
      V-->>R: error task_invalid_size
      R-->>C: 400 with error
    else valid
      V->>P: Update OtherRatios: {"seconds": s, "size": 1 or 1.666667}
      R-->>C: proceed
    end
  else other models
    V-->>R: proceed (existing flow)
    R-->>C: proceed
  end
Loading
sequenceDiagram
  autonumber
  participant RT as RelayTask
  participant RS as ratio_setting
  participant PH as Price Helper
  participant LG as Logger

  RT->>PH: Get per-call model price
  alt configured
    PH-->>RT: price
  else not configured
    PH->>RS: GetDefaultModelPriceMap()
    RS-->>PH: default price map
    PH-->>RT: default price or 0.1 if absent
  end
  RT->>RT: baseRatio = price
  RT->>RT: For each (k,v) in OtherRatios where v != 1.0: baseRatio *= v
  RT->>LG: log model, price, OtherRatios (non-1.0)
  RT-->>LG: deferred consume log includes parameters
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

I twitch my nose at pricing’s art,
New maps, new knobs, a careful start.
Sora sizes pass the gate,
Seconds count, we calculate.
Logs now sing the ratios true—
A rabbit’s ledger, neat and new. 🐇📜

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title succinctly captures the primary feature introduced by the changeset—adding parameter validation and billing logic for the Sora models—and directly aligns with the modifications across validation, pricing helpers, task submission, and ratio settings. It is concise, specific, and clearly informs reviewers of the key enhancement without extraneous details.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 3

🧹 Nitpick comments (1)
types/price_data.go (1)

32-34: Consider adding OtherRatios to ToSetting() for consistency.

The ToSetting() method doesn't include the new OtherRatios field, which may hinder debugging when these ratios are present.

Apply this diff to include OtherRatios in the output:

 func (p PriceData) ToSetting() string {
-	return fmt.Sprintf("ModelPrice: %f, ModelRatio: %f, CompletionRatio: %f, CacheRatio: %f, GroupRatio: %f, UsePrice: %t, CacheCreationRatio: %f, ShouldPreConsumedQuota: %d, ImageRatio: %f, AudioRatio: %f, AudioCompletionRatio: %f", p.ModelPrice, p.ModelRatio, p.CompletionRatio, p.CacheRatio, p.GroupRatioInfo.GroupRatio, p.UsePrice, p.CacheCreationRatio, p.ShouldPreConsumedQuota, p.ImageRatio, p.AudioRatio, p.AudioCompletionRatio)
+	return fmt.Sprintf("ModelPrice: %f, ModelRatio: %f, CompletionRatio: %f, CacheRatio: %f, GroupRatio: %f, UsePrice: %t, CacheCreationRatio: %f, ShouldPreConsumedQuota: %d, ImageRatio: %f, AudioRatio: %f, AudioCompletionRatio: %f, OtherRatios: %v", p.ModelPrice, p.ModelRatio, p.CompletionRatio, p.CacheRatio, p.GroupRatioInfo.GroupRatio, p.UsePrice, p.CacheCreationRatio, p.ShouldPreConsumedQuota, p.ImageRatio, p.AudioRatio, p.AudioCompletionRatio, p.OtherRatios)
 }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1031f1d and a54baf4.

📒 Files selected for processing (5)
  • relay/common/relay_utils.go (2 hunks)
  • relay/helper/price.go (1 hunks)
  • relay/relay_task.go (3 hunks)
  • setting/ratio_setting/model_ratio.go (2 hunks)
  • types/price_data.go (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
relay/common/relay_utils.go (2)
common/str.go (1)
  • String2Int (66-72)
types/price_data.go (1)
  • PriceData (11-24)
relay/relay_task.go (2)
setting/ratio_setting/model_ratio.go (1)
  • GetDefaultModelPriceMap (457-459)
types/price_data.go (1)
  • PriceData (11-24)
relay/helper/price.go (1)
setting/ratio_setting/model_ratio.go (1)
  • GetDefaultModelPriceMap (457-459)
🔇 Additional comments (8)
types/price_data.go (1)

20-20: LGTM! New field supports multi-parameter pricing.

The OtherRatios field enables flexible per-parameter price adjustments (e.g., seconds, size for sora models), correctly integrated with downstream price calculation logic.

setting/ratio_setting/model_ratio.go (2)

293-294: LGTM! Sora model pricing added.

The default prices for sora-2 ($0.3) and sora-2-pro ($0.5) are now defined, enabling proper billing for these models.


457-459: Add mutex lock to prevent race conditions.

Unlike GetModelPriceMap() (lines 368-372), this function accesses defaultModelPrice without acquiring a read lock. Since UpdateModelPriceByJSONString() (lines 385-394) can modify modelPriceMap and potentially affect shared state, concurrent reads without synchronization risk race conditions.

Apply this diff to add proper locking:

 func GetDefaultModelPriceMap() map[string]float64 {
+	modelPriceMapMutex.RLock()
+	defer modelPriceMapMutex.RUnlock()
 	return defaultModelPrice
 }

Likely an incorrect or invalid review comment.

relay/helper/price.go (1)

117-122: LGTM! Default price lookup now uses the price map.

The switch from GetDefaultModelRatioMap() to GetDefaultModelPriceMap() aligns default pricing retrieval with the per-call pricing path, ensuring consistency across the codebase.

relay/common/relay_utils.go (1)

13-13: LGTM! Lo library import added.

The lo.Contains() helper (used on lines 146, 149, 156) provides cleaner slice membership checks compared to manual loops. Based on learnings.

relay/relay_task.go (3)

56-62: LGTM! Default price lookup now uses the price map.

The switch from GetDefaultModelRatioMap() to GetDefaultModelPriceMap() ensures consistent default pricing across per-call and token-based billing paths.


73-79: LGTM! Multi-parameter price adjustments applied.

The code correctly multiplies the base ratio by each non-1.0 value in OtherRatios, enabling flexible pricing for parameters like seconds and size.


153-163: LGTM! Enhanced logging includes parameter details.

The log content now includes calculated parameters from OtherRatios, improving observability for multi-parameter pricing adjustments.

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

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.

Comment on lines +136 to +141
if ss, ok := form.Value["seconds"]; ok {
sInt := common.String2Int(ss[0])
if sInt > seconds {
seconds = common.String2Int(ss[0])
}
}

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
 			}
 		}

Comment on lines +146 to +151
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)
}

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.

@Calcium-Ion
Calcium-Ion merged commit eab768b into QuantumNous:main Oct 11, 2025
1 check passed
@coderabbitai coderabbitai Bot mentioned this pull request Oct 11, 2025
@coderabbitai coderabbitai Bot mentioned this pull request Dec 23, 2025
Closed
5 tasks
ennnnny pushed a commit to ennnnny/new-api that referenced this pull request Mar 17, 2026
feat: sora 增加参数校验与计费
jiutubaba pushed a commit to jiutubaba/fx-api that referenced this pull request May 17, 2026
…xplicit-session

fix(openai): avoid implicit image sticky sessions
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants