fix(openai): support Astra Ultrafast capabilities and pricing

This commit is contained in:
lyy0709
2026-09-29 11:50:08 -07:00
parent c91bb6124a
commit e6d191a83f
6 changed files with 93 additions and 1 deletions
@@ -96,6 +96,8 @@ type BillingCache interface {
// ModelPricing 模型价格配置(per-token价格,与LiteLLM格式一致)
type ModelPricing struct {
// UltrafastMultiplier is model-owned and independent of operator Fast pricing.
UltrafastMultiplier float64
InputPricePerToken float64 // 每token输入价格 (USD)
InputPricePerTokenPriority float64 // priority service tier 下每token输入价格 (USD)
ImageInputPricePerToken float64 // 图片输入 token 价格 (USD),用于多模态 embedding 等图文不同价场景;为 0 时回退到 InputPricePerToken
@@ -153,6 +155,9 @@ func serviceTierCostMultiplier(serviceTier string) float64 {
func configuredServiceTierMultiplier(serviceTier string, pricing *ModelPricing) float64 {
if pricing != nil {
if normalizeBillingServiceTier(serviceTier) == OpenAIFastTierUltrafast && pricing.UltrafastMultiplier > 0 {
return pricing.UltrafastMultiplier
}
switch normalizeBillingServiceTier(serviceTier) {
case "priority", "fast":
if pricing.FastMultiplier != nil {
@@ -1868,6 +1873,9 @@ func (s *BillingService) applyModelSpecificPricingPolicyEx(model string, pricing
return pricing
}
cloned := *pricing
if isOpenAIGPT6AstraModel(normalized) {
cloned.UltrafastMultiplier = 6
}
if needsOpus55FastMultiplier {
multiplier := 2.0
cloned.FastMultiplier = &multiplier
@@ -2091,6 +2091,44 @@ func TestNewModelPricingAliasesRetainExplicitOverrides(t *testing.T) {
}
}
func TestAstraUltrafastPricingUsesSixTimesStandard(t *testing.T) {
data, err := os.ReadFile("../../resources/model-pricing/model_prices_and_context_window.json")
require.NoError(t, err)
catalog := &PricingService{}
catalog.pricingData, err = catalog.parsePricingData(data)
require.NoError(t, err)
for _, svc := range []*BillingService{newTestBillingService(), NewBillingService(&config.Config{}, &PricingService{}), NewBillingService(&config.Config{}, catalog)} {
for _, model := range []string{"gpt-6-astra", "gpt-6", "openai/gpt-6-astra"} {
for _, n := range []int{271999, 272000, 272001} {
tokens := UsageTokens{InputTokens: n - 3000, CacheReadTokens: 2000, CacheCreationTokens: 1000, OutputTokens: 500}
cost, err := svc.CalculateCostWithServiceTier(model, tokens, 1, "ultrafast")
require.NoError(t, err)
im, om := 1.0, 1.0
if n > 272000 {
im, om = 2, 1.5
}
require.InDelta(t, float64(n-3000)*60e-6*im, cost.InputCost, 1e-10)
require.InDelta(t, 2000*6e-6*im, cost.CacheReadCost, 1e-10)
require.InDelta(t, 1000*75e-6*im, cost.CacheCreationCost, 1e-10)
require.InDelta(t, 500*300e-6*om, cost.OutputCost, 1e-10)
}
}
}
svc := newTestBillingService()
for _, custom := range []float64{0, 1e-6} {
fast := 3.0
p, err := svc.GetModelPricingWithChannel("gpt-6-astra", &ChannelModelPricing{InputPrice: &custom, OutputPrice: &custom, CacheWritePrice: &custom, CacheReadPrice: &custom, FastMultiplier: &fast})
require.NoError(t, err)
require.Equal(t, 6.0, configuredServiceTierMultiplier("ultrafast", p))
require.Equal(t, 3.0, configuredServiceTierMultiplier("priority", p))
cost := svc.computeTokenBreakdown(p, UsageTokens{InputTokens: 1000, OutputTokens: 1000, CacheReadTokens: 1000, CacheCreationTokens: 1000}, 1, "ultrafast", false)
require.InDelta(t, custom*24000, cost.TotalCost, 1e-10)
}
p, err := svc.GetModelPricing("gpt-6-sol")
require.NoError(t, err)
require.Equal(t, 2.0, configuredServiceTierMultiplier("ultrafast", p))
}
func TestGPT61SolExplicitZeroCacheWriteAcrossTiers(t *testing.T) {
pricing := &PricingService{}
var err error
@@ -9,6 +9,7 @@ import (
)
var codexToolCapabilityFields = []string{
"service_tiers",
"supports_search_tool", "apply_patch_tool_type", "comp_hash", "tool_mode", "use_responses_lite",
"multi_agent_reasoning_effort", "multi_agent_version",
}
@@ -22,7 +23,12 @@ func applyCodexToolCapabilities(dst, src map[string]json.RawMessage, overwrite b
}
// These Codex fields are nullable booleans or strings, never arbitrary objects.
if !bytes.Equal(value, []byte("null")) {
if field == "supports_search_tool" || field == "use_responses_lite" {
if field == "service_tiers" {
var tiers []configuredCodexServiceTier
if json.Unmarshal(value, &tiers) != nil {
continue
}
} else if field == "supports_search_tool" || field == "use_responses_lite" {
if !bytes.Equal(value, []byte("true")) && !bytes.Equal(value, []byte("false")) {
continue
}
@@ -88,6 +94,17 @@ func accountCodexToolCapabilities(account *Account, modelID string) map[string]j
capabilities["use_responses_lite"] = json.RawMessage("false")
}
}
// API Astra publicly supports Ultrafast. OAuth must advertise it in its
// account manifest; a subscription label alone does not grant the capability.
if account.IsOpenAIApiKey() && isOfficialOpenAIModelsBaseURL(baseURL) && isOpenAIGPT6AstraModel(modelID) {
tiers := configuredCodexServiceTiersForModel(modelID)
tiers = append(tiers, configuredCodexServiceTier{ID: OpenAIFastTierUltrafast, Name: "Ultrafast", Description: "Lowest latency; 6x Standard token pricing."})
encoded, err := json.Marshal(tiers)
if err != nil {
panic(err)
}
applyCodexToolCapabilities(capabilities, map[string]json.RawMessage{"service_tiers": encoded}, false)
}
return capabilities
}
@@ -3818,6 +3818,28 @@ func TestGPT61SolOfflineCodexCatalog(t *testing.T) {
}
}
func TestAstraUltrafastCatalogUsesAccountCapabilities(t *testing.T) {
for _, tc := range []struct {
accountType, base, model string
want bool
}{
{AccountTypeAPIKey, "https://api.openai.com", "gpt-6-astra", true},
{AccountTypeAPIKey, "https://api.openai.com", "gpt-6.1-sol", false},
{AccountTypeAPIKey, "https://proxy.example", "gpt-6-astra", false},
{AccountTypeOAuth, "https://chatgpt.com", "gpt-6-astra", false},
} {
account := &Account{Platform: PlatformOpenAI, Type: tc.accountType, Credentials: map[string]any{"base_url": tc.base, "plan_type": "promax"}}
caps := accountCodexToolCapabilities(account, tc.model)
require.Equal(t, tc.want, bytes.Contains(caps["service_tiers"], []byte("ultrafast")))
}
// Explicit native null/empty fields and account-provided tiers remain authoritative.
for _, raw := range []string{"null", "[]", `[{"id":"ultrafast","name":"Ultrafast"}]`} {
dst := map[string]json.RawMessage{"service_tiers": json.RawMessage(raw)}
require.False(t, applyCodexToolCapabilities(dst, map[string]json.RawMessage{"service_tiers": json.RawMessage(`[{"id":"priority"}]`)}, false))
require.JSONEq(t, raw, string(dst["service_tiers"]))
}
}
func TestGPT61SolAPIKeyCatalogUsesFullResponses(t *testing.T) {
account := &Account{Platform: PlatformOpenAI, Type: AccountTypeAPIKey, Credentials: map[string]any{"base_url": "https://api.openai.com"}}
body, err := completeAPIKeyCodexModelsManifestMetadata([]byte(`{"models":[{"slug":"gpt-6.1-sol"}]}`), true, account)
@@ -53,6 +53,8 @@ func serviceTierCostRank(tier string) (rank int, known bool) {
return 1, true
case "priority", "fast":
return 2, true
case "ultrafast":
return 3, true
default:
return 1, false
}
@@ -16,6 +16,11 @@ func TestResolveBillingServiceTier(t *testing.T) {
}{
{name: "openai priority served as default", requested: "priority", observed: "default", billing: "default", downgraded: true},
{name: "anthropic fast served as standard", requested: "fast", observed: "standard", billing: "standard", downgraded: true},
{name: "ultrafast served as priority", requested: "ultrafast", observed: "priority", billing: "priority", downgraded: true},
{name: "ultrafast served as default", requested: "ultrafast", observed: "default", billing: "default", downgraded: true},
{name: "ultrafast honoured", requested: "ultrafast", observed: "ultrafast", billing: "ultrafast"},
{name: "ultrafast missing declaration", requested: "ultrafast", billing: "ultrafast"},
{name: "response cannot upgrade to ultrafast", requested: "priority", observed: "ultrafast", billing: "priority"},
{name: "priority honoured", requested: "priority", observed: "priority", billing: "priority"},
{name: "no declaration keeps request", requested: "priority", observed: "", billing: "priority"},
{name: "no request no declaration", requested: "", observed: "", billing: ""},