Skip to content

feat: auto分组 - #1239

Merged
Calcium-Ion merged 3 commits into
alphafrom
auto_group
Jun 17, 2025
Merged

feat: auto分组#1239
Calcium-Ion merged 3 commits into
alphafrom
auto_group

Conversation

@creamlike1024

@creamlike1024 creamlike1024 commented Jun 16, 2025

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • New Features

    • Introduced support for an "auto" group that dynamically aggregates multiple groups for tokens and model access.
    • Added configuration options for managing auto groups and enabling default use of the "auto" group.
    • Enhanced user and token management interfaces to display and handle the "auto" group option.
  • Improvements

    • Centralized and refined quota and pricing calculations to incorporate the auto group context.
    • Improved error messages and group descriptions to clarify group-related operations.
    • Enhanced settings pages with controls for auto groups and default auto group usage.
  • Bug Fixes

    • Fixed group selection and channel retrieval logic to correctly support the "auto" group across workflows.

@creamlike1024
creamlike1024 marked this pull request as ready for review June 16, 2025 14:25
@coderabbitai

coderabbitai Bot commented Jun 16, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

This update introduces support for an "auto" user group that dynamically aggregates multiple configured groups for model selection and channel allocation. It adds new configuration options for auto groups, modifies token group handling, updates quota and pricing calculations to incorporate auto group logic, and refactors related code for consistent context-aware channel retrieval and group ratio management.

Changes

File(s) Change Summary
controller/group.go Added "auto" group entry in user groups with fixed ratio and description if enabled.
controller/misc.go Added default_use_auto_group flag to status data.
controller/model.go Support for "auto" token group to aggregate models from multiple groups.
controller/playground.go, controller/relay.go, middleware/distributor.go Updated calls to CacheGetRandomSatisfiedChannel to include Gin context and handle "auto" group with additional return value.
controller/user.go Assign token group as "auto" on registration if default auto group usage is enabled.
model/cache.go Refactored CacheGetRandomSatisfiedChannel to support "auto" group by iterating configured auto groups, returning selected group, and storing it in context.
model/option.go Added new options AutoGroups and DefaultUseAutoGroup to option map initialization and update logic.
relay/helper/price.go Added GroupRatioInfo struct; centralized group ratio handling with special user group ratio override; updated pricing helper to use new struct.
service/quota.go Unified quota functions to use GroupRatioInfo from price data; added override logic for auto group from context; updated function signatures.
setting/auto_group.go New file defining AutoGroups slice, DefaultUseAutoGroup flag, and functions for JSON serialization and membership checking.
setting/user_usable_group.go Added function to get user-friendly description for a group.
web/src/components/settings/OperationSetting.js Added state variables and parsing for AutoGroups and DefaultUseAutoGroup.
web/src/pages/Setting/Operation/GroupRatioSettings.js Added form inputs for editing AutoGroups (JSON array) and DefaultUseAutoGroup (boolean switch).
web/src/pages/Token/EditToken.js Inserted or reordered "auto" group option in token group list when default auto group usage is enabled; cosmetic JSX formatting changes.
controller/channel-test.go Updated calls to use GroupRatioInfo fields for group ratios in pricing info generation.
relay/relay-image.go Updated quota calculation to use nested GroupRatioInfo.GroupRatio.
relay/relay-text.go Updated group ratio access to use GroupRatioInfo fields in quota consumption.
relay/websocket.go Refactored model price and quota pre-consumption logic to use consolidated priceData from helper; removed manual ratio calculations.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Controller
    participant Model
    participant Cache
    participant Setting
    participant RelayHelper
    participant Service
    participant Middleware

    Client->>Controller: Request with token group "auto"
    Controller->>Setting: Check if "auto" group enabled and get AutoGroups list
    Controller->>Model: ListModels for each group in AutoGroups
    Model->>Cache: Retrieve models per group
    Cache-->>Model: Return aggregated unique models
    Model-->>Controller: Return combined model list

    Controller->>Cache: CacheGetRandomSatisfiedChannel(ctx, "auto", model, retry)
    Cache->>Setting: Get AutoGroups list
    Cache->>Cache: Iterate AutoGroups to find channel
    Cache-->>Controller: Return channel and selectedGroup
    Controller->>Middleware: Pass selectedGroup via context

    Middleware->>Cache: CacheGetRandomSatisfiedChannel(ctx, userGroup, model, flag)
    Cache-->>Middleware: Channel, selectedGroup
    Middleware->>Controller: Continue with selectedGroup info

    Controller->>RelayHelper: ModelPriceHelper(ctx, relayInfo, tokens...)
    RelayHelper->>Setting: Check context for "auto_group"
    RelayHelper->>Setting: Get group ratio or special ratio
    RelayHelper-->>Controller: Return PriceData with GroupRatioInfo

    Controller->>Service: PostConsumeQuota(ctx, relayInfo, priceData, ...)
    Service->>RelayHelper: Use GroupRatioInfo for quota calculation
