Merge pull request #6444 from Ritel-T/pr/group-free-openai-fast

Add group-level Standard billing for OpenAI Fast requests
This commit is contained in:
Wesley Liddick
2026-09-02 09:23:49 +08:00
committed by GitHub
38 changed files with 481 additions and 25 deletions
@@ -148,6 +148,7 @@ type CreateGroupRequest struct {
AllowMessagesDispatch bool `json:"allow_messages_dispatch"`
AllowLive bool `json:"allow_live"`
ForceOpenAIFast bool `json:"force_openai_fast"`
FreeOpenAIFast bool `json:"free_openai_fast"`
RequireOAuthOnly bool `json:"require_oauth_only"`
RequirePrivacySet bool `json:"require_privacy_set"`
DefaultMappedModel string `json:"default_mapped_model"`
@@ -220,6 +221,7 @@ type UpdateGroupRequest struct {
AllowMessagesDispatch *bool `json:"allow_messages_dispatch"`
AllowLive *bool `json:"allow_live"`
ForceOpenAIFast *bool `json:"force_openai_fast"`
FreeOpenAIFast *bool `json:"free_openai_fast"`
RequireOAuthOnly *bool `json:"require_oauth_only"`
RequirePrivacySet *bool `json:"require_privacy_set"`
DefaultMappedModel *string `json:"default_mapped_model"`
@@ -557,6 +559,7 @@ func (h *GroupHandler) Create(c *gin.Context) {
AllowMessagesDispatch: req.AllowMessagesDispatch,
AllowLive: req.AllowLive,
ForceOpenAIFast: req.ForceOpenAIFast,
FreeOpenAIFast: req.FreeOpenAIFast,
RequireOAuthOnly: req.RequireOAuthOnly,
RequirePrivacySet: req.RequirePrivacySet,
DefaultMappedModel: req.DefaultMappedModel,
@@ -688,6 +691,7 @@ func (h *GroupHandler) Update(c *gin.Context) {
AllowMessagesDispatch: req.AllowMessagesDispatch,
AllowLive: req.AllowLive,
ForceOpenAIFast: req.ForceOpenAIFast,
FreeOpenAIFast: req.FreeOpenAIFast,
RequireOAuthOnly: req.RequireOAuthOnly,
RequirePrivacySet: req.RequirePrivacySet,
DefaultMappedModel: req.DefaultMappedModel,
@@ -9,15 +9,19 @@ import (
func TestGroupRequestsDecodeForceOpenAIFast(t *testing.T) {
var createReq CreateGroupRequest
require.NoError(t, json.Unmarshal([]byte(`{"name":"fast","force_openai_fast":true}`), &createReq))
require.NoError(t, json.Unmarshal([]byte(`{"name":"fast","force_openai_fast":true,"free_openai_fast":true}`), &createReq))
require.True(t, createReq.ForceOpenAIFast)
require.True(t, createReq.FreeOpenAIFast)
var updateReq UpdateGroupRequest
require.NoError(t, json.Unmarshal([]byte(`{"force_openai_fast":false}`), &updateReq))
require.NoError(t, json.Unmarshal([]byte(`{"force_openai_fast":false,"free_openai_fast":false}`), &updateReq))
require.NotNil(t, updateReq.ForceOpenAIFast)
require.False(t, *updateReq.ForceOpenAIFast)
require.NotNil(t, updateReq.FreeOpenAIFast)
require.False(t, *updateReq.FreeOpenAIFast)
var omitted UpdateGroupRequest
require.NoError(t, json.Unmarshal([]byte(`{}`), &omitted))
require.Nil(t, omitted.ForceOpenAIFast)
require.Nil(t, omitted.FreeOpenAIFast)
}
@@ -10,14 +10,16 @@ import (
func TestGroupMapperExposesForceOpenAIFastOnlyToAdmins(t *testing.T) {
group := &service.Group{
ID: 7, Name: "fast", Platform: service.PlatformOpenAI, Status: service.StatusActive, ForceOpenAIFast: true,
ID: 7, Name: "fast", Platform: service.PlatformOpenAI, Status: service.StatusActive, ForceOpenAIFast: true, FreeOpenAIFast: true,
}
userJSON, err := json.Marshal(GroupFromService(group))
require.NoError(t, err)
require.NotContains(t, string(userJSON), "force_openai_fast")
require.NotContains(t, string(userJSON), "free_openai_fast")
adminJSON, err := json.Marshal(GroupFromServiceAdmin(group))
require.NoError(t, err)
require.Contains(t, string(adminJSON), `"force_openai_fast":true`)
require.Contains(t, string(adminJSON), `"free_openai_fast":true`)
}
+1
View File
@@ -149,6 +149,7 @@ func GroupFromServiceAdmin(g *service.Group) *AdminGroup {
out := &AdminGroup{
Group: groupFromServiceBase(g),
ForceOpenAIFast: g.ForceOpenAIFast,
FreeOpenAIFast: g.FreeOpenAIFast,
ProfitControlEnabled: g.ProfitControlEnabled,
ProfitMinMargin: g.ProfitMinMargin,
ProfitSafetyBuffer: g.ProfitSafetyBuffer,
+2
View File
@@ -168,6 +168,8 @@ type AdminGroup struct {
Group
// ForceOpenAIFast 是管理端请求策略,用户侧分组 DTO 无需暴露。
ForceOpenAIFast bool `json:"force_openai_fast"`
// FreeOpenAIFast 是管理端计费策略,用户侧分组 DTO 无需暴露。
FreeOpenAIFast bool `json:"free_openai_fast"`
// 分组利润控制(五个 token 平台分组可启用;margin/buffer 为小数存储)。
// 仅管理员可见:这三个字段与同响应中的 rate_multiplier 相乘即可反推出
@@ -211,6 +211,7 @@ func (r *apiKeyRepository) GetByKeyForAuth(ctx context.Context, key string) (*se
group.FieldAllowMessagesDispatch,
group.FieldAllowLive,
group.FieldForceOpenaiFast,
group.FieldFreeOpenaiFast,
group.FieldDefaultMappedModel,
group.FieldMessagesDispatchModelConfig,
group.FieldModelsListConfig,
@@ -1010,6 +1011,7 @@ func groupEntityToService(g *dbent.Group) *service.Group {
AllowMessagesDispatch: g.AllowMessagesDispatch,
AllowLive: g.AllowLive,
ForceOpenAIFast: g.ForceOpenaiFast,
FreeOpenAIFast: g.FreeOpenaiFast,
RequireOAuthOnly: g.RequireOauthOnly,
RequirePrivacySet: g.RequirePrivacySet,
DefaultMappedModel: g.DefaultMappedModel,
@@ -17,7 +17,7 @@ func TestGetByKeyForAuthCarriesGroupForceOpenAIFast(t *testing.T) {
suffix := time.Now().UnixNano()
group := mustCreateGroup(t, integrationEntClient, &service.Group{
Name: fmt.Sprintf("fast-proj-group-%d", suffix), Platform: service.PlatformOpenAI,
RateMultiplier: 1, ForceOpenAIFast: true,
RateMultiplier: 1, ForceOpenAIFast: true, FreeOpenAIFast: true,
})
user := mustCreateUser(t, integrationEntClient, &service.User{
Email: fmt.Sprintf("fast-proj-%d@example.com", suffix), Concurrency: 5,
@@ -42,4 +42,5 @@ func TestGetByKeyForAuthCarriesGroupForceOpenAIFast(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, got.Group)
require.True(t, got.Group.ForceOpenAIFast)
require.True(t, got.Group.FreeOpenAIFast)
}
@@ -91,6 +91,7 @@ func mustCreateGroup(t *testing.T, client *dbent.Client, g *service.Group) *serv
SetRateMultiplier(g.RateMultiplier).
SetIsExclusive(g.IsExclusive).
SetForceOpenaiFast(g.ForceOpenAIFast).
SetFreeOpenaiFast(g.FreeOpenAIFast).
SetProfitControlEnabled(g.ProfitControlEnabled).
SetProfitMinMargin(g.ProfitMinMargin).
SetProfitSafetyBuffer(g.ProfitSafetyBuffer)
@@ -104,6 +104,7 @@ func createGroupRecord(ctx context.Context, client *dbent.Client, groupIn *servi
SetAllowMessagesDispatch(groupIn.AllowMessagesDispatch).
SetAllowLive(groupIn.AllowLive).
SetForceOpenaiFast(groupIn.ForceOpenAIFast).
SetFreeOpenaiFast(groupIn.FreeOpenAIFast).
SetRequireOauthOnly(groupIn.RequireOAuthOnly).
SetRequirePrivacySet(groupIn.RequirePrivacySet).
SetDefaultMappedModel(groupIn.DefaultMappedModel).
@@ -282,6 +283,7 @@ func (r *groupRepository) Update(ctx context.Context, groupIn *service.Group) er
SetAllowMessagesDispatch(groupIn.AllowMessagesDispatch).
SetAllowLive(groupIn.AllowLive).
SetForceOpenaiFast(groupIn.ForceOpenAIFast).
SetFreeOpenaiFast(groupIn.FreeOpenAIFast).
SetRequireOauthOnly(groupIn.RequireOAuthOnly).
SetRequirePrivacySet(groupIn.RequirePrivacySet).
SetDefaultMappedModel(groupIn.DefaultMappedModel).
@@ -58,6 +58,7 @@ func TestMigrationsRunner_IsIdempotent_AndSchemaIsUpToDate(t *testing.T) {
// groups: OpenAI Live 与 Fast 强制策略都默认关闭,管理员显式开启后才生效。
requireColumn(t, tx, "groups", "allow_live", "boolean", 0, false)
requireColumn(t, tx, "groups", "force_openai_fast", "boolean", 0, false)
requireColumn(t, tx, "groups", "free_openai_fast", "boolean", 0, false)
// api_keys: key length should be 128
requireColumn(t, tx, "api_keys", "key", "character varying", 128, false)
+5
View File
@@ -303,6 +303,7 @@ func sanitizeGroupOpenAIFast(group *Group) {
if group == nil || !groupSupportsOpenAIFast(group.Platform) {
if group != nil {
group.ForceOpenAIFast = false
group.FreeOpenAIFast = false
}
}
}
@@ -517,6 +518,7 @@ func (s *adminServiceImpl) CreateGroup(ctx context.Context, input *CreateGroupIn
AllowMessagesDispatch: input.AllowMessagesDispatch,
AllowLive: input.AllowLive,
ForceOpenAIFast: input.ForceOpenAIFast,
FreeOpenAIFast: input.FreeOpenAIFast,
RequireOAuthOnly: input.RequireOAuthOnly,
RequirePrivacySet: input.RequirePrivacySet,
DefaultMappedModel: input.DefaultMappedModel,
@@ -884,6 +886,9 @@ func (s *adminServiceImpl) UpdateGroup(ctx context.Context, id int64, input *Upd
if input.ForceOpenAIFast != nil {
group.ForceOpenAIFast = *input.ForceOpenAIFast
}
if input.FreeOpenAIFast != nil {
group.FreeOpenAIFast = *input.FreeOpenAIFast
}
if input.RequireOAuthOnly != nil {
group.RequireOAuthOnly = *input.RequireOAuthOnly
}
@@ -145,6 +145,7 @@ func cloneGroupForDuplicate(source *Group, operationID string) *Group {
AllowMessagesDispatch: source.AllowMessagesDispatch,
AllowLive: source.AllowLive,
ForceOpenAIFast: source.ForceOpenAIFast,
FreeOpenAIFast: source.FreeOpenAIFast,
RequireOAuthOnly: source.RequireOAuthOnly,
RequirePrivacySet: source.RequirePrivacySet,
DefaultMappedModel: source.DefaultMappedModel,
@@ -167,6 +167,7 @@ func TestDuplicateGroupCopiesConfigurationDeeplyAndResetsRuntimeState(t *testing
AllowMessagesDispatch: true,
AllowLive: true,
ForceOpenAIFast: true,
FreeOpenAIFast: true,
RequireOAuthOnly: true,
RequirePrivacySet: true,
DefaultMappedModel: "gpt-5.4",
@@ -215,6 +216,7 @@ func TestDuplicateGroupCopiesConfigurationDeeplyAndResetsRuntimeState(t *testing
require.Equal(t, source.ModelRouting, duplicate.ModelRouting)
require.Equal(t, source.MessagesDispatchModelConfig, duplicate.MessagesDispatchModelConfig)
require.Equal(t, source.ForceOpenAIFast, duplicate.ForceOpenAIFast)
require.Equal(t, source.FreeOpenAIFast, duplicate.FreeOpenAIFast)
require.Equal(t, source.ModelsListConfig, duplicate.ModelsListConfig)
require.Equal(t, source.RPMLimit, duplicate.RPMLimit)
require.Equal(t, source.MaxReasoningEffort, duplicate.MaxReasoningEffort)
@@ -267,6 +267,7 @@ type CreateGroupInput struct {
AllowMessagesDispatch bool
AllowLive bool
ForceOpenAIFast bool
FreeOpenAIFast bool
DefaultMappedModel string
RequireOAuthOnly bool
RequirePrivacySet bool
@@ -345,6 +346,7 @@ type UpdateGroupInput struct {
AllowMessagesDispatch *bool
AllowLive *bool
ForceOpenAIFast *bool
FreeOpenAIFast *bool
DefaultMappedModel *string
RequireOAuthOnly *bool
RequirePrivacySet *bool
@@ -1099,19 +1099,20 @@ func TestAdminService_CreateGroup_NormalizesForceOpenAIFastByPlatform(t *testing
svc := &adminServiceImpl{groupRepo: repo}
group, err := svc.CreateGroup(context.Background(), &CreateGroupInput{
Name: "fast-" + tt.name, Platform: tt.platform, RateMultiplier: 1, ForceOpenAIFast: true,
Name: "fast-" + tt.name, Platform: tt.platform, RateMultiplier: 1, ForceOpenAIFast: true, FreeOpenAIFast: true,
})
require.NoError(t, err)
require.NotNil(t, group)
require.Equal(t, tt.want, repo.created.ForceOpenAIFast)
require.Equal(t, tt.want, repo.created.FreeOpenAIFast)
})
}
}
func TestAdminService_UpdateGroup_ClearsForceOpenAIFastWhenPlatformChanges(t *testing.T) {
existingGroup := &Group{
ID: 1, Name: "existing-fast", Platform: PlatformOpenAI, Status: StatusActive, ForceOpenAIFast: true,
ID: 1, Name: "existing-fast", Platform: PlatformOpenAI, Status: StatusActive, ForceOpenAIFast: true, FreeOpenAIFast: true,
}
repo := &groupRepoStubForAdmin{getByID: existingGroup}
svc := &adminServiceImpl{groupRepo: repo}
@@ -1123,6 +1124,7 @@ func TestAdminService_UpdateGroup_ClearsForceOpenAIFastWhenPlatformChanges(t *te
require.NoError(t, err)
require.NotNil(t, group)
require.False(t, repo.updated.ForceOpenAIFast)
require.False(t, repo.updated.FreeOpenAIFast)
}
func TestAdminService_UpdateGroup_ForceOpenAIFastInvalidatesAuthCache(t *testing.T) {
@@ -1136,11 +1138,13 @@ func TestAdminService_UpdateGroup_ForceOpenAIFastInvalidatesAuthCache(t *testing
group, err := svc.UpdateGroup(context.Background(), existingGroup.ID, &UpdateGroupInput{
ForceOpenAIFast: &enabled,
FreeOpenAIFast: &enabled,
})
require.NoError(t, err)
require.NotNil(t, group)
require.True(t, repo.updated.ForceOpenAIFast)
require.True(t, repo.updated.FreeOpenAIFast)
require.Equal(t, []int64{existingGroup.ID}, invalidator.groupIDs)
}
@@ -104,6 +104,7 @@ type APIKeyAuthGroupSnapshot struct {
AllowMessagesDispatch bool `json:"allow_messages_dispatch"`
AllowLive bool `json:"allow_live"`
ForceOpenAIFast bool `json:"force_openai_fast"`
FreeOpenAIFast bool `json:"free_openai_fast"`
DefaultMappedModel string `json:"default_mapped_model,omitempty"`
MessagesDispatchModelConfig OpenAIMessagesDispatchModelConfig `json:"messages_dispatch_model_config,omitempty"`
ModelsListConfig GroupModelsListConfig `json:"models_list_config,omitempty"`
@@ -14,7 +14,7 @@ import (
"github.com/dgraph-io/ristretto"
)
const apiKeyAuthSnapshotVersion = 21 // v21: group force_openai_fast field
const apiKeyAuthSnapshotVersion = 22 // v22: group free_openai_fast field
type apiKeyAuthCacheConfig struct {
l1Size int
@@ -419,6 +419,7 @@ func (s *APIKeyService) snapshotFromAPIKey(ctx context.Context, apiKey *APIKey)
AllowMessagesDispatch: apiKey.Group.AllowMessagesDispatch,
AllowLive: apiKey.Group.AllowLive,
ForceOpenAIFast: apiKey.Group.ForceOpenAIFast,
FreeOpenAIFast: apiKey.Group.FreeOpenAIFast,
DefaultMappedModel: apiKey.Group.DefaultMappedModel,
MessagesDispatchModelConfig: apiKey.Group.MessagesDispatchModelConfig,
ModelsListConfig: apiKey.Group.ModelsListConfig,
@@ -519,6 +520,7 @@ func (s *APIKeyService) snapshotToAPIKey(key string, snapshot *APIKeyAuthSnapsho
AllowMessagesDispatch: snapshot.Group.AllowMessagesDispatch,
AllowLive: snapshot.Group.AllowLive,
ForceOpenAIFast: snapshot.Group.ForceOpenAIFast,
FreeOpenAIFast: snapshot.Group.FreeOpenAIFast,
DefaultMappedModel: snapshot.Group.DefaultMappedModel,
MessagesDispatchModelConfig: snapshot.Group.MessagesDispatchModelConfig,
ModelsListConfig: snapshot.Group.ModelsListConfig,
@@ -15,7 +15,7 @@ func TestAPIKeyAuthSnapshotGroupForceOpenAIFastRoundtrip(t *testing.T) {
User: &User{ID: 40, Status: StatusActive},
Group: &Group{
ID: groupID, Name: "fast-roundtrip", Platform: PlatformOpenAI, Status: StatusActive,
Hydrated: true, ForceOpenAIFast: true,
Hydrated: true, ForceOpenAIFast: true, FreeOpenAIFast: true,
},
}
svc := &APIKeyService{}
@@ -31,5 +31,6 @@ func TestAPIKeyAuthSnapshotGroupForceOpenAIFastRoundtrip(t *testing.T) {
require.NotNil(t, materialized.Group)
require.True(t, materialized.Group.Hydrated)
require.True(t, materialized.Group.ForceOpenAIFast)
require.Equal(t, 21, cached.Snapshot.Version)
require.True(t, materialized.Group.FreeOpenAIFast)
require.Equal(t, 22, cached.Snapshot.Version)
}
@@ -53,7 +53,7 @@ func TestAPIKeyAuthSnapshotProfitControlRoundtrip(t *testing.T) {
snapshot := svc.snapshotFromAPIKey(context.Background(), apiKey)
require.NotNil(t, snapshot)
require.Equal(t, apiKeyAuthSnapshotVersion, snapshot.Version)
require.Equal(t, 21, snapshot.Version, "v21 起认证快照携带分组 Fast 强制开关")
require.Equal(t, 22, snapshot.Version, "v22 起认证快照携带分组免费 Fast 开关")
// 模拟 L2 缓存的完整 JSON 往返(与 apiKeyCache.SetAuthCache/GetAuthCache 同构)。
payload, err := json.Marshal(&APIKeyAuthCacheEntry{Snapshot: snapshot})
+1
View File
@@ -101,6 +101,7 @@ type Group struct {
AllowMessagesDispatch bool
AllowLive bool
ForceOpenAIFast bool // 强制 OpenAI 网关请求使用 service_tier=priority
FreeOpenAIFast bool // OpenAI Fast 请求按 Standard 价格向用户计费
RequireOAuthOnly bool // 仅允许非 apikey 类型账号关联(OpenAI/Antigravity/Anthropic/Gemini)
RequirePrivacySet bool // 调度时仅允许 privacy 已成功设置的账号(OpenAI/Antigravity/Anthropic/Gemini)
DefaultMappedModel string
@@ -3081,6 +3081,77 @@ func TestOpenAIGatewayServiceRecordUsage_ShadowUsesParentCredentialTierContract(
require.InDelta(t, fastCost.TotalCost, usageRepo.lastLog.TotalCost, 1e-10)
}
func TestOpenAIGatewayServiceRecordUsage_FreeOpenAIFastChargesStandard(t *testing.T) {
usageRepo := &openAIRecordUsageLogRepoStub{inserted: true}
svc := newOpenAIRecordUsageServiceForTest(
usageRepo,
&openAIRecordUsageUserRepoStub{},
&openAIRecordUsageSubRepoStub{},
nil,
)
svc.resolver = NewModelPricingResolver(nil, svc.billingService)
groupID := int64(77)
serviceTier := "priority"
tokens := UsageTokens{InputTokens: 100, OutputTokens: 50}
inputPrice := 0.001
outputPrice := 0.002
fastMultiplier := 3.0
apiKey := &APIKey{
ID: 1020,
GroupID: &groupID,
Group: &Group{
ID: groupID, Platform: PlatformOpenAI, Status: StatusActive,
Hydrated: true, RateMultiplier: 0.5, FreeOpenAIFast: true,
ModelPricing: []ChannelModelPricing{{
Models: []string{"gpt-5.6-sol"},
BillingMode: BillingModeToken,
InputPrice: &inputPrice,
OutputPrice: &outputPrice,
FastMultiplier: &fastMultiplier,
}},
},
}
err := svc.RecordUsage(context.Background(), &OpenAIRecordUsageInput{
Result: &OpenAIForwardResult{
RequestID: "resp_free_fast",
ServiceTier: &serviceTier,
UpstreamResponseServiceTier: "default",
Usage: OpenAIUsage{InputTokens: tokens.InputTokens, OutputTokens: tokens.OutputTokens},
Model: "gpt-5.6-sol",
Duration: time.Second,
},
APIKey: apiKey,
User: &User{ID: 2020},
Account: &Account{ID: 3020, Platform: PlatformOpenAI, Type: AccountTypeOAuth},
})
require.NoError(t, err)
require.NotNil(t, usageRepo.lastLog)
require.NotNil(t, usageRepo.lastLog.ServiceTier)
require.Equal(t, "priority", *usageRepo.lastLog.ServiceTier)
require.InDelta(t, 0.5, usageRepo.lastLog.RateMultiplier, 1e-12)
standardTotal := float64(tokens.InputTokens)*inputPrice + float64(tokens.OutputTokens)*outputPrice
require.InDelta(t, standardTotal*fastMultiplier, usageRepo.lastLog.TotalCost, 1e-10)
require.InDelta(t, standardTotal*0.5, usageRepo.lastLog.ActualCost, 1e-10)
}
func TestGroupBillsOpenAIFastAtStandardRequiresOpenAIAccount(t *testing.T) {
apiKey := &APIKey{Group: &Group{Platform: PlatformComposite, FreeOpenAIFast: true}}
require.True(t, groupBillsOpenAIFastAtStandard(
apiKey,
&Account{Platform: PlatformOpenAI, Type: AccountTypeOAuth},
"priority",
))
require.False(t, groupBillsOpenAIFastAtStandard(
apiKey,
&Account{Platform: PlatformGrok, Type: AccountTypeAPIKey},
"priority",
))
}
func TestOpenAIGatewayServiceRecordUsage_ServiceTierNeverRaisedByUpstreamResponse(t *testing.T) {
usageRepo := &openAIRecordUsageLogRepoStub{inserted: true}
userRepo := &openAIRecordUsageUserRepoStub{}
@@ -132,6 +132,24 @@ func openAIUsagePricingAt(input *OpenAIRecordUsageInput) time.Time {
return timezone.Now()
}
func groupBillsOpenAIFastAtStandard(apiKey *APIKey, account *Account, serviceTier string) bool {
if apiKey == nil || apiKey.Group == nil || !apiKey.Group.FreeOpenAIFast {
return false
}
if account == nil || !account.IsOpenAI() {
return false
}
if !groupSupportsOpenAIFast(apiKey.Group.Platform) {
return false
}
switch normalizeBillingServiceTier(serviceTier) {
case "priority", "fast":
return true
default:
return false
}
}
// RecordUsage records usage and deducts balance
func (s *OpenAIGatewayService) RecordUsage(ctx context.Context, input *OpenAIRecordUsageInput) error {
if input == nil {
@@ -277,6 +295,32 @@ func (s *OpenAIGatewayService) RecordUsage(ctx context.Context, input *OpenAIRec
}
}
// Free Fast changes only the customer charge. Keep priority TotalCost and
// service_tier for upstream accounting, but evaluate ActualCost once more at
// the Standard tier using the same channel, peak, and long-context policy.
if groupBillsOpenAIFastAtStandard(apiKey, billingAccount, serviceTier) {
standardCost, standardErr := s.calculateOpenAIRecordUsageCost(
ctx,
result,
apiKey,
billingModels,
multiplier,
imageMultiplier,
videoMultiplier,
baseMultiplier,
tokens,
"",
longContextBillingGate,
pricingAt,
)
if standardErr != nil {
return standardErr
}
if cost != nil && standardCost != nil {
cost.ActualCost = standardCost.ActualCost
}
}
// Determine billing type
isSubscriptionBilling := subscription != nil && apiKey.Group != nil && apiKey.Group.IsSubscriptionType()
billingType := BillingTypeBalance