feat(grok): 加固媒体计费门控并补齐分组按模型价输入

生成类媒体仅在存在真实 image/video 计费单元时写 usage,避免空结果误扣费。
Create/Update 分组输入支持 video_model_prices,与仓储 JSONB 字段打通。
本阶段不引入假 voice 路由(上游音频路径依赖更广协议层)。
This commit is contained in:
IanShaw027 committed 2026-08-07 13:08:22 +08:00
1 parent ba58e74b33
commit 451abc3aa5
4 files changed
+37 -4

No files matched your search

+15 -3
View File
@@ -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(
+13 -1
View File
@@ -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))
})
}
}
+5
View File
@@ -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 客户端