feat(openai): 支持 gpt-5.3-codex-spark 并统一 gpt-5.3 到 codex 计费
This commit is contained in:
@@ -15,8 +15,8 @@ type Model struct {
|
|||||||
|
|
||||||
// DefaultModels OpenAI models list
|
// DefaultModels OpenAI models list
|
||||||
var DefaultModels = []Model{
|
var DefaultModels = []Model{
|
||||||
{ID: "gpt-5.3", Object: "model", Created: 1735689600, OwnedBy: "openai", Type: "model", DisplayName: "GPT-5.3"},
|
|
||||||
{ID: "gpt-5.3-codex", Object: "model", Created: 1735689600, OwnedBy: "openai", Type: "model", DisplayName: "GPT-5.3 Codex"},
|
{ID: "gpt-5.3-codex", Object: "model", Created: 1735689600, OwnedBy: "openai", Type: "model", DisplayName: "GPT-5.3 Codex"},
|
||||||
|
{ID: "gpt-5.3-codex-spark", Object: "model", Created: 1735689600, OwnedBy: "openai", Type: "model", DisplayName: "GPT-5.3 Codex Spark"},
|
||||||
{ID: "gpt-5.2", Object: "model", Created: 1733875200, OwnedBy: "openai", Type: "model", DisplayName: "GPT-5.2"},
|
{ID: "gpt-5.2", Object: "model", Created: 1733875200, OwnedBy: "openai", Type: "model", DisplayName: "GPT-5.2"},
|
||||||
{ID: "gpt-5.2-codex", Object: "model", Created: 1733011200, OwnedBy: "openai", Type: "model", DisplayName: "GPT-5.2 Codex"},
|
{ID: "gpt-5.2-codex", Object: "model", Created: 1733011200, OwnedBy: "openai", Type: "model", DisplayName: "GPT-5.2 Codex"},
|
||||||
{ID: "gpt-5.1-codex-max", Object: "model", Created: 1730419200, OwnedBy: "openai", Type: "model", DisplayName: "GPT-5.1 Codex Max"},
|
{ID: "gpt-5.1-codex-max", Object: "model", Created: 1730419200, OwnedBy: "openai", Type: "model", DisplayName: "GPT-5.1 Codex Max"},
|
||||||
|
|||||||
@@ -9,54 +9,59 @@ import (
|
|||||||
var codexCLIInstructions string
|
var codexCLIInstructions string
|
||||||
|
|
||||||
var codexModelMap = map[string]string{
|
var codexModelMap = map[string]string{
|
||||||
"gpt-5.3": "gpt-5.3",
|
"gpt-5.3": "gpt-5.3-codex",
|
||||||
"gpt-5.3-none": "gpt-5.3",
|
"gpt-5.3-none": "gpt-5.3-codex",
|
||||||
"gpt-5.3-low": "gpt-5.3",
|
"gpt-5.3-low": "gpt-5.3-codex",
|
||||||
"gpt-5.3-medium": "gpt-5.3",
|
"gpt-5.3-medium": "gpt-5.3-codex",
|
||||||
"gpt-5.3-high": "gpt-5.3",
|
"gpt-5.3-high": "gpt-5.3-codex",
|
||||||
"gpt-5.3-xhigh": "gpt-5.3",
|
"gpt-5.3-xhigh": "gpt-5.3-codex",
|
||||||
"gpt-5.3-codex": "gpt-5.3-codex",
|
"gpt-5.3-codex": "gpt-5.3-codex",
|
||||||
"gpt-5.3-codex-low": "gpt-5.3-codex",
|
"gpt-5.3-codex-spark": "gpt-5.3-codex",
|
||||||
"gpt-5.3-codex-medium": "gpt-5.3-codex",
|
"gpt-5.3-codex-spark-low": "gpt-5.3-codex",
|
||||||
"gpt-5.3-codex-high": "gpt-5.3-codex",
|
"gpt-5.3-codex-spark-medium": "gpt-5.3-codex",
|
||||||
"gpt-5.3-codex-xhigh": "gpt-5.3-codex",
|
"gpt-5.3-codex-spark-high": "gpt-5.3-codex",
|
||||||
"gpt-5.1-codex": "gpt-5.1-codex",
|
"gpt-5.3-codex-spark-xhigh": "gpt-5.3-codex",
|
||||||
"gpt-5.1-codex-low": "gpt-5.1-codex",
|
"gpt-5.3-codex-low": "gpt-5.3-codex",
|
||||||
"gpt-5.1-codex-medium": "gpt-5.1-codex",
|
"gpt-5.3-codex-medium": "gpt-5.3-codex",
|
||||||
"gpt-5.1-codex-high": "gpt-5.1-codex",
|
"gpt-5.3-codex-high": "gpt-5.3-codex",
|
||||||
"gpt-5.1-codex-max": "gpt-5.1-codex-max",
|
"gpt-5.3-codex-xhigh": "gpt-5.3-codex",
|
||||||
"gpt-5.1-codex-max-low": "gpt-5.1-codex-max",
|
"gpt-5.1-codex": "gpt-5.1-codex",
|
||||||
"gpt-5.1-codex-max-medium": "gpt-5.1-codex-max",
|
"gpt-5.1-codex-low": "gpt-5.1-codex",
|
||||||
"gpt-5.1-codex-max-high": "gpt-5.1-codex-max",
|
"gpt-5.1-codex-medium": "gpt-5.1-codex",
|
||||||
"gpt-5.1-codex-max-xhigh": "gpt-5.1-codex-max",
|
"gpt-5.1-codex-high": "gpt-5.1-codex",
|
||||||
"gpt-5.2": "gpt-5.2",
|
"gpt-5.1-codex-max": "gpt-5.1-codex-max",
|
||||||
"gpt-5.2-none": "gpt-5.2",
|
"gpt-5.1-codex-max-low": "gpt-5.1-codex-max",
|
||||||
"gpt-5.2-low": "gpt-5.2",
|
"gpt-5.1-codex-max-medium": "gpt-5.1-codex-max",
|
||||||
"gpt-5.2-medium": "gpt-5.2",
|
"gpt-5.1-codex-max-high": "gpt-5.1-codex-max",
|
||||||
"gpt-5.2-high": "gpt-5.2",
|
"gpt-5.1-codex-max-xhigh": "gpt-5.1-codex-max",
|
||||||
"gpt-5.2-xhigh": "gpt-5.2",
|
"gpt-5.2": "gpt-5.2",
|
||||||
"gpt-5.2-codex": "gpt-5.2-codex",
|
"gpt-5.2-none": "gpt-5.2",
|
||||||
"gpt-5.2-codex-low": "gpt-5.2-codex",
|
"gpt-5.2-low": "gpt-5.2",
|
||||||
"gpt-5.2-codex-medium": "gpt-5.2-codex",
|
"gpt-5.2-medium": "gpt-5.2",
|
||||||
"gpt-5.2-codex-high": "gpt-5.2-codex",
|
"gpt-5.2-high": "gpt-5.2",
|
||||||
"gpt-5.2-codex-xhigh": "gpt-5.2-codex",
|
"gpt-5.2-xhigh": "gpt-5.2",
|
||||||
"gpt-5.1-codex-mini": "gpt-5.1-codex-mini",
|
"gpt-5.2-codex": "gpt-5.2-codex",
|
||||||
"gpt-5.1-codex-mini-medium": "gpt-5.1-codex-mini",
|
"gpt-5.2-codex-low": "gpt-5.2-codex",
|
||||||
"gpt-5.1-codex-mini-high": "gpt-5.1-codex-mini",
|
"gpt-5.2-codex-medium": "gpt-5.2-codex",
|
||||||
"gpt-5.1": "gpt-5.1",
|
"gpt-5.2-codex-high": "gpt-5.2-codex",
|
||||||
"gpt-5.1-none": "gpt-5.1",
|
"gpt-5.2-codex-xhigh": "gpt-5.2-codex",
|
||||||
"gpt-5.1-low": "gpt-5.1",
|
"gpt-5.1-codex-mini": "gpt-5.1-codex-mini",
|
||||||
"gpt-5.1-medium": "gpt-5.1",
|
"gpt-5.1-codex-mini-medium": "gpt-5.1-codex-mini",
|
||||||
"gpt-5.1-high": "gpt-5.1",
|
"gpt-5.1-codex-mini-high": "gpt-5.1-codex-mini",
|
||||||
"gpt-5.1-chat-latest": "gpt-5.1",
|
"gpt-5.1": "gpt-5.1",
|
||||||
"gpt-5-codex": "gpt-5.1-codex",
|
"gpt-5.1-none": "gpt-5.1",
|
||||||
"codex-mini-latest": "gpt-5.1-codex-mini",
|
"gpt-5.1-low": "gpt-5.1",
|
||||||
"gpt-5-codex-mini": "gpt-5.1-codex-mini",
|
"gpt-5.1-medium": "gpt-5.1",
|
||||||
"gpt-5-codex-mini-medium": "gpt-5.1-codex-mini",
|
"gpt-5.1-high": "gpt-5.1",
|
||||||
"gpt-5-codex-mini-high": "gpt-5.1-codex-mini",
|
"gpt-5.1-chat-latest": "gpt-5.1",
|
||||||
"gpt-5": "gpt-5.1",
|
"gpt-5-codex": "gpt-5.1-codex",
|
||||||
"gpt-5-mini": "gpt-5.1",
|
"codex-mini-latest": "gpt-5.1-codex-mini",
|
||||||
"gpt-5-nano": "gpt-5.1",
|
"gpt-5-codex-mini": "gpt-5.1-codex-mini",
|
||||||
|
"gpt-5-codex-mini-medium": "gpt-5.1-codex-mini",
|
||||||
|
"gpt-5-codex-mini-high": "gpt-5.1-codex-mini",
|
||||||
|
"gpt-5": "gpt-5.1",
|
||||||
|
"gpt-5-mini": "gpt-5.1",
|
||||||
|
"gpt-5-nano": "gpt-5.1",
|
||||||
}
|
}
|
||||||
|
|
||||||
type codexTransformResult struct {
|
type codexTransformResult struct {
|
||||||
@@ -153,7 +158,7 @@ func normalizeCodexModel(model string) string {
|
|||||||
return "gpt-5.3-codex"
|
return "gpt-5.3-codex"
|
||||||
}
|
}
|
||||||
if strings.Contains(normalized, "gpt-5.3") || strings.Contains(normalized, "gpt 5.3") {
|
if strings.Contains(normalized, "gpt-5.3") || strings.Contains(normalized, "gpt 5.3") {
|
||||||
return "gpt-5.3"
|
return "gpt-5.3-codex"
|
||||||
}
|
}
|
||||||
if strings.Contains(normalized, "gpt-5.1-codex-max") || strings.Contains(normalized, "gpt 5.1 codex max") {
|
if strings.Contains(normalized, "gpt-5.1-codex-max") || strings.Contains(normalized, "gpt 5.1 codex max") {
|
||||||
return "gpt-5.1-codex-max"
|
return "gpt-5.1-codex-max"
|
||||||
|
|||||||
@@ -167,10 +167,13 @@ func TestApplyCodexOAuthTransform_EmptyInput(t *testing.T) {
|
|||||||
|
|
||||||
func TestNormalizeCodexModel_Gpt53(t *testing.T) {
|
func TestNormalizeCodexModel_Gpt53(t *testing.T) {
|
||||||
cases := map[string]string{
|
cases := map[string]string{
|
||||||
"gpt-5.3": "gpt-5.3",
|
"gpt-5.3": "gpt-5.3-codex",
|
||||||
"gpt-5.3-codex": "gpt-5.3-codex",
|
"gpt-5.3-codex": "gpt-5.3-codex",
|
||||||
"gpt-5.3-codex-xhigh": "gpt-5.3-codex",
|
"gpt-5.3-codex-xhigh": "gpt-5.3-codex",
|
||||||
"gpt 5.3 codex": "gpt-5.3-codex",
|
"gpt-5.3-codex-spark": "gpt-5.3-codex",
|
||||||
|
"gpt-5.3-codex-spark-high": "gpt-5.3-codex",
|
||||||
|
"gpt-5.3-codex-spark-xhigh": "gpt-5.3-codex",
|
||||||
|
"gpt 5.3 codex": "gpt-5.3-codex",
|
||||||
}
|
}
|
||||||
|
|
||||||
for input, expected := range cases {
|
for input, expected := range cases {
|
||||||
|
|||||||
@@ -650,11 +650,20 @@ func (s *PricingService) matchByModelFamily(model string) *LiteLLMModelPricing {
|
|||||||
|
|
||||||
// matchOpenAIModel OpenAI 模型回退匹配策略
|
// matchOpenAIModel OpenAI 模型回退匹配策略
|
||||||
// 回退顺序:
|
// 回退顺序:
|
||||||
// 1. gpt-5.2-codex -> gpt-5.2(去掉后缀如 -codex, -mini, -max 等)
|
// 1. gpt-5.3-codex-spark* -> gpt-5.1-codex(按业务要求固定计费)
|
||||||
// 2. gpt-5.2-20251222 -> gpt-5.2(去掉日期版本号)
|
// 2. gpt-5.2-codex -> gpt-5.2(去掉后缀如 -codex, -mini, -max 等)
|
||||||
// 3. gpt-5.3-codex -> gpt-5.2-codex
|
// 3. gpt-5.2-20251222 -> gpt-5.2(去掉日期版本号)
|
||||||
// 4. 最终回退到 DefaultTestModel (gpt-5.1-codex)
|
// 4. gpt-5.3-codex -> gpt-5.2-codex
|
||||||
|
// 5. 最终回退到 DefaultTestModel (gpt-5.1-codex)
|
||||||
func (s *PricingService) matchOpenAIModel(model string) *LiteLLMModelPricing {
|
func (s *PricingService) matchOpenAIModel(model string) *LiteLLMModelPricing {
|
||||||
|
if strings.HasPrefix(model, "gpt-5.3-codex-spark") {
|
||||||
|
if pricing, ok := s.pricingData["gpt-5.1-codex"]; ok {
|
||||||
|
logger.LegacyPrintf("service.pricing", "[Pricing][SparkBilling] %s -> %s billing", model, "gpt-5.1-codex")
|
||||||
|
logger.LegacyPrintf("service.pricing", "[Pricing] OpenAI fallback matched %s -> %s", model, "gpt-5.1-codex")
|
||||||
|
return pricing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 尝试的回退变体
|
// 尝试的回退变体
|
||||||
variants := s.generateOpenAIModelVariants(model, openAIModelDatePattern)
|
variants := s.generateOpenAIModelVariants(model, openAIModelDatePattern)
|
||||||
|
|
||||||
|
|||||||
35
backend/internal/service/pricing_service_test.go
Normal file
35
backend/internal/service/pricing_service_test.go
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
package service
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetModelPricing_Gpt53CodexSparkUsesGpt51CodexPricing(t *testing.T) {
|
||||||
|
sparkPricing := &LiteLLMModelPricing{InputCostPerToken: 1}
|
||||||
|
gpt53Pricing := &LiteLLMModelPricing{InputCostPerToken: 9}
|
||||||
|
|
||||||
|
svc := &PricingService{
|
||||||
|
pricingData: map[string]*LiteLLMModelPricing{
|
||||||
|
"gpt-5.1-codex": sparkPricing,
|
||||||
|
"gpt-5.3": gpt53Pricing,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
got := svc.GetModelPricing("gpt-5.3-codex-spark")
|
||||||
|
require.Same(t, sparkPricing, got)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetModelPricing_Gpt53CodexFallbackStillUsesGpt52Codex(t *testing.T) {
|
||||||
|
gpt52CodexPricing := &LiteLLMModelPricing{InputCostPerToken: 2}
|
||||||
|
|
||||||
|
svc := &PricingService{
|
||||||
|
pricingData: map[string]*LiteLLMModelPricing{
|
||||||
|
"gpt-5.2-codex": gpt52CodexPricing,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
got := svc.GetModelPricing("gpt-5.3-codex")
|
||||||
|
require.Same(t, gpt52CodexPricing, got)
|
||||||
|
}
|
||||||
@@ -716,6 +716,7 @@ const allModels = [
|
|||||||
{ value: 'claude-3-opus-20240229', label: 'Claude 3 Opus' },
|
{ value: 'claude-3-opus-20240229', label: 'Claude 3 Opus' },
|
||||||
{ value: 'claude-3-5-sonnet-20241022', label: 'Claude 3.5 Sonnet' },
|
{ value: 'claude-3-5-sonnet-20241022', label: 'Claude 3.5 Sonnet' },
|
||||||
{ value: 'claude-3-haiku-20240307', label: 'Claude 3 Haiku' },
|
{ value: 'claude-3-haiku-20240307', label: 'Claude 3 Haiku' },
|
||||||
|
{ value: 'gpt-5.3-codex-spark', label: 'GPT-5.3 Codex Spark' },
|
||||||
{ value: 'gpt-5.2-2025-12-11', label: 'GPT-5.2' },
|
{ value: 'gpt-5.2-2025-12-11', label: 'GPT-5.2' },
|
||||||
{ value: 'gpt-5.2-codex', label: 'GPT-5.2 Codex' },
|
{ value: 'gpt-5.2-codex', label: 'GPT-5.2 Codex' },
|
||||||
{ value: 'gpt-5.1-codex-max', label: 'GPT-5.1 Codex Max' },
|
{ value: 'gpt-5.1-codex-max', label: 'GPT-5.1 Codex Max' },
|
||||||
@@ -760,6 +761,12 @@ const presetMappings = [
|
|||||||
to: 'claude-sonnet-4-5-20250929',
|
to: 'claude-sonnet-4-5-20250929',
|
||||||
color: 'bg-amber-100 text-amber-700 hover:bg-amber-200 dark:bg-amber-900/30 dark:text-amber-400'
|
color: 'bg-amber-100 text-amber-700 hover:bg-amber-200 dark:bg-amber-900/30 dark:text-amber-400'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: 'GPT-5.3 Codex Spark',
|
||||||
|
from: 'gpt-5.3-codex-spark',
|
||||||
|
to: 'gpt-5.3-codex-spark',
|
||||||
|
color: 'bg-teal-100 text-teal-700 hover:bg-teal-200 dark:bg-teal-900/30 dark:text-teal-400'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'GPT-5.2',
|
label: 'GPT-5.2',
|
||||||
from: 'gpt-5.2-2025-12-11',
|
from: 'gpt-5.2-2025-12-11',
|
||||||
|
|||||||
@@ -534,6 +534,18 @@ function generateOpenCodeConfig(platform: string, baseUrl: string, apiKey: strin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const openaiModels = {
|
const openaiModels = {
|
||||||
|
'gpt-5.3-codex-spark': {
|
||||||
|
name: 'GPT-5.3 Codex Spark',
|
||||||
|
options: {
|
||||||
|
store: false
|
||||||
|
},
|
||||||
|
variants: {
|
||||||
|
low: {},
|
||||||
|
medium: {},
|
||||||
|
high: {},
|
||||||
|
xhigh: {}
|
||||||
|
}
|
||||||
|
},
|
||||||
'gpt-5.2-codex': {
|
'gpt-5.2-codex': {
|
||||||
name: 'GPT-5.2 Codex',
|
name: 'GPT-5.2 Codex',
|
||||||
options: {
|
options: {
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ const openaiModels = [
|
|||||||
'o4-mini',
|
'o4-mini',
|
||||||
// GPT-5 系列(同步后端定价文件)
|
// GPT-5 系列(同步后端定价文件)
|
||||||
'gpt-5', 'gpt-5-2025-08-07', 'gpt-5-chat', 'gpt-5-chat-latest',
|
'gpt-5', 'gpt-5-2025-08-07', 'gpt-5-chat', 'gpt-5-chat-latest',
|
||||||
'gpt-5-codex', 'gpt-5-pro', 'gpt-5-pro-2025-10-06',
|
'gpt-5-codex', 'gpt-5.3-codex-spark', 'gpt-5-pro', 'gpt-5-pro-2025-10-06',
|
||||||
'gpt-5-mini', 'gpt-5-mini-2025-08-07',
|
'gpt-5-mini', 'gpt-5-mini-2025-08-07',
|
||||||
'gpt-5-nano', 'gpt-5-nano-2025-08-07',
|
'gpt-5-nano', 'gpt-5-nano-2025-08-07',
|
||||||
// GPT-5.1 系列
|
// GPT-5.1 系列
|
||||||
@@ -264,6 +264,7 @@ const openaiPresetMappings = [
|
|||||||
{ label: 'o1', from: 'o1', to: 'o1', color: 'bg-purple-100 text-purple-700 hover:bg-purple-200 dark:bg-purple-900/30 dark:text-purple-400' },
|
{ label: 'o1', from: 'o1', to: 'o1', color: 'bg-purple-100 text-purple-700 hover:bg-purple-200 dark:bg-purple-900/30 dark:text-purple-400' },
|
||||||
{ label: 'o3', from: 'o3', to: 'o3', color: 'bg-emerald-100 text-emerald-700 hover:bg-emerald-200 dark:bg-emerald-900/30 dark:text-emerald-400' },
|
{ label: 'o3', from: 'o3', to: 'o3', color: 'bg-emerald-100 text-emerald-700 hover:bg-emerald-200 dark:bg-emerald-900/30 dark:text-emerald-400' },
|
||||||
{ label: 'GPT-5', from: 'gpt-5', to: 'gpt-5', color: 'bg-amber-100 text-amber-700 hover:bg-amber-200 dark:bg-amber-900/30 dark:text-amber-400' },
|
{ label: 'GPT-5', from: 'gpt-5', to: 'gpt-5', color: 'bg-amber-100 text-amber-700 hover:bg-amber-200 dark:bg-amber-900/30 dark:text-amber-400' },
|
||||||
|
{ label: 'GPT-5.3 Codex Spark', from: 'gpt-5.3-codex-spark', to: 'gpt-5.3-codex-spark', color: 'bg-teal-100 text-teal-700 hover:bg-teal-200 dark:bg-teal-900/30 dark:text-teal-400' },
|
||||||
{ label: 'GPT-5.1', from: 'gpt-5.1', to: 'gpt-5.1', color: 'bg-orange-100 text-orange-700 hover:bg-orange-200 dark:bg-orange-900/30 dark:text-orange-400' },
|
{ label: 'GPT-5.1', from: 'gpt-5.1', to: 'gpt-5.1', color: 'bg-orange-100 text-orange-700 hover:bg-orange-200 dark:bg-orange-900/30 dark:text-orange-400' },
|
||||||
{ label: 'GPT-5.2', from: 'gpt-5.2', to: 'gpt-5.2', color: 'bg-red-100 text-red-700 hover:bg-red-200 dark:bg-red-900/30 dark:text-red-400' },
|
{ label: 'GPT-5.2', from: 'gpt-5.2', to: 'gpt-5.2', color: 'bg-red-100 text-red-700 hover:bg-red-200 dark:bg-red-900/30 dark:text-red-400' },
|
||||||
{ label: 'GPT-5.1 Codex', from: 'gpt-5.1-codex', to: 'gpt-5.1-codex', color: 'bg-cyan-100 text-cyan-700 hover:bg-cyan-200 dark:bg-cyan-900/30 dark:text-cyan-400' }
|
{ label: 'GPT-5.1 Codex', from: 'gpt-5.1-codex', to: 'gpt-5.1-codex', color: 'bg-cyan-100 text-cyan-700 hover:bg-cyan-200 dark:bg-cyan-900/30 dark:text-cyan-400' }
|
||||||
|
|||||||
Reference in New Issue
Block a user