Skip to content

fix: prevent OpenRouter cache calculation with custom model ratios - #1590

Merged
Calcium-Ion merged 2 commits into
QuantumNous:alphafrom
yyhhyyyyyy:fix/openrouter-custom-ratio-billing
Aug 15, 2025
Merged

fix: prevent OpenRouter cache calculation with custom model ratios#1590
Calcium-Ion merged 2 commits into
QuantumNous:alphafrom
yyhhyyyyyy:fix/openrouter-custom-ratio-billing

Conversation

@yyhhyyyyyy

@yyhhyyyyyy yyhhyyyyyy commented Aug 14, 2025

Copy link
Copy Markdown
Contributor

close #1585

fix OpenRouter billing conflict with custom model ratios
the current logic is as follows:

  1. If the user hasn't set any custom multipliers (including setting one that's the same as the default) / prices, then use OpenRouter's price and reverse-calculate the token count.
  2. If the user has set a custom multiplier/price, then the custom setting takes precedence.
08497c1560e06e21dc96be4380c77277 3d5caf269e64e6bc2b080037e8500a69

Summary by CodeRabbit

  • Bug Fixes

    • Fixed quota/usage miscalculations for cache-creation token handling with custom model ratios or pricing.
    • Cache token deduction is skipped when custom settings are active and prevented from becoming negative or over-deducting.
  • Improvements

    • More accurate and predictable billing/usage reporting.
    • Safer edge-case handling by deducting cache tokens only when thresholds are met.
    • Better support for custom configurations without extra token charges.

@coderabbitai

coderabbitai Bot commented Aug 14, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

Adds a helper to detect non-default model ratios, uses it to set an isUsingCustomSettings flag in PostClaudeConsumeQuota, skips OpenRouter cache-creation token calculation when custom settings are used, and guards assignment to avoid negative or excessive cache token deductions.

Changes

