Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,5 @@ skills-lock.json
.playwright-mcp
Operation.md
/home
.tools

2 changes: 2 additions & 0 deletions constant/context_key.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ const (
ContextKeyTokenModelLimitEnabled ContextKey = "token_model_limit_enabled"
ContextKeyTokenModelLimit ContextKey = "token_model_limit"
ContextKeyTokenCrossGroupRetry ContextKey = "token_cross_group_retry"
ContextKeyTokenRoutingPolicy ContextKey = "token_routing_policy"
ContextKeyTokenRoutePlan ContextKey = "token_route_plan"

/* channel related keys */
ContextKeyChannelId ContextKey = "channel_id"
Expand Down
10 changes: 6 additions & 4 deletions controller/group.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,15 @@ func GetUserGroups(c *gin.Context) {
}
}
}
for _, route := range service.GetUserAutoRoutes(userGroup, true) {
autoRoutes := service.GetUserAutoRoutes(userGroup, true)
for _, route := range autoRoutes {
addAutoRouteUsableGroup(usableGroups, route)
}
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "",
"data": usableGroups,
"success": true,
"message": "",
"data": usableGroups,
"auto_routes": autoRoutes,
})
}

Expand Down
55 changes: 38 additions & 17 deletions controller/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,16 +186,34 @@ func getModelListGroups(c *gin.Context) (modelListGroups, error) {
return modelListGroups{}, err
}
}
return buildModelListGroups(userGroup, tokenGroup), nil
return buildModelListGroupsWithOwnerGroups(
userGroup,
tokenGroup,
service.GetContextTokenRouteGroups(c, userGroup, tokenGroup),
), nil
}

