Segmented Ratio Implementation Summary - #2173
Conversation
|
Warning Rate limit exceeded@wans10 has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 3 minutes and 47 seconds before requesting another review. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. 📒 Files selected for processing (4)
WalkthroughA segmented ratio pricing feature is added, enabling different model pricing rules based on input/output token ranges. Backend provides CRUD APIs, evaluation logic, and database persistence; frontend offers comprehensive UI for configuration management and pricing display across the application. Changes
Sequence Diagram(s)sequenceDiagram
participant UI as Frontend UI
participant API as API Handler
participant DB as Database
participant Memory as In-Memory<br/>State
participant Eval as Evaluation<br/>Engine
rect rgb(220, 240, 255)
Note over UI,Eval: Segmented Ratio Configuration Flow
UI->>API: POST /api/segmented_ratio/<br/>(model_name, rules, enabled)
API->>API: Validate per-rule constraints
API->>DB: Persist as JSON to option
DB-->>API: ✓ Persisted
API->>Memory: UpdateSegmentedRatioByJSONString
Memory->>Memory: Sort rules by Priority
Memory->>Memory: Update segmentedRatioMap
Memory-->>API: ✓ Updated
API-->>UI: 200 OK
end
rect rgb(220, 255, 220)
Note over UI,Eval: Token Consumption & Pricing Flow
Eval->>Eval: consumeTokens(modelName, tokens)
Eval->>Memory: EvaluateSegmentedRatio<br/>(modelName, inputTokens, outputTokens)
alt Rule Matches
Memory->>Memory: matchesSegmentRule(rule, tokens)
Memory-->>Eval: (modelRatio, completionRatio, matched=true)
else No Match
Memory-->>Eval: (0, 0, matched=false)
end
alt Segmented Match Found
Eval->>Eval: Use segmented ratios
else Fallback
Eval->>Eval: Use fixed ratios
end
Eval-->>Eval: Calculate final quota
end
rect rgb(255, 240, 220)
Note over UI,Eval: Data Retrieval & Display
UI->>API: GET /api/segmented_ratio/
API->>Memory: GetSegmentedRatioCopy()
Memory-->>API: Deep copy of configs
API-->>UI: [configs]
UI->>UI: Render in SegmentedRatioEditor
UI->>UI: formatSegmentRuleDescription(rule)
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Areas requiring extra attention:
Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 inconclusive)
✅ Passed checks (2 passed)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 8
🧹 Nitpick comments (10)
web/src/components/settings/RatioSetting.jsx (1)
19-19: Remove trailing whitespace.Line 19 contains only whitespace which should be removed for code cleanliness.
web/src/components/table/model-pricing/view/table/PricingTableColumns.jsx (1)
238-244: Remove leftover debug loggingThese
console.logcalls will spam production consoles for every render of those models. Please drop them or guard them behind a debug flag before merging.web/src/helpers/utils.jsx (1)
715-726: Drop verbose console logsThese debug prints fire for every segmented model and will flood user consoles. Please remove them or guard them behind a development check before shipping.
Apply this cleanup:
- console.log('[calculateModelPrice] 检测到分段定价模型:', record.model_name); - console.log('[calculateModelPrice] 分段规则数量:', record.segmented_rules.length); - console.log('[calculateModelPrice] 分段规则详情:', record.segmented_rules); ... - console.log('[calculateModelPrice] 计算后的分段价格:', segmentedPrices);controller/segmented_ratio.go (1)
78-111: Consider validating overlapping or conflicting rules.While the current validation checks individual rule constraints, it doesn't detect overlapping token ranges that could lead to ambiguous matches. This could cause unexpected behavior when multiple rules match the same token counts.
Consider adding validation to detect overlapping ranges and warn users about potential conflicts, or document that the priority field resolves ambiguity.
web/src/pages/Setting/Ratio/SegmentedRatioEditor.jsx (3)
64-78: Remove unused exclusive/inclusive interval flags.The
initialRulesincludeinput_min_exclusive,input_max_exclusive,output_min_exclusive, andoutput_max_exclusiveflags, but these fields are not present in the backendSegmentRulestruct (setting/ratio_setting/segmented_ratio.go lines 34-38) and are never used in the API submission (line 127). The UI labels suggest open/closed intervals (">", "≤"), but the backend doesn't implement this behavior.Either:
- Remove the unused flags from the frontend if interval notation is not needed, or
- Implement support for these flags in the backend
SegmentRulestruct and evaluation logic if precise interval control is a requirement.Apply this diff if removing:
const initialRules = [ { input_min: 0, input_max: 32000, - input_min_exclusive: true, - input_max_exclusive: false, output_min: 0, output_max: 200000, - output_min_exclusive: true, - output_max_exclusive: false, model_ratio: 0.4, completion_ratio: 2.5, priority: 100, }, ];Apply the same change to lines 140-162 in
addRule.
373-408: Update form labels to match actual interval behavior.The form labels hardcode interval notation symbols ("输入最小值 (>)", "输入最大值 (≤)") that suggest precise open/closed interval semantics, but the backend implementation doesn't support exclusive/inclusive flags. This could mislead users about the actual matching behavior.
If the exclusive/inclusive flags are not implemented in the backend, update the labels to be neutral:
-<Form.InputNumber field={`rules[${index}].input_min`} label="输入最小值 (>)" /> +<Form.InputNumber field={`rules[${index}].input_min`} label="输入最小值" />Apply similar changes to lines 384, 395, and 403. Also update the
formatTokenRangefunction (lines 184-185) to remove the hardcoded "min < x ≤ max" format if the backend doesn't enforce these semantics.
113-138: Consider adding client-side validation for rule ranges.While the backend validates rule constraints, adding client-side validation would provide immediate feedback and improve the user experience by catching errors before submission.
Consider validating:
- Token ranges are non-negative
input_min <= input_max(when max > 0)output_min <= output_max(when max > 0)- Ratios are non-negative
web/src/pages/Setting/Ratio/UnifiedRatioEditor.jsx (3)
55-80: Remove console.log statements before production.Multiple console.log statements are present throughout the component (lines 55-80, 89-122, 130-142, 322-351, and others), which appear to be debugging artifacts. These should be removed or replaced with a proper logging solution before deployment.
Also applies to: 89-122, 130-142, 322-351
46-49: Remove unusedRMB_RATEconstant.The
RMB_RATEconstant (line 49) is defined but never used in the component. The CNY conversion logic directly usesUSD_TO_CNY_RATEinstead.Apply this diff:
const USD_TO_CNY_RATE = 7.3; const USD_RATE = 500; -const RMB_RATE = USD_RATE / USD_TO_CNY_RATE;
805-810: Complex form key suggests potential state management issue.The form's key prop includes mode flags (
pricingSubMode,segmentedPricingSubMode) to force re-mounting when switching modes. This pattern often indicates form state is not properly synchronized with mode changes, relying on component remounting as a workaround.Consider refactoring to properly reset form state when modes change, rather than relying on key changes to remount the component. This would be more explicit and easier to maintain:
useEffect(() => { if (visible && formRef.current) { // Reset form values when modes change const values = ratioMode === 'fixed' ? getFixedRatioInitValues() : { /* segmented values */ }; formRef.current.setValues(values); } }, [ratioMode, pricingSubMode, segmentedPricingSubMode, visible]);
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (19)
controller/option.go(1 hunks)controller/segmented_ratio.go(1 hunks)model/option.go(2 hunks)model/pricing.go(2 hunks)relay/compatible_handler.go(2 hunks)relay/helper/price.go(2 hunks)router/api-router.go(1 hunks)setting/ratio_setting/model_ratio.go(1 hunks)setting/ratio_setting/segmented_ratio.go(1 hunks)types/price_data.go(2 hunks)web/src/components/settings/RatioSetting.jsx(3 hunks)web/src/components/table/model-pricing/modal/components/ModelPricingTable.jsx(3 hunks)web/src/components/table/model-pricing/view/table/PricingTableColumns.jsx(4 hunks)web/src/helpers/utils.jsx(5 hunks)web/src/hooks/model-pricing/useModelPricingData.jsx(1 hunks)web/src/pages/Setting/Ratio/ModelRationNotSetEditor.jsx(3 hunks)web/src/pages/Setting/Ratio/ModelSettingsVisualEditor.jsx(1 hunks)web/src/pages/Setting/Ratio/SegmentedRatioEditor.jsx(1 hunks)web/src/pages/Setting/Ratio/UnifiedRatioEditor.jsx(1 hunks)
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: 9Ninety
Repo: QuantumNous/new-api PR: 1273
File: relay/channel/gemini/relay-gemini.go:97-116
Timestamp: 2025-06-21T03:37:41.726Z
Learning: In relay/channel/gemini/relay-gemini.go, the thinking budget calculation logic (including the MaxOutputTokens multiplication) was introduced in PR #1247. PR #1273 focused specifically on decoupling the thoughts summary feature from thinking budget settings and did not modify the existing thinking budget behavior.
📚 Learning: 2025-06-21T03:37:41.726Z
Learnt from: 9Ninety
Repo: QuantumNous/new-api PR: 1273
File: relay/channel/gemini/relay-gemini.go:97-116
Timestamp: 2025-06-21T03:37:41.726Z
Learning: In relay/channel/gemini/relay-gemini.go, the thinking budget calculation logic (including the MaxOutputTokens multiplication) was introduced in PR #1247. PR #1273 focused specifically on decoupling the thoughts summary feature from thinking budget settings and did not modify the existing thinking budget behavior.
Applied to files:
relay/compatible_handler.gorelay/helper/price.go
📚 Learning: 2025-08-05T17:14:17.246Z
Learnt from: neotf
Repo: QuantumNous/new-api PR: 1511
File: setting/ratio_setting/model_ratio.go:118-123
Timestamp: 2025-08-05T17:14:17.246Z
Learning: Claude models handle "-thinking" variants differently from Gemini models. For Claude models, only the base model (without "-thinking") gets an entry in defaultModelRatio map. The "-thinking" variants rely on the Claude relay handler stripping the suffix using strings.TrimSuffix(textRequest.Model, "-thinking") before looking up the ratio, so they automatically use the base model's ratio.
Applied to files:
relay/compatible_handler.gorelay/helper/price.go
📚 Learning: 2025-08-27T02:15:25.448Z
Learnt from: AAEE86
Repo: QuantumNous/new-api PR: 1658
File: web/src/components/table/channels/modals/EditChannelModal.jsx:555-569
Timestamp: 2025-08-27T02:15:25.448Z
Learning: In EditChannelModal.jsx, the applyModelMapping function transforms the models list by replacing original model names (mapping values) with display names (mapping keys). The database stores this transformed list containing mapped keys. On channel load, data.models contains these mapped display names, making the initialization filter if (data.models.includes(key)) correct.
Applied to files:
web/src/pages/Setting/Ratio/ModelRationNotSetEditor.jsx
🧬 Code graph analysis (15)
web/src/components/table/model-pricing/modal/components/ModelPricingTable.jsx (2)
web/src/helpers/utils.jsx (4)
calculateModelPrice(674-799)calculateModelPrice(674-799)formatSegmentRuleDescription(595-633)formatSegmentRuleDescription(595-633)web/src/hooks/model-pricing/useModelPricingData.jsx (3)
tokenUnit(46-46)groupRatio(50-50)usableGroup(51-51)
controller/option.go (1)
setting/ratio_setting/segmented_ratio.go (1)
UpdateSegmentedRatioByJSONString(119-138)
relay/compatible_handler.go (2)
types/price_data.go (1)
PriceData(11-28)setting/ratio_setting/segmented_ratio.go (1)
EvaluateSegmentedRatio(142-156)
model/pricing.go (3)
setting/ratio_setting/model_ratio.go (3)
CompletionRatio(320-320)GetModelRatio(436-447)GetCompletionRatio(505-524)constant/endpoint_type.go (1)
EndpointType(3-3)setting/ratio_setting/segmented_ratio.go (2)
SegmentRule(12-32)GetSegmentedRatio(72-77)
web/src/pages/Setting/Ratio/SegmentedRatioEditor.jsx (2)
web/src/pages/Setting/Ratio/UnifiedRatioEditor.jsx (15)
configs(34-34)editingConfig(37-37)searchText(38-38)formRules(39-39)formRef(44-44)loadConfigs(125-208)handleAdd(299-306)handleEdit(321-379)handleDelete(381-452)handleSubmit(454-572)addRule(596-613)removeRule(615-619)formatTokenRange(621-632)columns(656-754)filteredConfigs(756-758)web/src/helpers/utils.jsx (4)
showError(122-151)showSuccess(157-159)i(468-468)i(480-480)
web/src/helpers/utils.jsx (1)
web/src/hooks/model-pricing/useModelPricingData.jsx (3)
displayPrice(174-186)tokenUnit(46-46)currency(44-44)
controller/segmented_ratio.go (2)
setting/ratio_setting/segmented_ratio.go (7)
GetSegmentedRatio(72-77)GetSegmentedRatioCopy(88-103)SegmentedRatioConfig(35-39)SetSegmentedRatio(55-69)SegmentedRatio2JSONString(106-116)DeleteSegmentedRatio(80-85)UpdateSegmentedRatioByJSONString(119-138)model/option.go (1)
UpdateOption(177-191)
setting/ratio_setting/model_ratio.go (1)
setting/ratio_setting/segmented_ratio.go (1)
InitSegmentedRatio(48-52)
model/option.go (2)
common/constants.go (1)
OptionMap(37-37)setting/ratio_setting/segmented_ratio.go (2)
SegmentedRatio2JSONString(106-116)UpdateSegmentedRatioByJSONString(119-138)
web/src/components/table/model-pricing/view/table/PricingTableColumns.jsx (2)
web/src/components/table/model-pricing/view/card/PricingCardView.jsx (1)
isMobile(85-85)web/src/helpers/utils.jsx (2)
formatSegmentRuleDescription(595-633)formatSegmentRuleDescription(595-633)
router/api-router.go (3)
middleware/auth.go (1)
RootAuth(169-173)controller/segmented_ratio.go (6)
GetAllSegmentedRatios(40-47)GetSegmentedRatio(13-37)CreateOrUpdateSegmentedRatio(50-131)DeleteSegmentedRatio(134-161)ExportSegmentedRatios(164-171)ImportSegmentedRatios(174-210)setting/ratio_setting/segmented_ratio.go (2)
GetSegmentedRatio(72-77)DeleteSegmentedRatio(80-85)
web/src/components/settings/RatioSetting.jsx (1)
web/src/pages/Setting/Ratio/UnifiedRatioEditor.jsx (1)
UnifiedRatioEditor(32-1266)
setting/ratio_setting/segmented_ratio.go (3)
setting/ratio_setting/exposed_cache.go (1)
InvalidateExposedDataCache(23-25)controller/segmented_ratio.go (2)
GetSegmentedRatio(13-37)DeleteSegmentedRatio(134-161)common/sys_log.go (1)
SysError(16-19)
web/src/pages/Setting/Ratio/UnifiedRatioEditor.jsx (3)
web/src/pages/Setting/Ratio/SegmentedRatioEditor.jsx (15)
configs(32-32)editingConfig(35-35)searchText(36-36)formRules(37-37)formRef(38-38)loadConfigs(41-56)handleAdd(62-88)handleEdit(90-97)handleDelete(99-111)handleSubmit(113-138)addRule(140-162)removeRule(164-168)formatTokenRange(170-186)columns(188-262)filteredConfigs(264-266)web/src/pages/Setting/Ratio/ModelSettingsVisualEditor.jsx (4)
searchText(49-49)formRef(55-55)calculateCompletionRatioFromPrices(281-290)columns(179-254)web/src/helpers/utils.jsx (4)
showError(122-151)showSuccess(157-159)i(468-468)i(480-480)
relay/helper/price.go (6)
common/utils.go (1)
Max(275-281)common/constants.go (1)
PreConsumedQuota(106-106)setting/ratio_setting/segmented_ratio.go (1)
EvaluateSegmentedRatio(142-156)setting/ratio_setting/model_ratio.go (2)
GetModelRatio(436-447)GetCompletionRatio(505-524)dto/user_settings.go (1)
UserSetting(3-16)types/price_data.go (1)
PriceData(11-28)
🔇 Additional comments (15)
web/src/components/settings/RatioSetting.jsx (1)
29-29: LGTM: Import statement is correct.The import follows the same pattern as other component imports in the file.
relay/helper/price.go (1)
72-101: Segmented ratio integration LGTMThe segmented branch cleanly reuses EvaluateSegmentedRatio, and the fallback keeps the legacy ratio handling intact. Looks good to me.
web/src/components/table/model-pricing/modal/components/ModelPricingTable.jsx (1)
43-129: Segmented pricing table renders correctlyThe per-group segmented table wiring matches the new calculateModelPrice shape and the UI fallbacks look solid.
web/src/components/table/model-pricing/view/table/PricingTableColumns.jsx (1)
246-267: Segmented pricing column looks goodThe segmented branch renders each rule with description and per-unit pricing cleanly—nice addition.
model/pricing.go (1)
291-309: Backend segmented fallback LGTMGreat to see segmented configs surfaced here with the first-rule defaults and full rule list for the UI; fallback to legacy ratios remains intact.
controller/segmented_ratio.go (4)
13-37: LGTM!The handler correctly validates the model name parameter and returns appropriate error responses for missing or non-existent configurations.
40-47: LGTM!The handler correctly retrieves all segmented ratio configurations using a defensive copy to prevent external modification of the internal state.
134-161: LGTM!The handler correctly validates the model name and persists the deletion to the database.
164-171: LGTM!The export handler correctly serializes all configurations to JSON.
web/src/pages/Setting/Ratio/SegmentedRatioEditor.jsx (1)
41-56: LGTM!The
loadConfigsfunction correctly handles the API response and converts the map to an array for display.web/src/pages/Setting/Ratio/UnifiedRatioEditor.jsx (5)
214-237: LGTM!The currency conversion functions correctly implement the pricing formula: 1 ratio = $2.0 per 1M tokens. The CNY conversions appropriately go through USD as an intermediate step.
125-208: LGTM!The
loadConfigsfunction correctly merges configurations from both the segmented ratio API and the system options API, properly avoiding duplicates when a model has both types of configurations.
454-572: LGTM!The
handleSubmitfunction correctly handles both fixed and segmented ratio modes, with appropriate validation and API routing for each mode. The parallel PUT requests for fixed mode are acceptable for this use case.
635-654: LGTM!The
checkIsFixedModefunction correctly determines the configuration mode using explicit flags and sensible heuristics as fallbacks.
1144-1249: LGTM!The price input modes correctly convert user-entered prices to ratios and maintain hidden ratio fields for submission. This approach provides a better UX while preserving the backend's ratio-based data model.
| const formatTokenRange = (min, max) => { | ||
| const formatNum = (num) => { | ||
| if (num === 0) return '无限制'; | ||
| if (num >= 1000) return `${(num / 1000).toFixed(0)}K`; | ||
| return num; | ||
| }; | ||
|
|
||
| if (min === 0 && max === 0) return '无限制'; | ||
| if (min === 0) return `≤ ${formatNum(max)}`; | ||
| if (max === 0) return `> ${formatNum(min)}`; | ||
| return `${formatNum(min)} ~ ${formatNum(max)}`; | ||
| }; |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Extract shared utility functions to reduce duplication.
The formatTokenRange function (lines 621-632), addRule function (lines 596-613), and removeRule function (lines 615-619) are duplicated in SegmentedRatioEditor.jsx (lines 170-186, 140-162, 164-168). This violates the DRY principle and makes maintenance harder.
Extract these utilities to a shared module:
// web/src/helpers/ratioUtils.jsx
export const formatTokenRange = (min, max) => {
const formatNum = (num) => {
if (num === 0) return '无限制';
if (num >= 1000) return `${(num / 1000).toFixed(0)}K`;
return num;
};
if (min === 0 && max === 0) return '无限制';
if (min === 0) return `≤ ${formatNum(max)}`;
if (max === 0) return `> ${formatNum(min)}`;
return `${formatNum(min)} ~ ${formatNum(max)}`;
};Then import and use in both components.
🤖 Prompt for AI Agents
In web/src/pages/Setting/Ratio/UnifiedRatioEditor.jsx around lines 621-632 (and
corresponding duplicated functions in SegmentedRatioEditor.jsx lines ~140-186),
the formatTokenRange, addRule, and removeRule functions are duplicated; extract
them into a shared helper module (e.g., web/src/helpers/ratioUtils.jsx) that
exports formatTokenRange, addRule, and removeRule, replace the inline
definitions in both UnifiedRatioEditor.jsx and SegmentedRatioEditor.jsx with
imports from that helper, update any relative import paths, and remove the
now-duplicate code blocks so both components use the single shared
implementations.
There was a problem hiding this comment.
Actionable comments posted: 5
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
controller/segmented_ratio.go(1 hunks)setting/ratio_setting/segmented_ratio.go(1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: 9Ninety
Repo: QuantumNous/new-api PR: 1273
File: relay/channel/gemini/relay-gemini.go:97-116
Timestamp: 2025-06-21T03:37:41.726Z
Learning: In relay/channel/gemini/relay-gemini.go, the thinking budget calculation logic (including the MaxOutputTokens multiplication) was introduced in PR #1247. PR #1273 focused specifically on decoupling the thoughts summary feature from thinking budget settings and did not modify the existing thinking budget behavior.
🧬 Code graph analysis (2)
controller/segmented_ratio.go (3)
setting/ratio_setting/segmented_ratio.go (7)
GetSegmentedRatio(72-77)GetSegmentedRatioCopy(88-103)SegmentedRatioConfig(35-39)SetSegmentedRatio(55-69)SegmentedRatio2JSONString(106-116)DeleteSegmentedRatio(80-85)UpdateSegmentedRatioByJSONString(119-138)controller/option.go (1)
UpdateOption(45-224)model/option.go (1)
UpdateOption(177-191)
setting/ratio_setting/segmented_ratio.go (2)
setting/ratio_setting/exposed_cache.go (1)
InvalidateExposedDataCache(23-25)common/sys_log.go (1)
SysError(16-19)
🔇 Additional comments (1)
setting/ratio_setting/segmented_ratio.go (1)
159-221: LGTM: Boundary checking logic is correct.The inclusive/exclusive boundary logic for input and output token ranges is implemented correctly:
- Exclusive min (
>) checks:tokens <= minreturns false- Inclusive min (
>=) checks:tokens < minreturns false- Exclusive max (
<) checks:tokens >= maxreturns false- Inclusive max (
<=) checks:tokens > maxreturns falseThe zero-value convention (0 = no limit) is clear from the conditional guards.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
web/src/helpers/utils.jsx (1)
642-678: Extract currency symbol logic to eliminate duplication.The currency symbol extraction logic (lines 656-671) is duplicated in
calculateModelPrice(lines 759-774). This duplication makes maintenance harder and increases the risk of inconsistencies.Consider extracting this into a shared helper function:
+// 获取货币符号 +const getCurrencySymbol = (currency) => { + if (currency === 'USD') { + return '$'; + } else if (currency === 'CNY') { + return '¥'; + } else if (currency === 'CUSTOM') { + try { + const statusStr = localStorage.getItem('status'); + if (statusStr) { + const s = JSON.parse(statusStr); + return s?.custom_currency_symbol || '¤'; + } + return '¤'; + } catch (e) { + return '¤'; + } + } + return '$'; +}; + const calculateSegmentRulePrice = (rule, usedGroupRatio, displayPrice, tokenUnit, currency, precision) => { const inputRatioPriceUSD = rule.model_ratio * 2 * usedGroupRatio; const completionRatioPriceUSD = rule.model_ratio * rule.completion_ratio * 2 * usedGroupRatio; const unitDivisor = tokenUnit === 'K' ? 1000 : 1; const unitLabel = tokenUnit === 'K' ? 'K' : 'M'; const rawDisplayInput = displayPrice(inputRatioPriceUSD); const rawDisplayCompletion = displayPrice(completionRatioPriceUSD); const numInput = parseFloat(rawDisplayInput.replace(/[^0-9.]/g, '')) / unitDivisor; const numCompletion = parseFloat(rawDisplayCompletion.replace(/[^0-9.]/g, '')) / unitDivisor; - let symbol = '$'; - if (currency === 'CNY') { - symbol = '¥'; - } else if (currency === 'CUSTOM') { - try { - const statusStr = localStorage.getItem('status'); - if (statusStr) { - const s = JSON.parse(statusStr); - symbol = s?.custom_currency_symbol || '¤'; - } else { - symbol = '¤'; - } - } catch (e) { - symbol = '¤'; - } - } + const symbol = getCurrencySymbol(currency); return { inputPrice: `${symbol}${numInput.toFixed(precision)}`, completionPrice: `${symbol}${numCompletion.toFixed(precision)}`, unitLabel, }; };Then update
calculateModelPriceto use the same helper at lines 759-774.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
web/src/helpers/utils.jsx(5 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: 9Ninety
Repo: QuantumNous/new-api PR: 1273
File: relay/channel/gemini/relay-gemini.go:97-116
Timestamp: 2025-06-21T03:37:41.726Z
Learning: In relay/channel/gemini/relay-gemini.go, the thinking budget calculation logic (including the MaxOutputTokens multiplication) was introduced in PR #1247. PR #1273 focused specifically on decoupling the thoughts summary feature from thinking budget settings and did not modify the existing thinking budget behavior.
🧬 Code graph analysis (1)
web/src/helpers/utils.jsx (1)
web/src/hooks/model-pricing/useModelPricingData.jsx (3)
displayPrice(174-186)tokenUnit(46-46)currency(44-44)
🔇 Additional comments (2)
web/src/helpers/utils.jsx (2)
594-640: Previous feedback correctly implemented.The token formatting logic now properly preserves sub-1k values and shows k-values with at most one decimal, addressing the misleading segment range formatting issue from the previous review.
810-835: Well-designed segmented pricing display for card view.The implementation appropriately shows only the first segment rule in card view to keep the display compact, while indicating the presence of additional rules. The formatting and layout are consistent with the existing code style.
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
controller/segmented_ratio.go (1)
107-137: Previous feedback addressed – DB persistence now precedes in-memory update.The ordering is now correct: configuration is persisted to the database (lines 107-127) before updating the in-memory state (line 130), preventing inconsistency if the DB write fails.
Note:
model.UpdateOptionat line 120 likely triggersupdateOptionMap, which internally callsUpdateSegmentedRatioByJSONStringto update in-memory state. Line 130 then updates in-memory state again viaSetSegmentedRatio. This double-update is redundant but harmless. Consider refactoring to update memory only once if you want to optimize the write path.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
controller/segmented_ratio.go(1 hunks)web/src/pages/Setting/Ratio/ModelRationNotSetEditor.jsx(3 hunks)
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: 9Ninety
Repo: QuantumNous/new-api PR: 1273
File: relay/channel/gemini/relay-gemini.go:97-116
Timestamp: 2025-06-21T03:37:41.726Z
Learning: In relay/channel/gemini/relay-gemini.go, the thinking budget calculation logic (including the MaxOutputTokens multiplication) was introduced in PR #1247. PR #1273 focused specifically on decoupling the thoughts summary feature from thinking budget settings and did not modify the existing thinking budget behavior.
🧬 Code graph analysis (2)
web/src/pages/Setting/Ratio/ModelRationNotSetEditor.jsx (2)
web/src/pages/Setting/Ratio/UnifiedRatioEditor.jsx (1)
configs(34-34)web/src/pages/Setting/Ratio/SegmentedRatioEditor.jsx (1)
configs(32-32)
controller/segmented_ratio.go (3)
setting/ratio_setting/segmented_ratio.go (8)
SegmentRule(12-32)GetSegmentedRatio(72-77)GetSegmentedRatioCopy(88-103)SegmentedRatioConfig(35-39)SetSegmentedRatio(55-69)DeleteSegmentedRatio(80-85)SegmentedRatio2JSONString(106-116)UpdateSegmentedRatioByJSONString(119-138)controller/option.go (1)
UpdateOption(45-224)model/option.go (1)
UpdateOption(177-191)
🔇 Additional comments (6)
web/src/pages/Setting/Ratio/ModelRationNotSetEditor.jsx (1)
84-134: LGTM – Previous feedback addressed correctly.The segmented ratio fetching and filtering logic now correctly:
- Uses the map key (
modelName) instead of the optionalmodel_namefield- Filters to include only enabled configurations (
config?.enabled)- Integrates the segmented ratio check into the unset-model detection
The async pattern within
useEffectis appropriate, and error handling (logging and continuing) is reasonable for this non-critical fetch.controller/segmented_ratio.go (5)
14-30: LGTM – Comprehensive validation logic.The extracted validation function correctly checks:
- Token ranges are non-negative
- Min ≤ Max when Max > 0 (properly handling 0 = no limit)
- Ratios are non-negative
This addresses the previous feedback about duplicated validation and is now reusable across endpoints.
33-67: LGTM – Read operations are correct.Both
GetSegmentedRatioandGetAllSegmentedRatiosfollow standard patterns with appropriate validation and error handling.
150-179: Previous feedback addressed – DB persistence now precedes in-memory deletion.The delete operation correctly persists to the database first (lines 150-170) before removing from memory (line 173).
The same redundancy noted in
CreateOrUpdateSegmentedRatioapplies here:model.UpdateOptionat line 163 likely updates memory viaupdateOptionMap, and line 173 deletes from memory again.
182-189: LGTM – Export is straightforward.The export function correctly serializes all configurations to JSON.
205-260: Previous feedback fully addressed – validation and ordering are correct.The import handler now:
- Parses and validates all configurations before applying (lines 205-234), including per-rule constraints using the extracted
validateSegmentedRatioRulesfunction- Persists to the database first (line 237) before updating in-memory state (line 247)
This addresses all prior review concerns about bypassing validation and incorrect ordering.
The same redundancy applies:
model.UpdateOptionat line 237 likely updates memory viaupdateOptionMap, then line 247 explicitly callsUpdateSegmentedRatioByJSONStringto update memory again.
There was a problem hiding this comment.
✅ 1. controller/segmented_ratio.go - 字符串转换错误
修复内容:
添加了 fmt 包导入
将 4 处 string(rune(i+1)) 替换为 fmt.Sprintf("规则 %d ...", i+1)
涉及行:83, 91, 99, 107
影响: 错误消息现在能正确显示规则编号,而不是显示 Unicode 不可见字符。
✅ 2. controller/segmented_ratio.go - 导入配置缺少验证
修复内容:
在 ImportSegmentedRatios 函数中添加了完整的验证逻辑
验证项包括:
配置是否至少有一条规则
Token 范围不能为负数
最小值不能大于最大值
倍率不能为负数
验证失败时返回清晰的错误消息,包含模型名和规则编号
影响: 防止导入无效或恶意配置,提高系统安全性。
✅ 3. setting/ratio_setting/segmented_ratio.go - 排序稳定性
修复内容:
第 62 行:sort.Slice → sort.SliceStable
第 127 行:sort.Slice → sort.SliceStable
影响: 当多个规则具有相同优先级时,保持它们的声明顺序,确保规则匹配的可预测性。
✅ 4. web/src/helpers/utils.jsx - 格式化问题
修复内容:
重写了 formatSegmentRuleDescription 函数
新增 formatTokens 辅助函数:
小于 1000 的直接显示 tokens 数
大于等于 1000 的显示 k 值,保留一位小数(去除尾随的 .0)
示例:
100 tokens → "100 tokens" ✓ (之前是 "0k tokens")
1500 tokens → "1.5k tokens" ✓ (之前是 "2k tokens")
2000 tokens → "2k tokens" ✓
影响: 管理员看到准确的 token 范围,避免配置错误。
✅ 5. web/src/hooks/model-pricing/useModelPricingData.jsx - 调试日志
修复内容:
删除了 238-244 行的 3 条 console.log 调试语句
影响:
控制台更清爽
避免暴露敏感定价信息
略微提升性能
✅ 6. web/src/pages/Setting/Ratio/ModelRationNotSetEditor.jsx - 逻辑错误
修复内容:
将 Object.values(configs).forEach(config => ...) 改为 Object.entries(configs).forEach(([modelName, config]) => ...)
只在 config?.enabled === true 时才将模型标记为已配置
使用 map key 作为模型名,而不是依赖 config.model_name
影响: 禁用的分段定价配置的模型会正确显示在"未设置列表"中,管理员可以看到需要设置的所有模型。
验证建议
建议运行以下测试验证修复效果:
字符串转换: 尝试创建一个无效的分段规则,检查错误消息是否显示正确的规则编号
导入验证: 尝试导入包含负数或无效范围的配置,应该被拒绝
排序稳定性: 创建多个相同优先级的规则,验证它们按声明顺序执行
格式化: 检查分段规则的显示,特别是小于 1000 tokens 的情况
模型列表: 禁用一个分段定价配置,验证该模型是否出现在"未设置列表"中
✅ 7. 提取重复的验证逻辑
修复内容:
创建了 validateSegmentedRatioRules 共享函数
将 CreateOrUpdateSegmentedRatio 和 ImportSegmentedRatios 中的重复验证逻辑替换为函数调用
减少了约 60 行重复代码
影响: 符合 DRY 原则,维护更容易,修改验证逻辑时只需改一处。
✅ 8. 修复 CreateOrUpdateSegmentedRatio 的持久化顺序
修复内容:
先准备包含新配置的临时 map
序列化并保存到数据库
只有在数据库持久化成功后才更新内存状态
影响: 解决了数据不一致问题。如果数据库保存失败,内存状态不会被更新,避免了重启后数据丢失。
✅ 9. 修复 DeleteSegmentedRatio 的持久化顺序
修复内容:
先准备删除指定模型后的临时 map
序列化并保存到数据库
只有在数据库持久化成功后才从内存中删除
影响: 解决了删除操作的数据不一致问题。如果数据库保存失败,配置不会从内存中删除。
✅ 10. 修复 ImportSegmentedRatios 的持久化顺序
修复内容:
先保存到数据库
只有在数据库持久化成功后才更新内存状态
影响: 解决了导入操作的数据不一致问题。如果数据库保存失败,所有内存配置都不会被替换。
✅ 11. 修复并发访问竞态条件
修复内容:
在 EvaluateSegmentedRatio 函数开始时获取读锁
使用 defer 确保函数结束时释放锁
直接访问 segmentedRatioMap 而不是通过 GetSegmentedRatio
在持有锁的情况下遍历规则列表
影响: 解决了数据竞争问题。现在评估过程中,配置不会被其他线程修改,避免了潜在的崩溃和不确定行为。
✅ 12. 删除生产环境调试日志
修复内容:
删除了 web/src/helpers/utils.jsx:709-712,720 的 4 条 console.log 调试语句
影响:
控制台更清爽
避免暴露敏感定价信息
略微提升性能
关键改进
数据一致性保证: 所有写操作现在都遵循"先持久化到数据库,再更新内存"的原则,确保系统在任何时候重启都能恢复到一致的状态。 并发安全: EvaluateSegmentedRatio 函数现在在整个评估过程中持有读锁,防止配置在评估期间被修改。 代码质量: 通过提取共享验证函数,减少了代码重复,提高了可维护性。 所有修复都已完成!🎉
✅ 13. 传递 refresh prop (RatioSetting.jsx:97)
✅ 14. 接收 refresh prop (UnifiedRatioEditor.jsx:32)
export default function UnifiedRatioEditor({ refresh }) {
✅ 15. 在保存/删除操作成功后调用 refresh
分段倍率保存成功 (第 482-484 行): 保存分段倍率配置后通知父组件
固定倍率保存成功 (第 570-572 行): 保存固定倍率配置后通知父组件
固定倍率删除成功 (第 437-439 行): 删除固定倍率配置后通知父组件
分段倍率删除成功 (第 450-452 行): 删除分段倍率配置后通知父组件
✅ 影响
现在当用户在"统一倍率管理"标签页中修改或删除配置后:
自身数据立即刷新 (loadConfigs())
父组件状态也会刷新 (refresh())
其他标签页切换时会显示最新数据,无需手动刷新页面
✅ 实现方式
采用了建议中的第一种方式: 传递 refresh={onRefresh} prop 并在保存操作成功后调用 这种方式的优点:
简单直接,符合现有代码模式
与其他标签页保持一致
易于维护和理解
所有修复都已完成!现在所有标签页的数据会保持同步。🎉
Segmented Ratio Implementation Summary
Overview
This implementation adds segmented multiplier configuration functionality to the Visual model ratio settings, allowing you to configure different model and completion multipliers based on input and output token ranges.
Example Use Case
For the
doubao-seed-1.6model (or any model):Files Created/Modified
Backend (Go)
New Files
setting/ratio_setting/segmented_ratio.go (209 lines)
SegmentRulestruct: Defines token ranges and multipliersSegmentedRatioConfigstruct: Holds all rules for a modelEvaluateSegmentedRatio(): Matches token counts to rulesSetSegmentedRatio(),GetSegmentedRatio(): Configuration managementUpdateSegmentedRatioByJSONString(): Persistence supportInitSegmentedRatio(): Initializationcontroller/segmented_ratio.go (173 lines)
GET /api/segmented_ratio/- Get all configurationsGET /api/segmented_ratio/:model_name- Get specific model configPOST /api/segmented_ratio/- Create/Update configurationDELETE /api/segmented_ratio/:model_name- Delete configurationGET /api/segmented_ratio/export- Export as JSONPOST /api/segmented_ratio/import- Import from JSONModified Files
types/price_data.go
UseSegmentedRatio boolfield toPriceDatastruct (line 25)ToSetting()method to include segmented ratio statusrelay/helper/price.go
ModelPriceHelper()function (lines 68-97)relay/compatible_handler.go
ratio_settingpackage (line 21)postConsumeQuota()(lines 220-231)setting/ratio_setting/model_ratio.go
InitSegmentedRatio()call toInitRatioSettings()(line 369)controller/option.go
SegmentedRatioinUpdateOption()switch (lines 159-167)model/option.go
SegmentedRatioto option map initialization (line 120)SegmentedRatioinupdateValueByKey()switch (lines 422-423)router/api-router.go
RootAuth()middlewareFrontend (React)
New Files
Modified Files
SegmentedRatioEditor(line 10)Documentation
test_segmented_ratio.md (New)
SEGMENTED_RATIO_IMPLEMENTATION.md (This file)
Architecture
Data Flow
Configuration Storage
Price Calculation (Pre-consumption)
Price Calculation (Post-consumption)
Rule Matching Algorithm
Configuration Format
{ "model_name": "doubao-seed-1.6", "enabled": true, "rules": [ { "input_min": 0, "input_max": 32000, "output_min": 0, "output_max": 200000, "model_ratio": 0.4, "completion_ratio": 2.5, "priority": 100 } ] }Key Features
Usage
Via Visual UI
doubao-seed-1.6Via API
See test_segmented_ratio.md for detailed API examples.
Calculation Examples
Example 1: 10K input, 50K output
Example 2: 20K input, 250K output
Example 3: 50K input, 100K output
Example 4: 150K input, 50K output
Integration Points
Existing Systems
Middleware Integration
RootAuth()Database Schema
optionstableSegmentedRatioTesting
Unit Tests Needed
EvaluateSegmentedRatio()with various token rangesIntegration Tests Needed
Manual Testing Checklist
Performance Considerations
Rule Evaluation: O(n) where n = number of rules per model
Memory Usage: O(m × r) where m = models, r = avg rules per model
Concurrency: RWMutex ensures thread-safe access
Database: Single row in options table
Future Enhancements
Troubleshooting
Issue: Segmented pricing not being applied
Issue: Wrong ratio being applied
EvaluateSegmentedRatio()Issue: Configuration not persisting
Issue: UI not loading configurations
Migration Path
From Traditional Ratios
Rollback Procedure
enabled: false)Security Considerations
Compliance
Support
For questions or issues:
DEBUG_ENABLED=trueenvironment variableConclusion
The segmented multiplier functionality is now fully implemented and integrated into the existing system. It provides flexible, token-based pricing that adapts to actual usage patterns while maintaining backward compatibility with traditional fixed ratios.
All core functionality is complete:
The system is ready for testing and deployment.
Summary by CodeRabbit
Release Notes