feat(openai): add ultrafast pricing

Add published GPT-6 Astra and GPT-6.1 Sol rates for short and long context. Expose Ultrafast override bands in the pricing API and editor, with billing and validation coverage.

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
Zyy
2026-10-12 15:46:36 +08:00
co-authored by Codex
parent abd60ce60a
commit eb2eefd51e
12 changed files with 202 additions and 34 deletions
@@ -260,10 +260,12 @@ impl SettingsService for DefaultSettingsService {
PricingChange::Replace(pricing) => {
pricing.validate().map_err(AdminError::invalid)?;
if command.provider == "xai"
&& pricing
.bands
.keys()
.any(|band| matches!(band.as_str(), "image" | "flex" | "long_flex"))
&& pricing.bands.keys().any(|band| {
matches!(
band.as_str(),
"image" | "flex" | "long_flex" | "ultrafast" | "long_ultrafast"
)
})
{
return Err(AdminError::invalid("xAI 不支持此价格档位"));
}
@@ -100,6 +100,51 @@ async fn client_profile_preview_rejects_unknown_provider_before_loading_settings
assert_eq!(error.kind(), AdminErrorKind::Invalid);
}
#[tokio::test]
async fn pricing_accepts_ultrafast_for_openai_but_rejects_it_for_xai() {
for provider in ["openai", "xai"] {
let store = Arc::new(PricingSettingsStore::default());
let services = super::AdminHarness::new()
.settings(store.clone())
.provider(super::test_provider(provider))
.build()
.await;
let context = MutationContext {
actor: gateway_admin::model::MutationActor::System,
request_id: "request-ultrafast-pricing".to_owned(),
};
for band in ["ultrafast", "long_ultrafast"] {
let pricing = serde_json::from_value(serde_json::json!({
"multiplierBps": 10000,
"bands": {
"standard": {"input":"2", "output":"10", "cacheRead":"0.1", "cacheWrite":"2.5"},
(band): {"input":"12", "output":"60", "cacheRead":"0.6", "cacheWrite":"15"}
}
}))
.expect("Ultrafast override");
let update = UpdatePricing {
provider: provider.to_owned(),
models: vec!["example-model".to_owned()],
change: PricingChange::Replace(pricing),
};
let result = services
.settings()
.update_pricing(&context, update.clone())
.await;
if provider == "openai" {
result.expect("OpenAI accepts Ultrafast overrides");
assert_eq!(store.updated.lock().unwrap().as_ref(), Some(&update));
} else {
assert_eq!(
result.expect_err("xAI rejects Ultrafast").kind(),
AdminErrorKind::Invalid
);
assert!(store.updated.lock().unwrap().is_none());
}
}
}
}
#[tokio::test]
async fn pricing_accepts_native_provider_and_rejects_unknown_sync_provider() {
let store = Arc::new(PricingSettingsStore::default());
@@ -69,9 +69,11 @@ impl ModelPriceOverride {
band.as_str(),
"standard"
| "fast"
| "ultrafast"
| "flex"
| "long_standard"
| "long_fast"
| "long_ultrafast"
| "long_flex"
| "image"
)
@@ -37,6 +37,19 @@ fn override_validation_rejects_unsupported_bands_and_excessive_multiplier() {
assert!(price.validate().is_err());
}
#[test]
fn override_validation_accepts_ultrafast_bands() {
let pricing: ModelPriceOverride = serde_json::from_value(json!({
"multiplierBps": 10000,
"bands": {
"ultrafast": {"input":"12", "output":"60", "cacheRead":"0.6", "cacheWrite":"15"},
"long_ultrafast": {"input":"24", "output":"90", "cacheRead":"1.2", "cacheWrite":"30"}
}
}))
.unwrap();
assert!(pricing.validate().is_ok());
}
#[test]
fn sync_layer_cannot_erase_manual_bands_or_multiplier() {
let synced: PricingOverrides = serde_json::from_value(json!({"openai":{"model":{
@@ -139,9 +139,11 @@ struct ModelPricing {
standard: TokenRates,
flex: TokenRates,
fast: TokenRates,
ultrafast: TokenRates,
long_standard: TokenRates,
long_flex: TokenRates,
long_fast: TokenRates,
long_ultrafast: TokenRates,
cache_write_percent: u32,
unpriced_long_context: bool,
}
@@ -152,9 +154,11 @@ impl ModelPricing {
standard: TokenRates::new(input, output, cache_read),
flex: TokenRates::ZERO,
fast: TokenRates::ZERO,
ultrafast: TokenRates::ZERO,
long_standard: TokenRates::ZERO,
long_flex: TokenRates::ZERO,
long_fast: TokenRates::ZERO,
long_ultrafast: TokenRates::ZERO,
cache_write_percent: 0,
unpriced_long_context: false,
}
@@ -170,6 +174,11 @@ impl ModelPricing {
self
}
const fn with_ultrafast(mut self, input: u128, output: u128, cache_read: u128) -> Self {
self.ultrafast = TokenRates::new(input, output, cache_read);
self
}
const fn with_long(mut self, input: u128, output: u128, cache_read: u128) -> Self {
self.long_standard = TokenRates::new(input, output, cache_read);
self
@@ -185,6 +194,11 @@ impl ModelPricing {
self
}
const fn with_long_ultrafast(mut self, input: u128, output: u128, cache_read: u128) -> Self {
self.long_ultrafast = TokenRates::new(input, output, cache_read);
self
}
const fn with_cache_write(mut self, percent: u32) -> Self {
self.cache_write_percent = percent;
self
@@ -206,9 +220,11 @@ impl ModelPricing {
(PricingTier::Standard, false) => self.standard,
(PricingTier::Flex, false) => self.flex,
(PricingTier::Fast, false) => self.fast,
(PricingTier::Ultrafast, false) => self.ultrafast,
(PricingTier::Standard, true) => self.long_standard,
(PricingTier::Flex, true) => self.long_flex,
(PricingTier::Fast, true) => self.long_fast,
(PricingTier::Ultrafast, true) => self.long_ultrafast,
};
rates.is_configured().then_some(rates)
}
@@ -219,6 +235,7 @@ enum PricingTier {
Standard,
Flex,
Fast,
Ultrafast,
}
#[derive(Clone, Copy)]
@@ -230,6 +247,7 @@ struct PricingRule {
// 价格来源:https://developers.openai.com/api/docs/pricing
// 只登记已核验的常规价格及服务档位;临时优惠不写入内置价目
// 原有规则于 2026-09-09 核验,GPT-6.1 Sol 于 2026-09-29 核验,其余新增型号见对应条目
// Astra 与 GPT-6.1 Sol 的 Ultrafast 档位于 2026-10-12 对照官方价目表核验
// 已按 https://developers.openai.com/api/docs/deprecations 核验至 2026-09-13,
// 移除已关闭的型号;仅宣布弃用但尚未到关闭日期的型号继续保留
const PRICING_RULES: &[PricingRule] = &[
@@ -241,9 +259,11 @@ const PRICING_RULES: &[PricingRule] = &[
.with_cache_write(125)
.with_flex(50_000, 250_000, 5_000)
.with_fast(200_000, 1_000_000, 20_000)
.with_ultrafast(600_000, 3_000_000, 60_000)
.with_long(200_000, 750_000, 20_000)
.with_long_flex(100_000, 375_000, 10_000)
.with_long_fast(400_000, 1_500_000, 40_000),
.with_long_fast(400_000, 1_500_000, 40_000)
.with_long_ultrafast(1_200_000, 4_500_000, 120_000),
},
PricingRule {
model: "gpt-6.1-sol",
@@ -251,9 +271,11 @@ const PRICING_RULES: &[PricingRule] = &[
.with_cache_write(125)
.with_flex(10_000, 50_000, 500)
.with_fast(40_000, 200_000, 2_000)
.with_ultrafast(120_000, 600_000, 6_000)
.with_long(40_000, 150_000, 2_000)
.with_long_flex(20_000, 75_000, 1_000)
.with_long_fast(80_000, 300_000, 4_000),
.with_long_fast(80_000, 300_000, 4_000)
.with_long_ultrafast(240_000, 900_000, 12_000),
},
// https://developers.openai.com/api/docs/models/gpt-6-sol,核验日期 2026-09-24
PricingRule {
@@ -608,6 +630,7 @@ fn effective_rates(
PricingTier::Standard => "long_standard",
PricingTier::Fast => "long_fast",
PricingTier::Flex => "long_flex",
PricingTier::Ultrafast => "long_ultrafast",
};
let uses_long = long_context
&& (default.is_some_and(|p| p.long_standard.is_configured() || p.unpriced_long_context)
@@ -616,9 +639,11 @@ fn effective_rates(
(PricingTier::Standard, false) => "standard",
(PricingTier::Fast, false) => "fast",
(PricingTier::Flex, false) => "flex",
(PricingTier::Ultrafast, false) => "ultrafast",
(PricingTier::Standard, true) => "long_standard",
(PricingTier::Fast, true) => "long_fast",
(PricingTier::Flex, true) => "long_flex",
(PricingTier::Ultrafast, true) => "long_ultrafast",
};
if let Some(rates) = custom.and_then(|p| p.bands.get(band)) {
return Some((
@@ -656,9 +681,11 @@ pub(crate) fn pricing_catalog()
let bands = [
("standard", pricing.standard),
("fast", pricing.fast),
("ultrafast", pricing.ultrafast),
("flex", pricing.flex),
("long_standard", pricing.long_standard),
("long_fast", pricing.long_fast),
("long_ultrafast", pricing.long_ultrafast),
("long_flex", pricing.long_flex),
]
.into_iter()
@@ -891,6 +918,7 @@ fn pricing_tier(service_tier: Option<&str>) -> Option<PricingTier> {
None | Some("default" | "standard") => Some(PricingTier::Standard),
Some("flex") => Some(PricingTier::Flex),
Some("fast" | "priority") => Some(PricingTier::Fast),
Some("ultrafast") => Some(PricingTier::Ultrafast),
Some(_) => None,
}
}
+24 -5
View File
@@ -412,12 +412,31 @@ async fn copying_builtin_prices_keeps_cache_read_and_write_fallback_costs() {
"gpt-6-sol",
"gpt-6-luna",
] {
let usage = OpenAiBillingUsage::new(100, 10, 20, 15);
let inherited = openai_billing_breakdown(model, usage, None).unwrap();
let copied =
openai_billing_breakdown_with_override(model, usage, None, Some(&prices[model]))
let ultrafast_supported = matches!(model, "gpt-6-astra" | "gpt-6.1-sol");
for band in ["ultrafast", "long_ultrafast"] {
assert_eq!(prices[model].bands.contains_key(band), ultrafast_supported);
}
for tier in [None, Some("ultrafast")] {
if tier.is_some() && !ultrafast_supported {
continue;
}
for input in [100, 272_001] {
let usage = OpenAiBillingUsage::new(input, 10, 20, 15);
let inherited = openai_billing_breakdown(model, usage, tier).unwrap();
let copied = openai_billing_breakdown_with_override(
model,
usage,
tier,
Some(&prices[model]),
)
.unwrap();
assert_eq!(inherited.total_amount(), copied.total_amount(), "{model}");
assert_eq!(
inherited.total_amount(),
copied.total_amount(),
"{model} {tier:?} {input}"
);
}
}
}
}
@@ -1205,6 +1205,8 @@ fn billing_should_follow_requested_tier_independently_of_response_tier() {
for (requested, actual, expected) in [
(Some("priority"), Some("default"), Some(2_000_000_000)),
(Some("fast"), Some("default"), Some(2_000_000_000)),
(Some("ultrafast"), Some("default"), Some(6_000_000_000)),
(Some("ultrafast"), None, Some(6_000_000_000)),
(Some("auto"), Some("fast"), None),
(Some("default"), Some("flex"), Some(1_000_000_000)),
(Some("flex"), Some("priority"), Some(500_000_000)),
@@ -1213,6 +1215,7 @@ fn billing_should_follow_requested_tier_independently_of_response_tier() {
(Some("default"), Some("future"), Some(1_000_000_000)),
(Some("future"), Some("default"), None),
(None, Some("priority"), Some(1_000_000_000)),
(None, Some("ultrafast"), Some(1_000_000_000)),
(None, None, Some(1_000_000_000)),
] {
assert_eq!(
@@ -1230,7 +1233,11 @@ fn billing_should_add_file_and_web_search_fees_without_tier_markup() {
{"type": "file_search_call", "id": "fs_2", "status": "completed"},
{"type": "web_search_call", "id": "ws_1", "status": "completed", "action": {"type": "search"}}
]);
for (tier, expected) in [("default", 1_150_000_000), ("fast", 2_150_000_000)] {
for (tier, expected) in [
("default", 1_150_000_000),
("fast", 2_150_000_000),
("ultrafast", 6_150_000_000),
] {
assert_eq!(
pricing_response_cost(
Some(tier),
@@ -136,6 +136,48 @@ fn new_gpt_6_models_should_use_published_prices_across_tiers_and_context_boundar
}
}
#[test]
fn ultrafast_billing_uses_published_prices_across_the_context_boundary() {
// 每档依次核对每百万 Token 的输入、缓存读取、缓存写入和输出价格
for (model, input, expected, total) in [
("gpt-6-astra", 272_000, ["60", "6", "75", "300"], "16.32057"),
(
"gpt-6-astra",
272_001,
["120", "12", "150", "450"],
"32.64051",
),
(
"gpt-6.1-sol",
272_000,
["12", "0.6", "15", "60"],
"3.264102",
),
(
"gpt-6.1-sol",
272_001,
["24", "1.2", "30", "90"],
"6.528078",
),
] {
let breakdown =
openai_billing_breakdown(model, billing_usage(input, 5, 20, 10), Some("ultrafast"))
.expect("published Ultrafast pricing");
let prices = [
breakdown.input_price_per_million(),
breakdown.cache_read_price_per_million(),
breakdown.cache_write_price_per_million(),
breakdown.output_price_per_million(),
]
.map(|price| price.amount().canonical());
assert_eq!(prices, expected, "{model} {input}");
assert_eq!(breakdown.total_amount().amount().canonical(), total);
assert_eq!(breakdown.service_tier(), Some("ultrafast"));
assert_eq!(breakdown.multiplier_percent(), 600);
assert_eq!(breakdown.long_context_billing_applied(), input > 272_000);
}
}
#[test]
fn billing_breakdown_should_preserve_input_output_and_cache_components() {
let breakdown = openai_billing_breakdown("gpt-5.6-sol", billing_usage(100, 5, 20, 10), None)
@@ -405,7 +447,12 @@ fn billing_should_not_inherit_prices_for_unknown_models_or_tiers() {
("gpt-5.6-cyber", 1, Some("fast")),
("gpt-5.3-codex", 1, Some("flex")),
("gpt-6-astra", 1, Some("auto")),
("gpt-6-astra", 1, Some("ultrafast")),
("gpt-6-astra", 1, Some("future")),
("gpt-6-sol", 1, Some("ultrafast")),
("gpt-6-sol", 272_001, Some("ultrafast")),
("gpt-6-luna", 1, Some("ultrafast")),
("gpt-6-luna", 272_001, Some("ultrafast")),
("gpt-5.4", 1, Some("ultrafast")),
] {
assert!(
openai_billing_breakdown(model, billing_usage(input, 1, 0, 0), tier).is_none(),
@@ -906,29 +953,31 @@ fn custom_price_has_exact_cache_and_multiplier_math() {
}
#[test]
fn a_custom_long_priority_band_does_not_require_a_long_standard_band() {
let pricing = serde_json::from_value(json!({"multiplierBps":10000,"bands":{
"standard":{"input":"2","output":"10","cacheRead":"0","cacheWrite":"0"},
"long_fast":{"input":"8","output":"40","cacheRead":"0","cacheWrite":"0"}
}}))
.unwrap();
let result = openai_billing_breakdown_with_override(
"custom-model",
billing_usage(300_000, 0, 0, 0),
Some("priority"),
Some(&pricing),
)
.unwrap();
assert_eq!(result.total_amount().amount().canonical(), "2.4");
assert_eq!(result.standard_amount().amount().canonical(), "0.6");
assert!(result.long_context_billing_applied());
fn a_custom_long_tier_does_not_require_a_long_standard_band() {
for (tier, band) in [("priority", "long_fast"), ("ultrafast", "long_ultrafast")] {
let pricing = serde_json::from_value(json!({"multiplierBps":10000,"bands":{
"standard":{"input":"2","output":"10","cacheRead":"0","cacheWrite":"0"},
(band):{"input":"8","output":"40","cacheRead":"0","cacheWrite":"0"}
}}))
.unwrap();
let result = openai_billing_breakdown_with_override(
"custom-model",
billing_usage(300_000, 0, 0, 0),
Some(tier),
Some(&pricing),
)
.unwrap();
assert_eq!(result.total_amount().amount().canonical(), "2.4");
assert_eq!(result.standard_amount().amount().canonical(), "0.6");
assert!(result.long_context_billing_applied());
}
}
#[test]
fn multiplier_only_preserves_builtin_tiers_and_unknown_models_remain_unknown() {
let custom: ModelPriceOverride =
serde_json::from_value(json!({"multiplierBps":20000,"bands":{}})).unwrap();
for tier in [None, Some("priority"), Some("flex")] {
for tier in [None, Some("priority"), Some("flex"), Some("ultrafast")] {
for input in [100, 272_001] {
let usage = billing_usage(input, 10, 20, 5);
let original = openai_billing_breakdown("gpt-6-astra", usage, tier).unwrap();
+3 -2
View File
@@ -1536,9 +1536,10 @@ Guardian 以 `subagent_kind` 或 `client_metadata.x-openai-subagent` 值 `guardi
每个档位包含四个非负十进制字符串:`input`、`output`、`cacheRead`、`cacheWrite`,单位 USD / 百万
Token,范围 0~1000000、最多四位小数。`"0"` 表示免费,缺少整个档位表示继承;不能只缺少部分单价
`bands` 可用键为 `standard`、`fast`、`flex`、`long_standard`、`long_fast`、`long_flex`、`image`。
`bands` 可用键为 `standard`、`fast`、`ultrafast`、`flex`、`long_standard`、`long_fast`、`long_ultrafast`、`long_flex`、`image`。
OpenAI 长上下文为输入超过 272000 Token,xAI 为输入达到 200000 Token;仍按 Provider 支持的
模型与服务档位判断是否能估算。xAI 不接受 `flex`、`long_flex`、`image`。图像端点的 `standard`
模型与服务档位判断是否能估算。OpenAI 内置 Ultrafast 价目覆盖 `gpt-6-astra` 与 `gpt-6.1-sol`。
xAI 不接受 `flex`、`long_flex`、`ultrafast`、`long_ultrafast`、`image`。图像端点的 `standard`
用于文本输入,`image` 用于图像 Token;不能用文本输出单价替代图像输出单价。用量无法完整拆分时不估算
`multiplierBps` 为 0~1000000 的整数,10000 表示 1 倍、0 表示本地估算为零,最大 100 倍。
+1 -1
View File
@@ -1,7 +1,7 @@
import type { RequestOptions } from '../../request'
import request from '../../request'
export type PriceBand = 'standard' | 'fast' | 'flex' | 'long_standard' | 'long_fast' | 'long_flex' | 'image'
export type PriceBand = 'standard' | 'fast' | 'ultrafast' | 'flex' | 'long_standard' | 'long_fast' | 'long_ultrafast' | 'long_flex' | 'image'
export interface TokenPrices {
input: string
@@ -32,7 +32,7 @@ const priceMode = computed({
const bandOptions = computed(() => bands
.filter(band => props.row?.base.bands.image
? ['standard', 'image'].includes(band.value)
: props.provider !== 'xai' || !['flex', 'long_flex', 'image'].includes(band.value))
: props.provider !== 'xai' || !['flex', 'long_flex', 'ultrafast', 'long_ultrafast', 'image'].includes(band.value))
.map(band => ({ ...band, label: band.value === 'standard' && props.row?.base.bands.image ? '文本 Token' : band.label })))
watch(open, (value) => {
@@ -3,9 +3,11 @@ import type { ModelPricing, PriceBand, PricingCatalog, TokenPrices } from '@/api
export const bands: { value: PriceBand, label: string }[] = [
{ value: 'standard', label: '标准' },
{ value: 'fast', label: 'Priority / Fast' },
{ value: 'ultrafast', label: 'Ultrafast' },
{ value: 'flex', label: 'Flex' },
{ value: 'long_standard', label: '长上下文 · 标准' },
{ value: 'long_fast', label: '长上下文 · Priority' },
{ value: 'long_ultrafast', label: '长上下文 · Ultrafast' },
{ value: 'long_flex', label: '长上下文 · Flex' },
{ value: 'image', label: '图像 Token' },
]