Skip to content

✨ feat: 分组定价、自动 fallback 与运行时分组选择 ✨ feat: group-based pricing, auto fallback, and runtime group selection#6116

Open
Wu-jiyan wants to merge 2 commits into
QuantumNous:mainfrom
Wu-jiyan:feat/group-pricing-and-fallback
Open

✨ feat: 分组定价、自动 fallback 与运行时分组选择 ✨ feat: group-based pricing, auto fallback, and runtime group selection#6116
Wu-jiyan wants to merge 2 commits into
QuantumNous:mainfrom
Wu-jiyan:feat/group-pricing-and-fallback

Conversation

@Wu-jiyan

@Wu-jiyan Wu-jiyan commented Jul 11, 2026

Copy link
Copy Markdown

功能说明

实现了 AI API 调用的分组定价、自动 fallback 和运行时分组选择功能。

改动内容

  1. 模型×分组交叉倍率 — 新增 model_group_ratio,支持同一模型在不同分组设置不同价格
  2. 运行时分组选择 — 标准 API 请求体支持 group 参数,用户可自由选择分组
  3. 自定义 fallback 顺序 — 支持逗号分隔的分组列表 (如 "cheap,premium,backup") 作为 fallback 顺序
  4. 按实际分组扣费 — 计费基于实际调用成功的分组

向后兼容

不传 group 参数或不配置 model_group_ratio 时,行为与原来完全一致。

Summary by CodeRabbit

  • New Features
    • Model-level “System Prompt” is now supported across API requests (with modes: disabled, inject, override, append).
    • Model management UI now lets you edit and view system prompts, including language-specific JSON resolution.
    • Channel selection can use specified user groups for supported request types, plus auto and comma-separated fallback priorities with cross-group retries.
    • Pricing can apply per-model/per-group ratios.
  • Bug Fixes
    • Group-based channel selection now consistently respects group info for general requests.
    • When no channel is available, the selection flow returns the last encountered error.

@coderabbitai

coderabbitai Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Model-level system prompts are now stored, configured in the web UI, and applied to OpenAI, Gemini, and Claude requests. Request groups support ordered multi-group channel fallback, while model-specific group ratios flow into pricing.

Changes

Model-specific group ratios

Layer / File(s) Summary
Ratio storage and APIs
setting/ratio_setting/group_ratio.go
Adds model/group ratio storage, accessors, snapshots, and JSON update support.
Pricing integration
service/group.go, relay/helper/price.go, model/option.go
Resolves model-specific ratios with group-ratio fallbacks and synchronizes option-map updates.

Group-aware channel selection

Layer / File(s) Summary
Request group propagation
middleware/distributor.go
Copies request groups into model requests and uses them for validated channel-group selection.
Multi-group fallback
service/channel_select.go
Supports automatic and comma-separated groups, retry-state transitions, cross-group fallback, and final error propagation.

Model-level system prompts

