diff --git a/backend/internal/service/admin_group.go b/backend/internal/service/admin_group.go index 9e29c30eaa..032c7024bc 100644 --- a/backend/internal/service/admin_group.go +++ b/backend/internal/service/admin_group.go @@ -295,6 +295,18 @@ func groupSupportsOAuthOnlyFilter(platform string) bool { platform == PlatformComposite } +func groupSupportsOpenAIFast(platform string) bool { + return platform == PlatformOpenAI || platform == PlatformComposite +} + +func sanitizeGroupOpenAIFast(group *Group) { + if group == nil || !groupSupportsOpenAIFast(group.Platform) { + if group != nil { + group.ForceOpenAIFast = false + } + } +} + func (s *adminServiceImpl) CreateGroup(ctx context.Context, input *CreateGroupInput) (*Group, error) { if input.RateMultiplier <= 0 { return nil, errors.New("rate_multiplier must be > 0") @@ -500,6 +512,7 @@ func (s *adminServiceImpl) CreateGroup(ctx context.Context, input *CreateGroupIn SupportedModelScopes: input.SupportedModelScopes, AllowMessagesDispatch: input.AllowMessagesDispatch, AllowLive: input.AllowLive, + ForceOpenAIFast: input.ForceOpenAIFast, RequireOAuthOnly: input.RequireOAuthOnly, RequirePrivacySet: input.RequirePrivacySet, DefaultMappedModel: input.DefaultMappedModel, @@ -510,6 +523,7 @@ func (s *adminServiceImpl) CreateGroup(ctx context.Context, input *CreateGroupIn ReasoningEffortMappings: reasoningEffortMappings, } sanitizeGroupMessagesDispatchFields(group) + sanitizeGroupOpenAIFast(group) if group.Platform != PlatformOpenAI && group.Platform != PlatformComposite { group.AllowLive = false } @@ -862,6 +876,9 @@ func (s *adminServiceImpl) UpdateGroup(ctx context.Context, id int64, input *Upd if input.AllowLive != nil { group.AllowLive = *input.AllowLive } + if input.ForceOpenAIFast != nil { + group.ForceOpenAIFast = *input.ForceOpenAIFast + } if input.RequireOAuthOnly != nil { group.RequireOAuthOnly = *input.RequireOAuthOnly } @@ -895,6 +912,7 @@ func (s *adminServiceImpl) UpdateGroup(ctx context.Context, id int64, input *Upd group.ReasoningEffortMappings = reasoningEffortMappings } sanitizeGroupMessagesDispatchFields(group) + sanitizeGroupOpenAIFast(group) if group.Platform != PlatformOpenAI && group.Platform != PlatformComposite { group.AllowLive = false } diff --git a/backend/internal/service/admin_service_group_test.go b/backend/internal/service/admin_service_group_test.go index 72c5e9a0f7..02694cdfde 100644 --- a/backend/internal/service/admin_service_group_test.go +++ b/backend/internal/service/admin_service_group_test.go @@ -1082,6 +1082,66 @@ func TestAdminService_CreateCompositeGroupPreservesLive(t *testing.T) { require.True(t, repo.created.AllowLive) } +func TestAdminService_CreateGroup_NormalizesForceOpenAIFastByPlatform(t *testing.T) { + for _, tt := range []struct { + name string + platform string + want bool + }{ + {name: "openai", platform: PlatformOpenAI, want: true}, + {name: "composite", platform: PlatformComposite, want: true}, + {name: "anthropic", platform: PlatformAnthropic, want: false}, + } { + t.Run(tt.name, func(t *testing.T) { + repo := &groupRepoStubForAdmin{} + svc := &adminServiceImpl{groupRepo: repo} + + group, err := svc.CreateGroup(context.Background(), &CreateGroupInput{ + Name: "fast-" + tt.name, Platform: tt.platform, RateMultiplier: 1, ForceOpenAIFast: true, + }) + + require.NoError(t, err) + require.NotNil(t, group) + require.Equal(t, tt.want, repo.created.ForceOpenAIFast) + }) + } +} + +func TestAdminService_UpdateGroup_ClearsForceOpenAIFastWhenPlatformChanges(t *testing.T) { + existingGroup := &Group{ + ID: 1, Name: "existing-fast", Platform: PlatformOpenAI, Status: StatusActive, ForceOpenAIFast: true, + } + repo := &groupRepoStubForAdmin{getByID: existingGroup} + svc := &adminServiceImpl{groupRepo: repo} + + group, err := svc.UpdateGroup(context.Background(), existingGroup.ID, &UpdateGroupInput{ + Platform: PlatformAnthropic, + }) + + require.NoError(t, err) + require.NotNil(t, group) + require.False(t, repo.updated.ForceOpenAIFast) +} + +func TestAdminService_UpdateGroup_ForceOpenAIFastInvalidatesAuthCache(t *testing.T) { + existingGroup := &Group{ + ID: 1, Name: "existing-fast", Platform: PlatformOpenAI, Status: StatusActive, + } + repo := &groupRepoStubForAdmin{getByID: existingGroup} + invalidator := &authCacheInvalidatorStub{} + svc := &adminServiceImpl{groupRepo: repo, authCacheInvalidator: invalidator} + enabled := true + + group, err := svc.UpdateGroup(context.Background(), existingGroup.ID, &UpdateGroupInput{ + ForceOpenAIFast: &enabled, + }) + + require.NoError(t, err) + require.NotNil(t, group) + require.True(t, repo.updated.ForceOpenAIFast) + require.Equal(t, []int64{existingGroup.ID}, invalidator.groupIDs) +} + func TestAdminService_UpdateCompositeGroupPreservesLive(t *testing.T) { existingGroup := &Group{ ID: 1,