Loading

Possibly related PRs

  • feat: 分组特殊倍率 #1207: Related changes in handling group-specific ratios and their propagation through pricing and quota calculation, indicating a shared focus on group ratio logic enhancements.

Poem

🐇
A hop, a skip, an "auto" leap,
Groups combined, no bounds to keep.
Tokens dance with ratios new,
Channels pick from many, not few.
Configs set, the system hums,
Rabbit cheers as code becomes!
🌿✨🐇

✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 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.
    • Explain this complex logic.
    • 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. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • 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 src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

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

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

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

Documentation and Community

  • 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: 10

🔭 Outside diff range comments (2)
controller/relay.go (1)

262-267: Discarding selectedGroup loses useful context

CacheGetRandomSatisfiedChannel now returns the actual group picked.
Ignoring it means logs, billing or downstream logic still “see” the original group (often "auto").

-	channel, _, err := model.CacheGetRandomSatisfiedChannel(c, group, originalModel, retryCount)
+	channel, selectedGroup, err := model.CacheGetRandomSatisfiedChannel(c, group, originalModel, retryCount)
+	if err == nil {
+	    c.Set("group", selectedGroup) // keep context consistent
+	}

Propagating the real group keeps metrics accurate.

model/cache.go (1)

127-133: Nil-map index may panic when group/model unknown

group2model2channels[group] returns nil when the group key is missing; indexing that nil map ([model]) panics.
Auto groups provided via setting.AutoGroups might legitimately reference a group not yet cached.

channelSyncLock.RLock()
-modelMap := group2model2channels[group]
-channels := modelMap[model]
+modelMap, ok := group2model2channels[group]
+if !ok {
+    channelSyncLock.RUnlock()
+    return nil, errors.New("group not found")
+}
+channels := modelMap[model]
channelSyncLock.RUnlock()
🧹 Nitpick comments (13)
controller/user.go (1)

229-231: Extract hard-coded "auto" into a shared constant

The literal is used in multiple packages (controller, model, middleware, setting).
Introduce something like

const AutoGroupName = "auto"

(e.g. constant/group.go) and reference it here to avoid typos and ease future refactors.

controller/relay.go (1)

391-397: Same issue inside task-relay retry loop

The second return value is again ignored; consider applying the same fix here to avoid inconsistencies between normal relay and task relay paths.

web/src/components/settings/OperationSetting.js (1)

81-82: Boolean/JSON parsing condition is getting unwieldy

You now check the key three different times:

if (
  item.key.endsWith('Enabled') ||
  ['DefaultCollapseSidebar'].includes(item.key) ||
  ['DefaultUseAutoGroup'].includes(item.key)
)
  1. The two includes calls allocate new arrays every iteration.
  2. Readability suffers.

A compact alternative:

