增加渠道重试优先级模式(Round-robin 和 Sequential) - #2724
Conversation
WalkthroughThe PR introduces configuration-driven retry behavior for channel selection, adding support for avoiding previously-failed channels and choosing between sequential or round-robin priority modes. New global variables, database query filters, state tracking, and UI controls are added across backend and frontend layers. Changes
Sequence Diagram(s)sequenceDiagram
participant Client as Relay Handler
participant Service as Channel Select
participant Model as Channel Selection
participant DB as Database
Client->>Service: Start retry loop with RetryParam
loop Until channel found or retries exhausted
Service->>Service: Determine priority (round-robin or sequential)
Service->>Model: GetRandomSatisfiedChannel(group, model, retry, usedIds)
alt RetryAvoidUsedChannelEnabled && usedIds provided
Model->>DB: Query channels WHERE NOT IN (usedIds) <br/> ORDER BY priority, weight DESC
DB-->>Model: Return candidates
Model->>Model: Filter non-excluded channels
else Normal path
Model->>DB: Query channels ORDER BY priority, weight DESC
DB-->>Model: Return candidates
end
alt Channel found
Model-->>Service: Return channel
Service->>Service: AddUsedChannel(channelId)
Service-->>Client: Return selected channel
else No channels in priority (all excluded)
Model-->>Service: Return nil, nil
Service->>Service: IncreasePriorityIndex()
Service->>Service: Retry next priority
else All priorities exhausted
Service-->>Client: Error - no usable channels
end
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
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: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
service/channel_select.go (1)
147-158: Auto-group nil handling now skips remaining priorities.With exclusions enabled,
GetRandomSatisfiedChannelcan returnnilbecause the current priority is exhausted, not the entire group. The current logic immediately advances to the next group and resets retry, which skips remaining priorities in the same group.🛠️ Suggested fix (stay in same group and let retry advance priority)
- channel, _ = model.GetRandomSatisfiedChannel(autoGroup, param.ModelName, priorityRetry, param.UsedChannelIds) - if channel == nil { - // Current group has no available channel for this model, try next group - logger.LogDebug(param.Ctx, "No available channel in group %s for model %s at priorityRetry %d, trying next group", autoGroup, param.ModelName, priorityRetry) - common.SetContextKey(param.Ctx, constant.ContextKeyAutoGroupIndex, i+1) - common.SetContextKey(param.Ctx, constant.ContextKeyAutoGroupRetryIndex, 0) - param.SetRetry(0) - continue - } + channel, _ = model.GetRandomSatisfiedChannel(autoGroup, param.ModelName, priorityRetry, param.UsedChannelIds) + if channel == nil { + // Priority exhausted (possibly due to exclusions); stay in same group and let outer retry advance priority + logger.LogDebug(param.Ctx, "Priority exhausted in group %s for model %s at priorityRetry %d, retrying same group", autoGroup, param.ModelName, priorityRetry) + common.SetContextKey(param.Ctx, constant.ContextKeyAutoGroupIndex, i) + return nil, selectGroup, nil + }
🤖 Fix all issues with AI agents
In `@controller/relay.go`:
- Around line 331-334: RelayTask currently dereferences channel.Id after calling
getChannel, but getChannel can return (nil, nil) when a priority is exhausted;
add a nil check for channel immediately after the getChannel call in RelayTask
and handle that case (e.g., skip to the next priority loop iteration or return
gracefully) instead of dereferencing channel.Id, ensuring any subsequent logic
that expects a non-nil channel is only executed when channel != nil; refer to
the getChannel call and the channel.Id usage inside RelayTask to locate where to
add this guard.
In `@model/option.go`:
- Around line 414-417: The "RetryPriorityMode" branch currently ignores invalid
values and only updates common.RetryPriorityMode for known values, allowing bad
values to be persisted; change the handler in the switch case for
"RetryPriorityMode" to validate the incoming value and return an error when it
is not "sequential" or "round-robin" so the caller can abort updating
OptionMap/DB, and only set common.RetryPriorityMode when the value is valid;
ensure the surrounding function (the option-setting function that contains the
switch for "RetryPriorityMode") propagates this error back to the caller so
persistence is skipped on invalid input.
In `@verify-changes.sh`:
- Around line 122-133: The script verify-changes.sh uses absolute local file
paths in the check_file_exists calls which will break on other machines/CI;
update the four check_file_exists invocations (the ones checking the
开发文档/自测文档/部署指南/变更总结) to compute repo-relative paths (e.g., based on the script's
directory or a $REPO_ROOT env var) or guard them behind an optional env flag
(e.g., SKIP_DOC_CHECKS) so CI and other devs can run the script without those
user-specific /Users/... paths.
- Around line 139-156: Update the Go version check to match go.mod by changing
REQUIRED_VERSION from "1.24.0" to "1.25.1", enable reliable exit-code checks by
adding "set -o pipefail" before running the build, and replace the unreliable
"go build ... | grep -q 'error'" logic with an exit-status based test around the
go build invocation (use the same go build command that creates
/tmp/new-api-test); adjust the if/else accordingly to treat a non-zero exit as
failure and remove the grep filtering.
| if channel == nil { | ||
| return nil, types.NewError(fmt.Errorf("分组 %s 下模型 %s 的可用渠道不存在(retry)", selectGroup, info.OriginModelName), types.ErrorCodeGetChannelFailed, types.ErrOptionWithSkipRetry()) | ||
| // 该优先级的所有渠道都被排除,返回 nil 以便继续尝试下一个优先级 | ||
| // All channels at this priority have been excluded, return nil to continue trying next priority | ||
| return nil, nil |
There was a problem hiding this comment.
Guard against nil channel in RelayTask to avoid panic.
getChannel can now return (nil, nil) when a priority is exhausted by exclusions. RelayTask dereferences channel.Id without a nil check, which can panic in this new path.
🛠️ Suggested fix (handle nil channel in RelayTask)
--- a/controller/relay.go
+++ b/controller/relay.go
@@
for ; shouldRetryTaskRelay(c, channelId, taskErr, retryTimes) && retryParam.GetRetry() < retryTimes; retryParam.IncreaseRetry() {
channel, newAPIError := getChannel(c, relayInfo, retryParam)
if newAPIError != nil {
logger.LogError(c, fmt.Sprintf("CacheGetRandomSatisfiedChannel failed: %s", newAPIError.Error()))
taskErr = service.TaskErrorWrapperLocal(newAPIError.Err, "get_channel_failed", http.StatusInternalServerError)
break
}
+ if channel == nil {
+ retryParam.IncreasePriorityIndex()
+ logger.LogInfo(c, fmt.Sprintf("priority exhausted for task retry, switching priority index=%d", retryParam.GetPriorityIndex()))
+ continue
+ }
channelId = channel.Id🤖 Prompt for AI Agents
In `@controller/relay.go` around lines 331 - 334, RelayTask currently dereferences
channel.Id after calling getChannel, but getChannel can return (nil, nil) when a
priority is exhausted; add a nil check for channel immediately after the
getChannel call in RelayTask and handle that case (e.g., skip to the next
priority loop iteration or return gracefully) instead of dereferencing
channel.Id, ensuring any subsequent logic that expects a non-nil channel is only
executed when channel != nil; refer to the getChannel call and the channel.Id
usage inside RelayTask to locate where to add this guard.
| case "RetryPriorityMode": | ||
| if value == "sequential" || value == "round-robin" { | ||
| common.RetryPriorityMode = value | ||
| } |
There was a problem hiding this comment.
Reject invalid RetryPriorityMode to avoid config drift.
Right now invalid values are silently accepted into OptionMap/DB while common.RetryPriorityMode stays unchanged, which can desync UI vs runtime behavior. Consider returning an error (and keeping OptionMap consistent) when the value is invalid.
🛠️ Suggested fix
-import (
- "strconv"
- "strings"
- "time"
+import (
+ "fmt"
+ "strconv"
+ "strings"
+ "time"
@@
case "RetryPriorityMode":
- if value == "sequential" || value == "round-robin" {
- common.RetryPriorityMode = value
- }
+ if value == "sequential" || value == "round-robin" {
+ common.RetryPriorityMode = value
+ } else {
+ common.OptionMap[key] = common.RetryPriorityMode
+ return fmt.Errorf("invalid RetryPriorityMode: %s", value)
+ }🤖 Prompt for AI Agents
In `@model/option.go` around lines 414 - 417, The "RetryPriorityMode" branch
currently ignores invalid values and only updates common.RetryPriorityMode for
known values, allowing bad values to be persisted; change the handler in the
switch case for "RetryPriorityMode" to validate the incoming value and return an
error when it is not "sequential" or "round-robin" so the caller can abort
updating OptionMap/DB, and only set common.RetryPriorityMode when the value is
valid; ensure the surrounding function (the option-setting function that
contains the switch for "RetryPriorityMode") propagates this error back to the
caller so persistence is skipped on invalid input.
| # 检查文档文件 | ||
| check_file_exists "/Users/liyifan20/Documents/个人知识库/百度/10、项目介绍/new-api/功能开发/02-渠道重试避开已用渠道-开发文档.md" \ | ||
| "开发文档存在" | ||
|
|
||
| check_file_exists "/Users/liyifan20/Documents/个人知识库/百度/10、项目介绍/new-api/功能开发/03-渠道重试避开已用渠道-自测文档.md" \ | ||
| "自测文档存在" | ||
|
|
||
| check_file_exists "/Users/liyifan20/Documents/个人知识库/百度/10、项目介绍/new-api/功能开发/04-部署和测试指南.md" \ | ||
| "部署指南存在" | ||
|
|
||
| check_file_exists "/Users/liyifan20/Documents/个人知识库/百度/10、项目介绍/new-api/功能开发/05-代码变更总结.md" \ | ||
| "变更总结存在" |
There was a problem hiding this comment.
Avoid absolute local paths in repo scripts.
These /Users/... paths will fail for anyone else and in CI. Consider repo‑relative paths or make these checks optional via env vars.
♻️ Suggested adjustment (repo-relative paths + optional checks)
- check_file_exists "/Users/liyifan20/Documents/个人知识库/百度/10、项目介绍/new-api/功能开发/02-渠道重试避开已用渠道-开发文档.md" \
- "开发文档存在"
+ check_file_exists "docs/02-渠道重试避开已用渠道-开发文档.md" \
+ "开发文档存在 (repo)"🤖 Prompt for AI Agents
In `@verify-changes.sh` around lines 122 - 133, The script verify-changes.sh uses
absolute local file paths in the check_file_exists calls which will break on
other machines/CI; update the four check_file_exists invocations (the ones
checking the 开发文档/自测文档/部署指南/变更总结) to compute repo-relative paths (e.g., based on
the script's directory or a $REPO_ROOT env var) or guard them behind an optional
env flag (e.g., SKIP_DOC_CHECKS) so CI and other devs can run the script without
those user-specific /Users/... paths.
| # 检查 Go 语法(如果 Go 可用) | ||
| if command -v go &> /dev/null; then | ||
| GO_VERSION=$(go version | awk '{print $3}' | sed 's/go//') | ||
| echo -e "${YELLOW}当前 Go 版本: $GO_VERSION${NC}" | ||
|
|
||
| # 检查版本是否满足要求 | ||
| REQUIRED_VERSION="1.24.0" | ||
| if [ "$(printf '%s\n' "$REQUIRED_VERSION" "$GO_VERSION" | sort -V | head -n1)" = "$REQUIRED_VERSION" ]; then | ||
| echo -e "${GREEN}✓${NC} Go 版本满足要求 (>= 1.24.0)" | ||
|
|
||
| # 尝试编译检查 | ||
| echo " 正在进行编译检查..." | ||
| if go build -o /tmp/new-api-test main.go 2>&1 | grep -q "error"; then | ||
| echo -e "${RED}✗${NC} 编译检查失败,请查看错误信息" | ||
| go build -o /tmp/new-api-test main.go 2>&1 | head -20 | ||
| else | ||
| echo -e "${GREEN}✓${NC} 编译检查通过" | ||
| rm -f /tmp/new-api-test |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
fd go.modRepository: QuantumNous/new-api
Length of output: 69
🏁 Script executed:
cat verify-changes.sh | sed -n '139,156p'Repository: QuantumNous/new-api
Length of output: 782
🏁 Script executed:
cat go.modRepository: QuantumNous/new-api
Length of output: 5657
🏁 Script executed:
head -20 verify-changes.shRepository: QuantumNous/new-api
Length of output: 364
Align Go version requirement with go.mod and fix build check to use exit codes.
The hardcoded 1.24.0 requirement should be updated to match go.mod which specifies go 1.25.1. Additionally, the current go build | grep -q "error" check is unreliable; use exit codes instead by adding set -o pipefail and removing the grep filter.
🛠️ Suggested fix (exit-code based build check)
set -e
+set -o pipefail
@@
- REQUIRED_VERSION="1.24.0"
+ REQUIRED_VERSION="1.25.1"
if [ "$(printf '%s\n' "$REQUIRED_VERSION" "$GO_VERSION" | sort -V | head -n1)" = "$REQUIRED_VERSION" ]; then
echo -e "${GREEN}✓${NC} Go 版本满足要求 (>= 1.24.0)"
@@
- if go build -o /tmp/new-api-test main.go 2>&1 | grep -q "error"; then
- echo -e "${RED}✗${NC} 编译检查失败,请查看错误信息"
- go build -o /tmp/new-api-test main.go 2>&1 | head -20
- else
+ if go build -o /tmp/new-api-test main.go; then
echo -e "${GREEN}✓${NC} 编译检查通过"
rm -f /tmp/new-api-test
+ else
+ echo -e "${RED}✗${NC} 编译检查失败,请查看错误信息"
fi🤖 Prompt for AI Agents
In `@verify-changes.sh` around lines 139 - 156, Update the Go version check to
match go.mod by changing REQUIRED_VERSION from "1.24.0" to "1.25.1", enable
reliable exit-code checks by adding "set -o pipefail" before running the build,
and replace the unreliable "go build ... | grep -q 'error'" logic with an
exit-status based test around the go build invocation (use the same go build
command that creates /tmp/new-api-test); adjust the if/else accordingly to treat
a non-zero exit as failure and remove the grep filtering.
功能描述
本 PR 包含两个相关的渠道重试增强功能:
在渠道重试过程中,系统会自动排除已经尝试过的渠道,避免重复请求同一个失败的渠道。
功能特性:
实现了两种渠道重试的优先级模式,用户可根据业务需求选择:
Sequential(顺序模式):
Round-robin(轮询模式):
实现细节
功能 1: 重试避开已尝试渠道
修改的文件:
- 新增 RetryAvoidUsedChannelEnabled 配置常量
- 添加配置项的加载和更新逻辑
- 在重试循环中记录已使用的渠道 ID
- 将排除列表传递给渠道选择函数
- 新增 UsedChannelIds 字段到 RetryParam 结构体
- 将排除列表传递给底层查询函数
- 在渠道查询时应用排除逻辑
- 使用 NOT IN 子句过滤已使用的渠道
- web/src/pages/Setting/Operation/SettingsMonitoring.jsx - 新增开关按钮
- web/src/i18n/locales/zh.json & en.json - 国际化文本
功能 2: 渠道重试优先级模式
修改的文件:
- 修复了 RetryPriorityMode 配置加载问题
- 原本错误地放在了只处理 "Enabled" 结尾配置项的条件块内
- 在 getPriority 函数中添加了 round-robin 模式的模运算逻辑
- 确保在轮询模式下正确循环优先级
- 在 GetRandomSatisfiedChannel 函数中添加了 round-robin 模式的模运算逻辑
- 支持内存缓存场景下的轮询模式
- 移除了 round-robin 模式下的 IncreasePriorityIndex() 调用
- 避免双重优先级切换导致的跳过问题
- 区分两种模式的参数传递
- Round-robin 模式传递 retry 参数
- Sequential 模式传递 priorityIndex 参数
- web/src/pages/Setting/Operation/SettingsMonitoring.jsx - 优先级模式选择器
- web/src/i18n/locales/zh.json & en.json - 国际化文本
核心逻辑
Round-robin 模式:
if common.RetryPriorityMode == "round-robin" && len(priorities) > 0 {
priorityToUse = priorities[retry%len(priorities)]
}
Sequential 模式:
if retry >= len(priorities) {
priorityToUse = priorities[len(priorities)-1]
} else {
priorityToUse = priorities[retry]
}
渠道排除逻辑:
if len(excludeIds) > 0 {
var excludeIdList []int
for id := range excludeIds {
excludeIdList = append(excludeIdList, id)
}
channelQuery = channelQuery.Where("channel_id NOT IN ?", excludeIdList)
}
测试结果
功能 1: 重试避开已尝试渠道
功能 2: 渠道重试优先级模式
测试环境:
Round-robin 模式测试结果:
retry=0 → 渠道 #2 (优先级 10) ✓
retry=1 → 渠道 #5 (优先级 0) ✓
retry=2 → 渠道 #3 (优先级 10) ✓
retry=3 → 渠道 #6 (优先级 0) ✓
符合预期的轮询模式:在优先级之间交替选择。
相关 Issue
(如果有相关 issue,请在此处引用)
检查清单
备注
Summary by CodeRabbit
Release Notes
✏️ Tip: You can customize this high-level summary in your review settings.