feat(grok): 加固媒体计费门控并补齐分组按模型价输入
生成类媒体仅在存在真实 image/video 计费单元时写 usage,避免空结果误扣费。 Create/Update 分组输入支持 video_model_prices,与仓储 JSONB 字段打通。 本阶段不引入假 voice 路由(上游音频路径依赖更广协议层)。
This commit is contained in:
1 parent
ba58e74b33
commit
451abc3aa5
4 files changed
+37
-4
No files matched your search
@@ -417,7 +417,7 @@ func (h *OpenAIGatewayHandler) handleGrokMedia(c *gin.Context, endpoint service.
|
||||
)
|
||||
}
|
||||
}
|
||||
if shouldRecordGrokMediaUsage(endpoint, requestModel) {
|
||||
if shouldRecordGrokMediaUsage(endpoint, requestModel, result) {
|
||||
recordGrokMediaUsage(c, h, reqLog, apiKey, subject, subscription, account, result, requestModel, body, requestID)
|
||||
}
|
||||
reqLog.Debug("grok_media.request_completed",
|
||||
@@ -459,8 +459,20 @@ func grokMediaScheduleModel(account *service.Account, routingModel string, resul
|
||||
return account.GetMappedModel(routingModel)
|
||||
}
|
||||
|
||||
func shouldRecordGrokMediaUsage(endpoint service.GrokMediaEndpoint, requestModel string) bool {
|
||||
return endpoint.IsGenerationRequest() && strings.TrimSpace(requestModel) != ""
|
||||
// shouldRecordGrokMediaUsage gates usage writes for Grok media generation only.
|
||||
// Status/content polls, empty model, and failed generations with zero billable
|
||||
// image/video units never bill.
|
||||
func shouldRecordGrokMediaUsage(endpoint service.GrokMediaEndpoint, requestModel string, result *service.OpenAIForwardResult) bool {
|
||||
if !endpoint.IsGenerationRequest() || strings.TrimSpace(requestModel) == "" {
|
||||
return false
|
||||
}
|
||||
if result == nil {
|
||||
return false
|
||||
}
|
||||
if result.VideoCount > 0 {
|
||||
return true
|
||||
}
|
||||
return result.ImageCount > 0
|
||||
}
|
||||
|
||||
func recordGrokMediaUsage(
|
||||
|
||||
@@ -3,6 +3,7 @@ package handler
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/Wei-Shaw/sub2api/internal/service"
|
||||
@@ -68,7 +69,18 @@ func TestShouldRecordGrokMediaUsage(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
require.Equal(t, tt.want, shouldRecordGrokMediaUsage(tt.endpoint, tt.model))
|
||||
// Nil result must never bill.
|
||||
require.False(t, shouldRecordGrokMediaUsage(tt.endpoint, tt.model, nil))
|
||||
// Non-nil result with units only bills generation endpoints with model.
|
||||
result := &service.OpenAIForwardResult{ImageCount: 1, VideoCount: 0}
|
||||
if tt.endpoint.IsGenerationRequest() && strings.TrimSpace(tt.model) != "" {
|
||||
require.Equal(t, tt.want, shouldRecordGrokMediaUsage(tt.endpoint, tt.model, result))
|
||||
} else {
|
||||
require.False(t, shouldRecordGrokMediaUsage(tt.endpoint, tt.model, result))
|
||||
}
|
||||
// Zero billable units never bill even for generation + model.
|
||||
empty := &service.OpenAIForwardResult{}
|
||||
require.False(t, shouldRecordGrokMediaUsage(tt.endpoint, tt.model, empty))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -476,6 +476,7 @@ func (s *adminServiceImpl) CreateGroup(ctx context.Context, input *CreateGroupIn
|
||||
VideoPrice480P: videoPrice480P,
|
||||
VideoPrice720P: videoPrice720P,
|
||||
VideoPrice1080P: videoPrice1080P,
|
||||
VideoModelPrices: NormalizeVideoModelPrices(input.VideoModelPrices),
|
||||
WebSearchPricePerCall: webSearchPricePerCall,
|
||||
ClaudeCodeOnly: input.ClaudeCodeOnly,
|
||||
FallbackGroupID: input.FallbackGroupID,
|
||||
@@ -755,6 +756,10 @@ func (s *adminServiceImpl) UpdateGroup(ctx context.Context, id int64, input *Upd
|
||||
if input.VideoPrice1080P != nil {
|
||||
group.VideoPrice1080P = normalizePrice(input.VideoPrice1080P)
|
||||
}
|
||||
// nil = leave unchanged; empty map = clear per-model prices.
|
||||
if input.VideoModelPrices != nil {
|
||||
group.VideoModelPrices = NormalizeVideoModelPrices(input.VideoModelPrices)
|
||||
}
|
||||
if input.WebSearchPricePerCall != nil {
|
||||
group.WebSearchPricePerCall = normalizePrice(input.WebSearchPricePerCall)
|
||||
}
|
||||
|
||||
@@ -238,6 +238,8 @@ type CreateGroupInput struct {
|
||||
VideoPrice480P *float64
|
||||
VideoPrice720P *float64
|
||||
VideoPrice1080P *float64
|
||||
// VideoModelPrices 可选按模型族×分辨率覆盖视频每秒单价。
|
||||
VideoModelPrices map[string]map[string]float64
|
||||
// Codex alpha/search 网页搜索单次价格(USD/次,仅 openai 平台使用);nil/负数按默认价 0.01 处理
|
||||
WebSearchPricePerCall *float64
|
||||
ClaudeCodeOnly bool // 仅允许 Claude Code 客户端
|
||||
@@ -303,6 +305,8 @@ type UpdateGroupInput struct {
|
||||
VideoPrice480P *float64
|
||||
VideoPrice720P *float64
|
||||
VideoPrice1080P *float64
|
||||
// VideoModelPrices 可选按模型族×分辨率覆盖;nil 表示不修改,空 map 表示清除。
|
||||
VideoModelPrices map[string]map[string]float64
|
||||
// Codex alpha/search 网页搜索单次价格(USD/次);nil 表示不修改,负数表示清除回默认价 0.01
|
||||
WebSearchPricePerCall *float64
|
||||
ClaudeCodeOnly *bool // 仅允许 Claude Code 客户端
|
||||
|
||||
Reference in new issue
Block a user