func getContextModelListGroups(c *gin.Context) modelListGroups {
return buildModelListGroups(
common.GetContextKeyString(c, constant.ContextKeyUserGroup),
common.GetContextKeyString(c, constant.ContextKeyTokenGroup),
userGroup := common.GetContextKeyString(c, constant.ContextKeyUserGroup)
tokenGroup := common.GetContextKeyString(c, constant.ContextKeyTokenGroup)
return buildModelListGroupsWithOwnerGroups(
userGroup,
tokenGroup,
service.GetContextTokenRouteGroups(c, userGroup, tokenGroup),
)
}

func buildModelListGroupsWithOwnerGroups(userGroup string, tokenGroup string, ownerGroups []string) modelListGroups {
if len(ownerGroups) > 0 {
return modelListGroups{
userGroup: userGroup,
tokenGroup: tokenGroup,
ownerGroups: ownerGroups,
}
}
return buildModelListGroups(userGroup, tokenGroup)
}

func buildModelListGroups(userGroup string, tokenGroup string) modelListGroups {
if setting.IsAutoRouteKey(tokenGroup) {
return modelListGroups{
Expand All @@ -222,6 +240,21 @@ func buildModelListGroups(userGroup string, tokenGroup string) modelListGroups {
}
}

func getEnabledModelsForGroups(ownerGroups []string) []string {
models := make([]string, 0)
seen := make(map[string]struct{})
for _, ownerGroup := range ownerGroups {
for _, groupModel := range model.GetGroupEnabledModels(ownerGroup) {
if _, exists := seen[groupModel]; exists {
continue
}
seen[groupModel] = struct{}{}
models = append(models, groupModel)
}
}
return models
}
Comment on lines +243 to +256

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Deduplicate with a set and consider one query for all groups.

common.StringsContains scans the accumulated slice for every candidate model. With up to MaxTokenManualGroups groups and large model catalogs, the cost is quadratic on a request path. model.GetGroupEnabledModels also runs one query per group. Use a map[string]struct{} for deduplication, and consider a single IN query over all owner groups.

♻️ Proposed refactor
 func getEnabledModelsForGroups(ownerGroups []string) []string {
 	models := make([]string, 0)
+	seen := make(map[string]struct{})
 	for _, ownerGroup := range ownerGroups {
 		for _, groupModel := range model.GetGroupEnabledModels(ownerGroup) {
-			if !common.StringsContains(models, groupModel) {
-				models = append(models, groupModel)
-			}
+			if _, exists := seen[groupModel]; exists {
+				continue
+			}
+			seen[groupModel] = struct{}{}
+			models = append(models, groupModel)
 		}
 	}
 	return models
 }
📝 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 getEnabledModelsForGroups(ownerGroups []string) []string {
models := make([]string, 0)
for _, ownerGroup := range ownerGroups {
for _, groupModel := range model.GetGroupEnabledModels(ownerGroup) {
if !common.StringsContains(models, groupModel) {
models = append(models, groupModel)
}
}
}
return models
}
func getEnabledModelsForGroups(ownerGroups []string) []string {
models := make([]string, 0)
seen := make(map[string]struct{})
for _, ownerGroup := range ownerGroups {
for _, groupModel := range model.GetGroupEnabledModels(ownerGroup) {
if _, exists := seen[groupModel]; exists {
continue
}
seen[groupModel] = struct{}{}
models = append(models, groupModel)
}
}
return models
}
🤖 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 `@controller/model.go` around lines 243 - 253, Update getEnabledModelsForGroups
to track seen model names with a map[string]struct{} instead of repeatedly
calling common.StringsContains, while preserving the returned slice of unique
models. Also refactor the model lookup toward a single query that accepts all
ownerGroups through an IN condition, if the model package supports it, rather
than querying once per group.


func ListModels(c *gin.Context, modelType int) {
acceptUnsetRatioModel := operation_setting.SelfUseModeEnabled
if !acceptUnsetRatioModel {
Expand Down Expand Up @@ -269,19 +302,7 @@ func ListModels(c *gin.Context, modelType int) {
userModelNames = append(userModelNames, allowModel)
}
} else {
var models []string
if setting.IsAutoRouteKey(groups.tokenGroup) {
for _, autoGroup := range ownerGroups {
groupModels := model.GetGroupEnabledModels(autoGroup)
for _, g := range groupModels {
if !common.StringsContains(models, g) {
models = append(models, g)
}
}
}
} else if len(ownerGroups) > 0 {
models = model.GetGroupEnabledModels(ownerGroups[0])
}
models := getEnabledModelsForGroups(ownerGroups)
for _, modelName := range models {
if !acceptUnsetRatioModel {
if !helper.HasModelBillingConfig(modelName) {
Expand Down
15 changes: 15 additions & 0 deletions controller/model_list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,21 @@ func TestListModelsIncludesTieredBillingModel(t *testing.T) {
require.Empty(t, missingExprPricing.BillingExpr)
}

func TestGetEnabledModelsForGroupsIncludesModelsFromEveryOwnerGroup(t *testing.T) {
db := setupModelListControllerTestDB(t)
require.NoError(t, db.Create(&[]model.Ability{
{Group: "vip", Model: "zz-manual-first-model", ChannelId: 1, Enabled: true},
{Group: "default", Model: "zz-manual-later-model", ChannelId: 2, Enabled: true},
{Group: "default", Model: "zz-manual-first-model", ChannelId: 3, Enabled: true},
}).Error)

models := getEnabledModelsForGroups([]string{"vip", "default"})
require.ElementsMatch(t, []string{
"zz-manual-first-model",
"zz-manual-later-model",
}, models)
}

func TestListModelsTokenLimitIncludesTieredBillingModel(t *testing.T) {
withSelfUseModeDisabled(t)
withTieredBillingConfig(t, map[string]string{
Expand Down
18 changes: 18 additions & 0 deletions controller/model_owned_by_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (

"github.com/MAX-API-Next/MAX-API/common"
"github.com/MAX-API-Next/MAX-API/constant"
"github.com/MAX-API-Next/MAX-API/model"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/require"
)
Expand Down Expand Up @@ -83,3 +84,20 @@ func TestGetModelListGroupsUsesExplicitTokenGroup(t *testing.T) {
require.Equal(t, "vip", groups.tokenGroup)
require.Equal(t, []string{"vip"}, groups.ownerGroups)
}

func TestGetModelListGroupsUsesManualTokenRoutingGroups(t *testing.T) {
gin.SetMode(gin.TestMode)
ctx, _ := gin.CreateTestContext(httptest.NewRecorder())
common.SetContextKey(ctx, constant.ContextKeyUserGroup, "default")
common.SetContextKey(ctx, constant.ContextKeyTokenGroup, "vip")
common.SetContextKey(ctx, constant.ContextKeyTokenRoutingPolicy, model.TokenRoutingPolicy{
Version: model.TokenRoutingPolicyVersion,
Mode: model.TokenRoutingModeManual,
Groups: []string{"vip", "default"},
RetryOnFailure: true,
})

groups, err := getModelListGroups(ctx)
require.NoError(t, err)
require.Equal(t, []string{"vip", "default"}, groups.ownerGroups)
}
Loading
Loading