Cohort / File(s) Summary
Quota and cache token handling
service/quota.go
Added private helper hasCustomModelRatio(modelName, currentRatio); compute `isUsingCustomSettings = priceData.UsePrice

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant QuotaService
    participant OpenRouterCache

    Client->>QuotaService: PostClaudeConsumeQuota(modelName, priceData, promptTokens)
    activate QuotaService
    QuotaService->>QuotaService: hasCustomModelRatio(modelName, priceData.ModelRatio)
    QuotaService->>QuotaService: isUsingCustomSettings = priceData.UsePrice || hasCustomModelRatio(...)
    alt Default settings
        QuotaService->>OpenRouterCache: compute maybeCacheCreationTokens
        OpenRouterCache-->>QuotaService: maybeCacheCreationTokens
        QuotaService->>QuotaService: if maybeCacheCreationTokens >= 0 and promptTokens >= maybeCacheCreationTokens then assign cacheCreationTokens
    else Custom settings
        QuotaService->>QuotaService: skip cacheCreationTokens calculation
    end
    QuotaService-->>Client: updated quota accounting
    deactivate QuotaService
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~15–20 minutes

Assessment against linked issues

Objective (issue#) Addressed Explanation
Ensure user-configured model redirection and custom model ratios are respected in pricing calculation so final price uses custom ratios (1585)

Poem

I nibble ratios, hop and chart,
If custom rules, I skip that part.
Tokens guarded, numbers neat,
No negative crumbs beneath my feet.
A rabbit counts — precise and smart. 🐇✨

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between d75e198 and 039b00d.

📒 Files selected for processing (1)
  • service/quota.go (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • service/quota.go
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • 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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@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: 1

🔭 Outside diff range comments (1)
service/quota.go (1)

256-266: Fix: assert usage.Cost's numeric type before comparing and clamp back-calculated tokens

Brief: usage.Cost is declared as any (dto/openai_response.go:229), so comparing usage.Cost != 0 is unreliable. rg shows the only direct comparison is in service/quota.go (around line 259). Fix by safely converting to a numeric value before the comparison and clamp the computed tokens.

Locations to update

  • dto/openai_response.go:229 — Cost any \json:"cost,omitempty"`(consider changing tofloat64` or adding a helper accessor)
  • service/quota.go: ~256-266 — replace the direct usage.Cost != 0 comparison with a safe type assertion/conversion and clamp the result

Suggested patch for service/quota.go:

 if relayInfo.ChannelType == constant.ChannelTypeOpenRouter {
   promptTokens -= cacheTokens
   isUsingCustomSettings := priceData.UsePrice || hasCustomModelRatio(modelName, priceData.ModelRatio)
-  if cacheCreationTokens == 0 && priceData.CacheCreationRatio != 1 && usage.Cost != 0 && !isUsingCustomSettings {
-    maybeCacheCreationTokens := CalcOpenRouterCacheCreateTokens(*usage, priceData)
-    if maybeCacheCreationTokens >= 0 && promptTokens >= maybeCacheCreationTokens {
-      cacheCreationTokens = maybeCacheCreationTokens
-    }
-  }
+  // Safely convert usage.Cost (declared as `any`) to a float for the zero check.
+  var costFloat float64
+  switch v := usage.Cost.(type) {
+  case float64:
+    costFloat = v
+  case float32:
+    costFloat = float64(v)
+  case int:
+    costFloat = float64(v)
+  case int64:
+    costFloat = float64(v)
+  case string:
+    if f, err := strconv.ParseFloat(v, 64); err == nil {
+      costFloat = f
+    }
+  case json.Number:
+    if f, err := v.Float64(); err == nil {
+      costFloat = f
+    }
+  default:
+    costFloat = 0
+  }
+
+  if cacheCreationTokens == 0 && priceData.CacheCreationRatio != 1 && costFloat != 0 && !isUsingCustomSettings {
+    maybeCacheCreationTokens := CalcOpenRouterCacheCreateTokens(*usage, priceData)
+    // Clamp to [0, promptTokens] to avoid negative or excessive deductions
+    if maybeCacheCreationTokens < 0 {
+      maybeCacheCreationTokens = 0
+    }
+    if maybeCacheCreationTokens > promptTokens {
+      maybeCacheCreationTokens = promptTokens
+    }
+    cacheCreationTokens = maybeCacheCreationTokens
+  }
   promptTokens -= cacheCreationTokens
 }

Notes

  • Add imports if needed: strconv and encoding/json.
  • Prefer a stronger type for Cost (e.g., change the DTO to float64 or add a central CostFloat() accessor) to avoid repeated runtime checks.

Evidence from verification

  • dto/openai_response.go:229 shows Cost any \json:"cost,omitempty"``
  • rg found the direct comparison only at service/quota.go:259
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these settings in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 223f0d0 and d75e198.

📒 Files selected for processing (1)
  • service/quota.go (2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-06-18T12:20:25.779Z
Learnt from: neotf
PR: QuantumNous/new-api#1120
File: service/quota.go:0-0
Timestamp: 2025-06-18T12:20:25.779Z
Learning: For OpenRouter integration: cacheTokens returned from upstream always belong to [0, promptTokens], meaning cacheTokens will never exceed the original promptTokens value. This constraint ensures that operations like `promptTokens -= cacheTokens` will not result in negative values.

Applied to files:

  • service/quota.go
🧬 Code Graph Analysis (1)
service/quota.go (1)
setting/ratio_setting/model_ratio.go (1)
  • GetDefaultModelRatioMap (398-400)
🔇 Additional comments (1)
service/quota.go (1)

258-258: LGTM: Correctly skips OpenRouter cache-creation back-calculation under custom settings

This aligns with the PR goal to avoid billing conflicts when custom price/model ratios are in effect.

Comment thread service/quota.go
Comment on lines +40 to +46
func hasCustomModelRatio(modelName string, currentRatio float64) bool {
defaultRatio, exists := ratio_setting.GetDefaultModelRatioMap()[modelName]
if !exists {
return true
}
return currentRatio != defaultRatio
}

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.

🛠️ Refactor suggestion

Avoid fragile float equality when detecting custom ratios

Direct float equality can misclassify defaults due to precision drift. Use a tolerance.

 func hasCustomModelRatio(modelName string, currentRatio float64) bool {
   defaultRatio, exists := ratio_setting.GetDefaultModelRatioMap()[modelName]
   if !exists {
     return true
   }
-  return currentRatio != defaultRatio
+  // Avoid false positives caused by floating-point rounding
+  return math.Abs(currentRatio-defaultRatio) > 1e-9
 }
📝 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
func hasCustomModelRatio(modelName string, currentRatio float64) bool {
defaultRatio, exists := ratio_setting.GetDefaultModelRatioMap()[modelName]
if !exists {
return true
}
return currentRatio != defaultRatio
}
func hasCustomModelRatio(modelName string, currentRatio float64) bool {
defaultRatio, exists := ratio_setting.GetDefaultModelRatioMap()[modelName]
if !exists {
return true
}
// Avoid false positives caused by floating-point rounding
return math.Abs(currentRatio-defaultRatio) > 1e-9
}
🤖 Prompt for AI Agents
In service/quota.go around lines 40 to 46, the function uses direct float
equality to detect custom ratios which is fragile; change the comparison to use
a tolerance (epsilon) and treat the ratio as custom when math.Abs(currentRatio -
defaultRatio) > epsilon (choose a small constant like 1e-9 or make it
configurable), ensure math is imported and consider handling NaN/Inf by treating
them as custom if encountered.

@Calcium-Ion

Copy link
Copy Markdown
Member

可以解决一下冲突嘛

@yyhhyyyyyy

Copy link
Copy Markdown
Contributor Author

@Calcium-Ion 已解决冲突

@Calcium-Ion
Calcium-Ion merged commit 5a2dad2 into QuantumNous:alpha Aug 15, 2025
3 checks passed
@yyhhyyyyyy
yyhhyyyyyy deleted the fix/openrouter-custom-ratio-billing branch April 8, 2026 01:13
x22x22 pushed a commit to x22x22/new-api that referenced this pull request Apr 24, 2026
…stom-ratio-billing

fix: prevent OpenRouter cache calculation with custom model ratios
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