Layer / File(s) Summary
Prompt storage and application
model/model_meta.go, constant/context_key.go, middleware/distributor.go, relay/helper/system_prompt.go, relay/*_handler.go
Stores model prompt configuration and applies multilingual prompt modes across OpenAI, Gemini, and Claude requests.
Prompt management UI
web/default/src/features/models/...
Adds form fields, submission mapping, editing controls, and a model-list display for system prompts.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ModelUI
  participant ModelAPI
  participant Distributor
  participant RelayHandler
  participant Upstream

  ModelUI->>ModelAPI: submit system prompt and mode
  ModelAPI->>Distributor: provide active model configuration
  Distributor->>RelayHandler: set resolved prompt context
  RelayHandler->>Upstream: send request with model-level prompt
Loading

Possibly related PRs

Suggested reviewers: creamlike1024, Calcium-Ion

Poem

A rabbit tunes prompts by moonlight bright,
Adds groups to channels in ordered flight.
Ratios hop softly through every fare—
“Inject, append, override!” sings the hare.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 59.26% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main group-based pricing and runtime selection changes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

…m fallback

- Add model_group_ratio for per-model per-group pricing overlay
- Support group parameter in standard API request body for runtime group selection
- Support comma-separated group list as custom fallback order
- Billing uses the actual group that successfully served the request
- Fully backward compatible when no new config is set

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

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@service/channel_select.go`:
- Around line 106-135: Update the group-selection loop around
GetRandomSatisfiedChannel to capture each returned error while preserving the
existing channel fallback behavior. Track the last non-nil error across failed
groups, continue trying groups as before, and when all groups are exhausted
return nil with that error; retain a nil error when failures only indicate no
available channel.

In `@setting/ratio_setting/group_ratio.go`:
- Around line 154-156: Add a "ModelGroupRatio" case to the options update switch
in model/option.go, routing its value to UpdateModelGroupRatioByJSONString.
Preserve the existing option handling and ensure the exposed model_group_ratio
setting is updated through the standard options path.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6af0008a-eb0c-4eed-9a50-969cfcbe95db

📥 Commits

Reviewing files that changed from the base of the PR and between ad900bb and 4527f70.

📒 Files selected for processing (5)
  • middleware/distributor.go
  • relay/helper/price.go
  • service/channel_select.go
  • service/group.go
  • setting/ratio_setting/group_ratio.go

Comment thread service/channel_select.go
Comment thread setting/ratio_setting/group_ratio.go
@Wu-jiyan
Wu-jiyan force-pushed the feat/group-pricing-and-fallback branch from 4527f70 to 3eddaf4 Compare July 11, 2026 11:00
- Fix error being discarded when all groups fail in multi-group fallback
- Add ModelGroupRatio option route for admin API read/write
@Wu-jiyan
Wu-jiyan force-pushed the feat/group-pricing-and-fallback branch from 29fe543 to 386c029 Compare July 11, 2026 11:56

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
middleware/distributor.go (1)

89-95: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Validate each fallback group individually.

GroupInUserUsableGroups performs an exact lookup, so a valid value such as "group-a,group-b" is checked as one group and rejected with 403. Split the ordered list, validate every group against the original user group, then store the normalized list for downstream fallback.

Proposed fix
 if modelRequest.Group != "" {
-	if !service.GroupInUserUsableGroups(usingGroup, modelRequest.Group) && modelRequest.Group != usingGroup {
-		abortWithOpenAiMessage(c, http.StatusForbidden, i18n.T(c, i18n.MsgDistributorGroupAccessDenied))
-		return
+	requestedGroups := strings.Split(modelRequest.Group, ",")
+	for index, requestedGroup := range requestedGroups {
+		requestedGroup = strings.TrimSpace(requestedGroup)
+		if requestedGroup == "" ||
+			(!service.GroupInUserUsableGroups(usingGroup, requestedGroup) && requestedGroup != usingGroup) {
+			abortWithOpenAiMessage(c, http.StatusForbidden, i18n.T(c, i18n.MsgDistributorGroupAccessDenied))
+			return
+		}
+		requestedGroups[index] = requestedGroup
 	}
-	usingGroup = modelRequest.Group
+	usingGroup = strings.Join(requestedGroups, ",")
 	common.SetContextKey(c, constant.ContextKeyUsingGroup, usingGroup)
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@middleware/distributor.go` around lines 89 - 95, Update the
modelRequest.Group handling around GroupInUserUsableGroups to split the ordered
fallback-group string into individual groups, validate each group against the
original usingGroup, and reject the request if any is unauthorized. Normalize
and store the validated list as usingGroup and in ContextKeyUsingGroup while
preserving the existing single-group behavior and fallback order.
🧹 Nitpick comments (1)
web/default/src/features/models/components/drawers/model-mutate-drawer.tsx (1)

1327-1353: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider spreading {...field} on the system prompt Textarea for consistency.

The Textarea uses explicit value/onChange instead of {...field}, omitting field.ref and field.onBlur. Other Textarea fields in this file (e.g., the description field at line 738) use {...field}. While system_prompt has no validation constraints so no visible issues arise, including ref/onBlur ensures consistent form registration and blur-state tracking.

♻️ Suggested change
                     <FormControl>
                       <Textarea
+                        {...field}
                         value={field.value || ''}
-                        onChange={field.onChange}
                         placeholder={
                           '{\n  "en": "You are a helpful assistant.",\n  "zh": "你是一个有用的助手。"\n}'
                         }
                         rows={6}
                         disabled={form.watch('system_prompt_mode') === 0}
                         className='font-mono text-sm'
                       />
                     </FormControl>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/default/src/features/models/components/drawers/model-mutate-drawer.tsx`
around lines 1327 - 1353, Update the system_prompt Textarea in the FormField
render callback to spread the complete field object with {...field}, removing
the separate value and onChange props while preserving the existing empty-value
fallback behavior if needed. Keep the disabled, placeholder, styling, and other
Textarea configuration unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@middleware/distributor.go`:
- Around line 89-95: Update the modelRequest.Group handling around
GroupInUserUsableGroups to split the ordered fallback-group string into
individual groups, validate each group against the original usingGroup, and
reject the request if any is unauthorized. Normalize and store the validated
list as usingGroup and in ContextKeyUsingGroup while preserving the existing
single-group behavior and fallback order.

---

Nitpick comments:
In `@web/default/src/features/models/components/drawers/model-mutate-drawer.tsx`:
- Around line 1327-1353: Update the system_prompt Textarea in the FormField
render callback to spread the complete field object with {...field}, removing
the separate value and onChange props while preserving the existing empty-value
fallback behavior if needed. Keep the disabled, placeholder, styling, and other
Textarea configuration unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8b8173bb-5d20-47db-a3c1-f8f290aea27c

📥 Commits

Reviewing files that changed from the base of the PR and between 386c029 and 29fe543.

📒 Files selected for processing (11)
  • constant/context_key.go
  • middleware/distributor.go
  • model/model_meta.go
  • relay/claude_handler.go
  • relay/compatible_handler.go
  • relay/gemini_handler.go
  • relay/helper/system_prompt.go
  • web/default/src/features/models/components/drawers/model-mutate-drawer.tsx
  • web/default/src/features/models/components/models-columns.tsx
  • web/default/src/features/models/lib/model-form.ts
  • web/default/src/features/models/types.ts

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.

1 participant