mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-03-30 02:09:43 +00:00
- 打通 service_tier 在 OpenAI HTTP、WS、passthrough 与 usage 记录中的传递 - 修正 priority/flex 计费逻辑,并将 fast 归一化为 priority - 在用户端和管理端补齐服务档位与计费明细展示 - 补齐前后端测试,并修复 WS 限流信号重复持久化导致的全量回归失败 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
161 lines
5.5 KiB
Go
161 lines
5.5 KiB
Go
package service
|
|
|
|
import (
|
|
"encoding/json"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestParsePricingData_ParsesPriorityAndServiceTierFields(t *testing.T) {
|
|
svc := &PricingService{}
|
|
body := []byte(`{
|
|
"gpt-5.4": {
|
|
"input_cost_per_token": 0.0000025,
|
|
"input_cost_per_token_priority": 0.000005,
|
|
"output_cost_per_token": 0.000015,
|
|
"output_cost_per_token_priority": 0.00003,
|
|
"cache_creation_input_token_cost": 0.0000025,
|
|
"cache_read_input_token_cost": 0.00000025,
|
|
"cache_read_input_token_cost_priority": 0.0000005,
|
|
"supports_service_tier": true,
|
|
"supports_prompt_caching": true,
|
|
"litellm_provider": "openai",
|
|
"mode": "chat"
|
|
}
|
|
}`)
|
|
|
|
data, err := svc.parsePricingData(body)
|
|
require.NoError(t, err)
|
|
pricing := data["gpt-5.4"]
|
|
require.NotNil(t, pricing)
|
|
require.InDelta(t, 5e-6, pricing.InputCostPerTokenPriority, 1e-12)
|
|
require.InDelta(t, 3e-5, pricing.OutputCostPerTokenPriority, 1e-12)
|
|
require.InDelta(t, 5e-7, pricing.CacheReadInputTokenCostPriority, 1e-12)
|
|
require.True(t, pricing.SupportsServiceTier)
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
func TestGetModelPricing_OpenAIFallbackMatchedLoggedAsInfo(t *testing.T) {
|
|
logSink, restore := captureStructuredLog(t)
|
|
defer restore()
|
|
|
|
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)
|
|
|
|
require.True(t, logSink.ContainsMessageAtLevel("[Pricing] OpenAI fallback matched gpt-5.3-codex -> gpt-5.2-codex", "info"))
|
|
require.False(t, logSink.ContainsMessageAtLevel("[Pricing] OpenAI fallback matched gpt-5.3-codex -> gpt-5.2-codex", "warn"))
|
|
}
|
|
|
|
func TestGetModelPricing_Gpt54UsesStaticFallbackWhenRemoteMissing(t *testing.T) {
|
|
svc := &PricingService{
|
|
pricingData: map[string]*LiteLLMModelPricing{
|
|
"gpt-5.1-codex": &LiteLLMModelPricing{InputCostPerToken: 1.25e-6},
|
|
},
|
|
}
|
|
|
|
got := svc.GetModelPricing("gpt-5.4")
|
|
require.NotNil(t, got)
|
|
require.InDelta(t, 2.5e-6, got.InputCostPerToken, 1e-12)
|
|
require.InDelta(t, 1.5e-5, got.OutputCostPerToken, 1e-12)
|
|
require.InDelta(t, 2.5e-7, got.CacheReadInputTokenCost, 1e-12)
|
|
require.Equal(t, 272000, got.LongContextInputTokenThreshold)
|
|
require.InDelta(t, 2.0, got.LongContextInputCostMultiplier, 1e-12)
|
|
require.InDelta(t, 1.5, got.LongContextOutputCostMultiplier, 1e-12)
|
|
}
|
|
|
|
func TestParsePricingData_PreservesPriorityAndServiceTierFields(t *testing.T) {
|
|
raw := map[string]any{
|
|
"gpt-5.4": map[string]any{
|
|
"input_cost_per_token": 2.5e-6,
|
|
"input_cost_per_token_priority": 5e-6,
|
|
"output_cost_per_token": 15e-6,
|
|
"output_cost_per_token_priority": 30e-6,
|
|
"cache_read_input_token_cost": 0.25e-6,
|
|
"cache_read_input_token_cost_priority": 0.5e-6,
|
|
"supports_service_tier": true,
|
|
"supports_prompt_caching": true,
|
|
"litellm_provider": "openai",
|
|
"mode": "chat",
|
|
},
|
|
}
|
|
body, err := json.Marshal(raw)
|
|
require.NoError(t, err)
|
|
|
|
svc := &PricingService{}
|
|
pricingMap, err := svc.parsePricingData(body)
|
|
require.NoError(t, err)
|
|
|
|
pricing := pricingMap["gpt-5.4"]
|
|
require.NotNil(t, pricing)
|
|
require.InDelta(t, 2.5e-6, pricing.InputCostPerToken, 1e-12)
|
|
require.InDelta(t, 5e-6, pricing.InputCostPerTokenPriority, 1e-12)
|
|
require.InDelta(t, 15e-6, pricing.OutputCostPerToken, 1e-12)
|
|
require.InDelta(t, 30e-6, pricing.OutputCostPerTokenPriority, 1e-12)
|
|
require.InDelta(t, 0.25e-6, pricing.CacheReadInputTokenCost, 1e-12)
|
|
require.InDelta(t, 0.5e-6, pricing.CacheReadInputTokenCostPriority, 1e-12)
|
|
require.True(t, pricing.SupportsServiceTier)
|
|
}
|
|
|
|
func TestParsePricingData_PreservesServiceTierPriorityFields(t *testing.T) {
|
|
svc := &PricingService{}
|
|
pricingData, err := svc.parsePricingData([]byte(`{
|
|
"gpt-5.4": {
|
|
"input_cost_per_token": 0.0000025,
|
|
"input_cost_per_token_priority": 0.000005,
|
|
"output_cost_per_token": 0.000015,
|
|
"output_cost_per_token_priority": 0.00003,
|
|
"cache_read_input_token_cost": 0.00000025,
|
|
"cache_read_input_token_cost_priority": 0.0000005,
|
|
"supports_service_tier": true,
|
|
"litellm_provider": "openai",
|
|
"mode": "chat"
|
|
}
|
|
}`))
|
|
require.NoError(t, err)
|
|
|
|
pricing := pricingData["gpt-5.4"]
|
|
require.NotNil(t, pricing)
|
|
require.InDelta(t, 0.0000025, pricing.InputCostPerToken, 1e-12)
|
|
require.InDelta(t, 0.000005, pricing.InputCostPerTokenPriority, 1e-12)
|
|
require.InDelta(t, 0.000015, pricing.OutputCostPerToken, 1e-12)
|
|
require.InDelta(t, 0.00003, pricing.OutputCostPerTokenPriority, 1e-12)
|
|
require.InDelta(t, 0.00000025, pricing.CacheReadInputTokenCost, 1e-12)
|
|
require.InDelta(t, 0.0000005, pricing.CacheReadInputTokenCostPriority, 1e-12)
|
|
require.True(t, pricing.SupportsServiceTier)
|
|
}
|