- if (
-   item.key.endsWith('Enabled') ||
-   ['DefaultCollapseSidebar'].includes(item.key) ||
-   ['DefaultUseAutoGroup'].includes(item.key)
- ) {
+ const booleanKeys = ['DefaultCollapseSidebar', 'DefaultUseAutoGroup'];
+ if (item.key.endsWith('Enabled') || booleanKeys.includes(item.key)) {

Not critical but keeps maintenance simple.

Also applies to: 90-93

controller/model.go (1)

184-196: O(n²) duplicate elimination can degrade with many models

common.StringsContains inside the nested loop turns the aggregation into quadratic complexity.
For dozens of auto-groups × hundreds of models this becomes noticeable.

- var models []string
+ modelSet := map[string]struct{}{}
  if tokenGroup == "auto" {
      for _, autoGroup := range setting.AutoGroups {
          groupModels := model.GetGroupModels(autoGroup)
-         for _, g := range groupModels {
-             if !common.StringsContains(models, g) {
-                 models = append(models, g)
-             }
-         }
+         for _, m := range groupModels {
+             modelSet[m] = struct{}{}
+         }
      }
  } else {
-     models = model.GetGroupModels(group)
+     for _, m := range model.GetGroupModels(group) {
+         modelSet[m] = struct{}{}
+     }
  }
+ for m := range modelSet {
+     models = append(models, m)
+ }

Keeps insertion O(1) and avoids repeated scans.

controller/playground.go (1)

61-64: Error path loses original error context

model.CacheGetRandomSatisfiedChannel already returns an error; wrapping it with a generic
message hides the root cause (e.g., DB outage vs. no channel).

Consider preserving it:

- message := fmt.Sprintf("当前分组 %s 下对于模型 %s 无可用渠道", finalGroup, playgroundRequest.Model)
- openaiErr = service.OpenAIErrorWrapperLocal(errors.New(message), "get_playground_channel_failed", http.StatusInternalServerError)
+ openaiErr = service.OpenAIErrorWrapperLocal(
+     fmt.Errorf("group %s / model %s: %w", finalGroup, playgroundRequest.Model, err),
+     "get_playground_channel_failed",
+     http.StatusInternalServerError,
+ )

Makes troubleshooting simpler.

web/src/pages/Token/EditToken.js (2)

46-47: statusDispatch is retrieved but never used

useContext destructures [statusState, statusDispatch], yet statusDispatch is not referenced afterwards, leading to an unused-variable lint error in most React setups.
Delete it or switch to {state} = useContext(StatusContext) if only the state is needed.

-  const [statusState, statusDispatch] = useContext(StatusContext);
+  const [statusState] = useContext(StatusContext);

127-129: Comparator can return only -1 or 1

sort((a,b)=>a.value==='auto'?-1:1) never returns 0, so non-auto items keep relative order only by engine’s stability.
Return 0 when neither element is auto for clarity.

- localGroupOptions.sort((a, b) => (a.value === 'auto' ? -1 : 1));
+ localGroupOptions.sort((a, b) => {
+   if (a.value === 'auto') return -1;
+   if (b.value === 'auto') return 1;
+   return 0;
+ });
relay/helper/price.go (2)

36-41: Unconditional log.Printf will spam production logs

log.Printf("final group ratio: …") executes on every request.
Either guard behind common.DebugEnabled (like later in the file) or switch to common.LogDebug.

-        log.Printf("final group ratio: %f", groupRatio)
+        if common.DebugEnabled {
+            log.Printf("final group ratio: %f", groupRatio)
+        }

42-48: groupRatio reassignment can be simplified

After computing actualGroupRatio, the code immediately does groupRatio = actualGroupRatio.
All later references already use groupRatio; the extra variable adds noise.
Optionally drop actualGroupRatio or return it directly.

middleware/distributor.go (1)

52-56: Magic string "auto" appears in multiple places

Hard-coded literals make refactors error-prone.
Consider promoting "auto" to a const tokenGroupAuto = "auto" in a shared package.

service/quota.go (1)

6-7: log imported solely for debug prints

The new log import is only used for the same unconditional prints added elsewhere. Prefer the project’s logging util and guard behind debug flag to avoid extra dependency noise.

model/cache.go (2)

158-165: Hard-coded magic number smoothingFactor := 10

Consider turning 10 into a const at package scope or a configurable option so tuning doesn’t require recompilation.

const weightSmoothingFactor = 10totalWeight += channel.GetWeight() + weightSmoothingFactor

82-112: Return value naming inconsistency

Inside the function the variable is called selectGroup, but the exported name in docs/call-sites will likely be “selectedGroup”. Minor but improves readability.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 6b7295b and 7fa21ce.

📒 Files selected for processing (16)
  • controller/group.go (2 hunks)
  • controller/misc.go (2 hunks)
  • controller/model.go (2 hunks)
  • controller/playground.go (2 hunks)
  • controller/relay.go (2 hunks)
  • controller/user.go (1 hunks)
  • middleware/distributor.go (2 hunks)
  • model/cache.go (2 hunks)
  • model/option.go (2 hunks)
  • relay/helper/price.go (2 hunks)
  • service/quota.go (5 hunks)
  • setting/auto_group.go (1 hunks)
  • setting/user_usable_group.go (1 hunks)
  • web/src/components/settings/OperationSetting.js (3 hunks)
  • web/src/pages/Setting/Operation/GroupRatioSettings.js (2 hunks)
  • web/src/pages/Token/EditToken.js (9 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (11)
model/option.go (2)
common/constants.go (1)
  • OptionMap (36-36)
setting/auto_group.go (3)
  • AutoGroups2JsonString (25-31)
  • DefaultUseAutoGroup (9-9)
  • UpdateAutoGroupsByJsonString (20-23)
controller/user.go (1)
setting/auto_group.go (1)
  • DefaultUseAutoGroup (9-9)
controller/relay.go (1)
model/cache.go (1)
  • CacheGetRandomSatisfiedChannel (82-112)
controller/misc.go (6)
common/constants.go (25)
  • Version (13-13)
  • StartTime (12-12)
  • EmailVerificationEnabled (44-44)
  • GitHubOAuthEnabled (45-45)
  • GitHubClientId (82-82)
  • LinuxDOOAuthEnabled (46-46)
  • LinuxDOClientId (84-84)
  • TelegramOAuthEnabled (48-48)
  • TelegramBotName (95-95)
  • SystemName (14-14)
  • Logo (16-16)
  • Footer (15-15)
  • WeChatAccountQRCodeImageURL (89-89)
  • WeChatAuthEnabled (47-47)
  • TurnstileCheckEnabled (49-49)
  • TurnstileSiteKey (91-91)
  • TopUpLink (17-17)
  • QuotaPerUnit (21-21)
  • DisplayInCurrencyEnabled (22-22)
  • BatchUpdateEnabled (117-117)
  • DrawingEnabled (24-24)
  • TaskEnabled (25-25)
  • DataExportEnabled (26-26)
  • DataExportDefaultTime (28-28)
  • DefaultCollapseSidebar (29-29)
setting/system_setting.go (1)
  • ServerAddress (3-3)
setting/operation_setting/general_setting.go (1)
  • GetGeneralSetting (23-25)
setting/chat.go (1)
  • Chats (8-24)
setting/operation_setting/operation_setting.go (2)
  • DemoSiteEnabled (5-5)
  • SelfUseModeEnabled (6-6)
setting/auto_group.go (1)
  • DefaultUseAutoGroup (9-9)
controller/group.go (1)
setting/user_usable_group.go (2)
  • GroupInUserUsableGroups (49-52)
  • GetUsableGroupDescription (54-59)
controller/model.go (2)
setting/auto_group.go (1)
  • AutoGroups (5-7)
model/ability.go (1)
  • GetGroupModels (24-29)
web/src/pages/Setting/Operation/GroupRatioSettings.js (2)
web/src/helpers/utils.js (2)
  • verifyJSON (236-243)
  • verifyJSON (236-243)
web/src/components/settings/OperationSetting.js (1)
  • inputs (21-67)
service/quota.go (2)
setting/operation_setting/model-ratio.go (1)
  • GetModelRatio (345-357)
setting/group_ratio.go (2)
  • GetGroupRatio (64-74)
  • GetGroupGroupRatio (76-89)
relay/helper/price.go (1)
setting/group_ratio.go (2)
  • GetGroupRatio (64-74)
  • GetGroupGroupRatio (76-89)
controller/playground.go (1)
model/cache.go (1)
  • CacheGetRandomSatisfiedChannel (82-112)
middleware/distributor.go (1)
model/cache.go (1)
  • CacheGetRandomSatisfiedChannel (82-112)
🔇 Additional comments (9)
controller/user.go (1)

229-231: Confirm that the new token is visible to the user

When DefaultUseAutoGroup is true, the initial token is stored in the "auto" group.
Please double-check that
setting.GetUserUsableGroups(user.Group) and UI components already include "auto", otherwise the freshly-created token will not appear in group selectors.

setting/user_usable_group.go (1)

54-59: Helper looks good

GetUsableGroupDescription is concise, nil-safe, and avoids repeated map look-ups.

controller/misc.go (1)

78-79: API surface updated correctly

default_use_auto_group is exposed with consistent snake_case naming – no further issues spotted.

web/src/pages/Setting/Operation/GroupRatioSettings.js (2)

20-22: Ensure value types match what the Switch expects

If props.options.DefaultUseAutoGroup arrives as the string "true" / "false" (current backend pattern), the controlled Switch will receive a string instead of boolean, triggering a React warning.
Cast to boolean in useEffect (currentInputs[key] = props.options[key] === 'true') or adjust backend serialization.


195-205: Confirm option parsing for "DefaultUseAutoGroup"

compareObjects converts booleans to "true"/"false" strings before the PUT call.
Verify that the server side uses strconv.ParseBool (or equivalent) when reading this option; otherwise "false" may be mis-interpreted.

controller/group.go (1)

38-43: ```shell
#!/bin/bash

Search for all occurrences of "ratio" in the codebase with context

rg -n -A3 -B3 '"ratio"' .

Locate where usableGroups is defined and populated in controller/group.go

rg -n "usableGroups" -n controller/group.go


</details>
<details>
<summary>model/option.go (1)</summary>

`79-81`: **Option keys added, but not documented**

`AutoGroups` and `DefaultUseAutoGroup` are persisted immediately, yet there is no comment
in the file header or in `setting/auto_group.go` describing their expected format &
semantics. Add brief godoc to help future maintainers.

[nitpick]

</details>
<details>
<summary>middleware/distributor.go (1)</summary>

`100-108`: **Possible empty group in error message**

When `CacheGetRandomSatisfiedChannel` fails before selecting a concrete group, `selectGroup` may still equal `"auto"`, resulting in `auto()` in the message.


Ensure `selectGroup` is non-empty before interpolation or fall back to `"unknown"` to avoid confusing responses.

</details>
<details>
<summary>service/quota.go (1)</summary>

`101-105`: **Potential panic if `auto_group` is not `string`**

`ctx.Get` returns `any`; if some middleware sets a non-string value, `.(string)` panics.  
Use a type-assertion check:

```go
if g, ok := autoGroup.(string); ok {
    groupRatio = setting.GetGroupRatio(g)
    relayInfo.Group = g
}

Comment thread web/src/pages/Setting/Operation/GroupRatioSettings.js
Comment thread controller/group.go
Comment thread web/src/components/settings/OperationSetting.js
Comment thread model/option.go
Comment on lines +124 to +136
if (statusState?.status?.default_use_auto_group) {
// if contain auto, add it to the first position
if (localGroupOptions.some((group) => group.value === 'auto')) {
// 排序
localGroupOptions.sort((a, b) => (a.value === 'auto' ? -1 : 1));
} else {
localGroupOptions.unshift({ label: t('自动选择'), value: 'auto' });
}
}
setGroups(localGroupOptions);
if (statusState?.status?.default_use_auto_group) {
setInputs({ ...inputs, group: 'auto' });
}

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

Editing an existing token is silently reset to auto group

loadGroups unconditionally overrides the form state with group: 'auto' whenever default_use_auto_group is true.
When editing a token whose group ≠ auto, that value is lost because loadGroups runs after loadToken. Users will save an unexpected change.

-      if (statusState?.status?.default_use_auto_group) {
-        setInputs({ ...inputs, group: 'auto' });
-      }
+      if (!isEdit && statusState?.status?.default_use_auto_group) {
+        // only default to auto for NEW tokens
+        setInputs((prev) => ({ ...prev, group: 'auto' }));
+      }

Note the functional setInputs form to avoid stale-state issues.
Consider the same guard for the insertion/sort logic to keep UI consistent.

📝 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 (statusState?.status?.default_use_auto_group) {
// if contain auto, add it to the first position
if (localGroupOptions.some((group) => group.value === 'auto')) {
// 排序
localGroupOptions.sort((a, b) => (a.value === 'auto' ? -1 : 1));
} else {
localGroupOptions.unshift({ label: t('自动选择'), value: 'auto' });
}
}
setGroups(localGroupOptions);
if (statusState?.status?.default_use_auto_group) {
setInputs({ ...inputs, group: 'auto' });
}
if (statusState?.status?.default_use_auto_group) {
// if contain auto, add it to the first position
if (localGroupOptions.some((group) => group.value === 'auto')) {
// 排序
localGroupOptions.sort((a, b) => (a.value === 'auto' ? -1 : 1));
} else {
localGroupOptions.unshift({ label: t('自动选择'), value: 'auto' });
}
}
setGroups(localGroupOptions);
if (!isEdit && statusState?.status?.default_use_auto_group) {
// only default to auto for NEW tokens
setInputs((prev) => ({ ...prev, group: 'auto' }));
}
🤖 Prompt for AI Agents
In web/src/pages/Token/EditToken.js around lines 124 to 136, the code
unconditionally sets the input group to 'auto' if default_use_auto_group is
true, which overwrites the existing token's group when editing. To fix this,
update the setInputs call to use the functional form and only set group to
'auto' if the current input group is not already set or different. Also, apply a
similar conditional guard to the insertion and sorting logic for 'auto' in
localGroupOptions to keep the UI consistent and avoid unnecessary changes.

Comment thread setting/auto_group.go
Comment on lines +20 to +23
func UpdateAutoGroupsByJsonString(jsonString string) error {
AutoGroups = make([]string, 0)
return json.Unmarshal([]byte(jsonString), &AutoGroups)
}

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

State is lost on JSON unmarshal error

UpdateAutoGroupsByJsonString clears AutoGroups before checking for errors.
If jsonString is malformed you end up with an empty config.

-func UpdateAutoGroupsByJsonString(jsonString string) error {
-    AutoGroups = make([]string, 0)
-    return json.Unmarshal([]byte(jsonString), &AutoGroups)
+func UpdateAutoGroupsByJsonString(jsonString string) error {
+    var tmp []string
+    if err := json.Unmarshal([]byte(jsonString), &tmp); err != nil {
+        return err
+    }
+    AutoGroups = tmp
+    return nil
 }
📝 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 UpdateAutoGroupsByJsonString(jsonString string) error {
AutoGroups = make([]string, 0)
return json.Unmarshal([]byte(jsonString), &AutoGroups)
}
func UpdateAutoGroupsByJsonString(jsonString string) error {
var tmp []string
if err := json.Unmarshal([]byte(jsonString), &tmp); err != nil {
return err
}
AutoGroups = tmp
return nil
}
🤖 Prompt for AI Agents
In setting/auto_group.go around lines 20 to 23, the function
UpdateAutoGroupsByJsonString clears the AutoGroups slice before unmarshaling the
JSON, which causes loss of the previous state if unmarshaling fails. To fix
this, first attempt to unmarshal the JSON into a temporary variable, and only if
unmarshaling succeeds, assign the result to AutoGroups. This preserves the
original AutoGroups if the JSON is malformed.

Comment thread setting/auto_group.go
Comment on lines +5 to +9
var AutoGroups = []string{
"default",
}

var DefaultUseAutoGroup = false

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

Global slice mutated without synchronisation

AutoGroups is read/written from multiple goroutines (HTTP handlers, cron jobs, …).
Without a mutex you risk data races under -race.

-var AutoGroups = []string{ "default" }
+var (
+    autoGroups     = []string{"default"}
+    autoGroupsLock sync.RWMutex
+)
+
+func AutoGroups() []string {
+    autoGroupsLock.RLock()
+    defer autoGroupsLock.RUnlock()
+    return append([]string(nil), autoGroups...)
+}

…and adapt setters/getters similarly.

📝 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
var AutoGroups = []string{
"default",
}
var DefaultUseAutoGroup = false
var (
autoGroups = []string{"default"}
autoGroupsLock sync.RWMutex
)
func AutoGroups() []string {
autoGroupsLock.RLock()
defer autoGroupsLock.RUnlock()
return append([]string(nil), autoGroups...)
}
var DefaultUseAutoGroup = false
🤖 Prompt for AI Agents
In setting/auto_group.go around lines 5 to 9, the global slice AutoGroups is
accessed concurrently from multiple goroutines without synchronization, risking
data races. Introduce a mutex to protect all accesses and modifications to
AutoGroups. Refactor any setters and getters to lock and unlock this mutex
appropriately when reading or writing the slice to ensure thread safety.

Comment thread service/quota.go
Comment on lines +100 to 108
autoGroup, exists := ctx.Get("auto_group")
if exists {
groupRatio = setting.GetGroupRatio(autoGroup.(string))
log.Printf("final group ratio: %f", groupRatio)
relayInfo.Group = autoGroup.(string)
}

actualGroupRatio := groupRatio
userGroupRatio, ok := setting.GetGroupGroupRatio(relayInfo.UserGroup, relayInfo.Group)

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

auto_group handling duplicated in three places

The override logic (ctx.Get("auto_group") → fetch ratio → log) is copy-pasted in PreWssConsumeQuota, PostWssConsumeQuota, and PostAudioConsumeQuota.
Extract to a small helper:

func overrideGroupRatio(ctx *gin.Context, relayInfo *relaycommon.RelayInfo) float64 {
    if g, ok := ctx.Get("auto_group"); ok {
        r := setting.GetGroupRatio(g.(string))
        if common.DebugEnabled {
            log.Printf("final group ratio: %f", r)
        }
        relayInfo.Group = g.(string)
        return r
    }
    return setting.GetGroupRatio(relayInfo.Group)
}
🤖 Prompt for AI Agents
In service/quota.go around lines 100 to 108, the logic for handling "auto_group"
from the context and overriding the group ratio is duplicated in multiple
functions. To fix this, extract this logic into a helper function that takes the
context and relayInfo as parameters, checks for "auto_group", updates
relayInfo.Group if present, logs the ratio if debugging is enabled, and returns
the appropriate group ratio. Replace the duplicated code in the three functions
with calls to this new helper function.

Comment thread model/cache.go Outdated
Comment on lines +91 to +99
log.Printf("autoGroup: %s", autoGroup)
channel, _ = getRandomSatisfiedChannel(autoGroup, model, retry)
if channel == nil {
continue
} else {
c.Set("auto_group", autoGroup)
selectGroup = autoGroup
log.Printf("selectGroup: %s", selectGroup)
break

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

Inconsistent logging API – use common.SysLog for uniformity

Lines 91 & 98 rely on log.Printf, whereas the rest of the file (e.g. line 71) uses common.SysLog.
Mixing logging back-ends complicates log collection and formatting.

-			log.Printf("autoGroup: %s", autoGroup)
+			common.SysLog(fmt.Sprintf("autoGroup: %s", autoGroup))-				log.Printf("selectGroup: %s", selectGroup)
+				common.SysLog(fmt.Sprintf("selectGroup: %s", selectGroup))
🤖 Prompt for AI Agents
In model/cache.go between lines 91 and 99, replace the log.Printf calls with
common.SysLog to maintain consistent logging across the file. Identify the
log.Printf statements on lines 91 and 98 and change them to use common.SysLog
with equivalent formatting and message content. This ensures uniform log
collection and formatting throughout the code.

Comment thread model/cache.go

@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

🧹 Nitpick comments (1)
relay/relay-text.go (1)

513-514: Parameter semantics drift – rename for clarity

GenerateTextOtherInfo still names its last argument userGroupRatio, but the caller now passes GroupSpecialRatio.
The values align, yet the semantic mismatch is confusing. Either:

  1. Rename the parameter inside GenerateTextOtherInfo to groupSpecialRatio, or
  2. Add a short comment at the call site to clarify the mapping.

No functional bug, just readability.

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 7fa21ce and 0f35d23.

📒 Files selected for processing (9)
  • controller/channel-test.go (2 hunks)
  • model/cache.go (2 hunks)
  • model/option.go (4 hunks)
  • relay/helper/price.go (2 hunks)
  • relay/relay-image.go (1 hunks)
  • relay/relay-text.go (2 hunks)
  • relay/websocket.go (3 hunks)
  • service/quota.go (12 hunks)
  • web/src/pages/Setting/Operation/GroupRatioSettings.js (2 hunks)
✅ Files skipped from review due to trivial changes (1)
  • controller/channel-test.go
🚧 Files skipped from review as they are similar to previous changes (5)
  • web/src/pages/Setting/Operation/GroupRatioSettings.js
  • model/cache.go
  • model/option.go
  • service/quota.go
  • relay/helper/price.go
🧰 Additional context used
🧬 Code Graph Analysis (2)
relay/relay-image.go (2)
relay/helper/price.go (1)
  • GroupRatioInfo (14-17)
common/constants.go (1)
  • QuotaPerUnit (21-21)
relay/relay-text.go (2)
relay/helper/price.go (1)
  • GroupRatioInfo (14-17)
service/log_info_generate.go (1)
  • GenerateTextOtherInfo (10-32)
🔇 Additional comments (3)
relay/relay-image.go (1)

165-166: Guard against nil / zero GroupRatioInfo.GroupRatio before quota calculation

quota now relies on priceData.GroupRatioInfo.GroupRatio.
If GroupRatioInfo is a pointer and happens to be nil, or if GroupRatio is 0 (e.g. when no group/auto-group resolution succeeded), the multiplication silently yields 0, letting the request escape billing.

Consider validating the ratio before use:

- quota = int(priceData.ModelPrice * priceData.GroupRatioInfo.GroupRatio * common.QuotaPerUnit)
+gr := priceData.GroupRatioInfo.GroupRatio
+if gr <= 0 {
+    return service.OpenAIErrorWrapperLocal(
+        fmt.Errorf("invalid group ratio: %v", gr),
+        "invalid_group_ratio", http.StatusInternalServerError)
+}
+quota = int(priceData.ModelPrice * gr * common.QuotaPerUnit)

This prevents accidental free quota and avoids a potential nil-pointer panic.

relay/websocket.go (2)

41-48: WebSocket path now depends on ModelPriceHelper – double-check pre-consume logic

ModelPriceHelper is invoked with promptTokens = 0 and maxTokens = 0, then priceData.ShouldPreConsumedQuota is used for pre-consumption.

If ShouldPreConsumedQuota is derived from token counts, it may now always be zero, bypassing the safety deposit that previously protected long-running WS sessions.

Please verify the helper’s implementation for the WS code path.


85-86: Compile-time check only

service.PostWssConsumeQuota now receives priceData. Ensure its signature was updated accordingly across the codebase; otherwise this will fail to compile.

Comment thread relay/relay-text.go
Comment on lines +364 to 365
groupRatio := priceData.GroupRatioInfo.GroupRatio
modelPrice := priceData.ModelPrice

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

Same nil / zero-ratio risk as in image helper

groupRatio := priceData.GroupRatioInfo.GroupRatio inherits the same pitfalls:
a missing or zero ratio collapses all subsequent quota math (ratio := dModelRatio.Mul(dGroupRatio)), effectively charging nothing.

Add a sanity check right after assignment (or inside ModelPriceHelper) to ensure groupRatio > 0, otherwise abort with an error.

🤖 Prompt for AI Agents
In relay/relay-text.go around lines 364 to 365, the assignment of groupRatio
from priceData.GroupRatioInfo.GroupRatio risks being zero or nil, which causes
incorrect quota calculations and results in no charge. Add a check immediately
after this assignment to verify that groupRatio is greater than zero; if not,
return or raise an error to abort processing. This validation ensures that
subsequent math using groupRatio is safe and meaningful.

@Calcium-Ion
Calcium-Ion merged commit ab627db into alpha Jun 17, 2025
@Calcium-Ion
Calcium-Ion deleted the auto_group branch June 19, 2025 06:35
@coderabbitai coderabbitai Bot mentioned this pull request Jul 28, 2025
@coderabbitai coderabbitai Bot mentioned this pull request Sep 13, 2025
@coderabbitai coderabbitai Bot mentioned this pull request Nov 22, 2025
2 tasks
@coderabbitai coderabbitai Bot mentioned this pull request Apr 18, 2026
9 tasks
x22x22 pushed a commit to x22x22/new-api that referenced this pull request Apr 24, 2026
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