mirror of
https://github.com/docker/docker-agent.git
synced 2026-11-04 10:02:15 +00:00
fix: price OpenAI requests by actual service tier
Remove the model allowlist and instead derive the Fast tier premium from matching catalogue entries, falling back to the documented multiplier when metadata is missing or inconsistent. Endpoint and cost-override guards, tests, and docs are preserved. Signed-off-by: David Gageot <david.gageot@docker.com>
This commit is contained in:
9 files changed
+171
-48
No files matched your search
@@ -74,12 +74,12 @@ OpenAI also accepts `priority` for Fast mode. It provides faster processing at p
|
||||
|
||||
The value is forwarded unchanged to Chat Completions (including reranking) and Responses requests, over either SSE or WebSocket. OpenAI-compatible providers using these APIs also receive the option when set; the endpoint must support it. Other tiers, such as `auto`, `default`, and `flex`, can also be requested; availability and valid values depend on the API and model. When omitted or empty, no `service_tier` is sent, leaving the API's default behavior unchanged. Non-string values are ignored.
|
||||
|
||||
For GPT-6 Astra, request [Ultrafast mode](https://developers.openai.com/api/docs/guides/ultrafast-mode) with `service_tier: ultrafast`.
|
||||
For models that support it, such as GPT-6 Astra and GPT-6.1 Sol, request [Ultrafast mode](https://developers.openai.com/api/docs/guides/ultrafast-mode) with `service_tier: ultrafast`.
|
||||
|
||||
Cost estimates use the **actual response tier**, not the requested tier. For models served by the official OpenAI endpoint (including an explicitly configured `base_url: https://api.openai.com/v1`), Fast (`fast` or `priority`) applies 2× catalogue rates to `gpt-5.6` (the Sol alias), `gpt-5.6-sol`, `gpt-5.6-terra`, `gpt-5.6-luna`, `gpt-6-astra`, `gpt-6-sol`, `gpt-6-luna`, and `gpt-6.1-sol`. Ultrafast applies 6× rates to `gpt-6-astra` only. These adjustments cover input, cached input, cache writes, and output, including the applicable long-context band. A response reporting `default` retains standard pricing, even if Fast was requested.
|
||||
Cost estimates use the **actual response tier**, not the requested tier. For models served by the official OpenAI endpoint (including an explicitly configured `base_url: https://api.openai.com/v1`), an actual Fast tier (`fast` or `priority`) uses a catalogue-derived premium when matching Vercel Standard/Fast entries have the same base rates and a uniform premium across token types. Otherwise it is estimated at 2× catalogue rates. `ultrafast` is estimated at 6× rates. The response selects the tier; Docker Agent does not maintain a model allowlist. These adjustments cover input, cached input, cache writes, and output, including the applicable long-context band. A response reporting `default` retains standard pricing, even if Fast was requested.
|
||||
|
||||
> [!WARNING]
|
||||
> Missing response tiers, unlisted models or tiers, custom endpoints (including `OPENAI_BASE_URL`), rule-based routers, and other providers retain catalogue pricing. Gateway model IDs already priced as `-fast` are not multiplied again. Estimates do not include endpoint surcharges or negotiated discounts. Use a model's [`cost` override](../../configuration/models/index.md#custom-token-pricing) when automatic pricing does not apply; overrides replace the entire price table and are never multiplied by the service tier.
|
||||
> Missing or unrecognized response tiers, custom endpoints (including `OPENAI_BASE_URL`), rule-based routers, and other providers retain catalogue pricing. Gateway model IDs already priced as `-fast` are not multiplied again. The response reports the tier, not a price. Catalogue-derived premiums cover differing Fast rates such as GPT-4o and GPT-5.5; missing or inconsistent tier metadata falls back to the estimate. Estimates do not include endpoint surcharges or negotiated discounts. Use a model's [`cost` override](../../configuration/models/index.md#custom-token-pricing) when a model's published tier rates differ or automatic pricing does not apply. Overrides replace the entire price table, including context bands, and are never adjusted for the service tier—even after a downgrade.
|
||||
|
||||
See [`examples/openai-service-tier.yaml`](https://github.com/docker/docker-agent/blob/main/examples/openai-service-tier.yaml) for a complete example.
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# Cost estimates use the actual response tier for supported OpenAI models.
|
||||
# Cost estimates use the actual response tier, not the requested tier or model name.
|
||||
# https://developers.openai.com/api/docs/guides/fast-mode
|
||||
# https://developers.openai.com/api/docs/guides/ultrafast-mode
|
||||
|
||||
agents:
|
||||
root:
|
||||
model: fast-gpt # Switch to ultrafast-astra to use Ultrafast mode.
|
||||
model: fast-gpt # Switch to ultrafast-astra or ultrafast-sol for Ultrafast mode.
|
||||
description: An assistant using OpenAI Fast mode.
|
||||
instruction: You are a helpful assistant.
|
||||
|
||||
@@ -19,3 +19,8 @@ models:
|
||||
model: gpt-6-astra
|
||||
provider_opts:
|
||||
service_tier: ultrafast
|
||||
ultrafast-sol:
|
||||
provider: openai
|
||||
model: gpt-6.1-sol
|
||||
provider_opts:
|
||||
service_tier: ultrafast
|
||||
@@ -159,6 +159,9 @@ func TestServiceTierUsageTransports(t *testing.T) {
|
||||
{requested: "ultrafast", actual: "ultrafast"},
|
||||
{requested: "fast", actual: "default"},
|
||||
{requested: "ultrafast", actual: "default"},
|
||||
{requested: "ultrafast", actual: "fast"},
|
||||
{requested: "fast", actual: "ultrafast"},
|
||||
{actual: "ultrafast"},
|
||||
{actual: "fast"},
|
||||
{requested: "fast"},
|
||||
} {
|
||||
|
||||
@@ -1,41 +1,29 @@
|
||||
package modelsdev
|
||||
|
||||
import (
|
||||
"math"
|
||||
"slices"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ForServiceTier adjusts catalogue rates for the actual response tier without mutating c.
|
||||
// Unlisted models, providers and tiers retain their catalogue pricing.
|
||||
// ForServiceTier estimates tier pricing from the actual response tier without mutating c.
|
||||
// Other providers and unrecognized tiers retain their catalogue pricing.
|
||||
func (c *Cost) ForServiceTier(id ID, tier string) *Cost {
|
||||
if c == nil || id.Provider != "openai" {
|
||||
return c
|
||||
}
|
||||
model := id.Model
|
||||
if len(model) > len("-2006-01-02") {
|
||||
cut := len(model) - len("2006-01-02")
|
||||
if model[cut-1] == '-' {
|
||||
if _, err := time.Parse(time.DateOnly, model[cut:]); err == nil {
|
||||
model = model[:cut-1]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// https://developers.openai.com/api/docs/pricing; don't extrapolate to future models.
|
||||
factor := 1.0
|
||||
// Current tier premiums; models with different rates need a cost override.
|
||||
// https://developers.openai.com/api/docs/pricing
|
||||
var factor float64
|
||||
switch tier {
|
||||
case "fast", "priority":
|
||||
switch model {
|
||||
case "gpt-5.6", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna",
|
||||
"gpt-6-astra", "gpt-6-sol", "gpt-6-luna", "gpt-6.1-sol":
|
||||
factor = c.fastMultiplier
|
||||
if factor == 0 {
|
||||
factor = 2
|
||||
}
|
||||
case "ultrafast":
|
||||
if model == "gpt-6-astra" {
|
||||
factor = 6
|
||||
}
|
||||
}
|
||||
if factor == 1 {
|
||||
factor = 6
|
||||
default:
|
||||
return c
|
||||
}
|
||||
|
||||
@@ -53,3 +41,66 @@ func (c *Cost) ForServiceTier(id ID, tier string) *Cost {
|
||||
}
|
||||
return &out
|
||||
}
|
||||
|
||||
// withFastPricing derives a uniform premium only when gateway and vendor base rates agree.
|
||||
func (c *Cost) withFastPricing(db *Database, id ID) *Cost {
|
||||
if c == nil || id.Provider != "openai" {
|
||||
return c
|
||||
}
|
||||
models := db.Providers["vercel"].Models
|
||||
model := id.Model
|
||||
for {
|
||||
standard := models["openai/"+model].Cost
|
||||
fast := models["openai/"+model+"-fast"].Cost
|
||||
if standard != nil && fast != nil && standard.RatesFor(0) == c.RatesFor(0) {
|
||||
if factor := uniformPremium(standard, fast); factor > 0 {
|
||||
out := *c
|
||||
out.fastMultiplier = factor
|
||||
return &out
|
||||
}
|
||||
}
|
||||
// Dated models may share the alias's pricing, but never assume its base rates match.
|
||||
if len(model) <= len("-2006-01-02") {
|
||||
return c
|
||||
}
|
||||
cut := len(model) - len("2006-01-02")
|
||||
if model[cut-1] != '-' {
|
||||
return c
|
||||
}
|
||||
if _, err := time.Parse(time.DateOnly, model[cut:]); err != nil {
|
||||
return c
|
||||
}
|
||||
model = model[:cut-1]
|
||||
}
|
||||
}
|
||||
|
||||
func uniformPremium(standard, fast *Cost) float64 {
|
||||
factor := fast.Input / standard.Input
|
||||
if factor < 1 || math.IsNaN(factor) || math.IsInf(factor, 0) {
|
||||
return 0
|
||||
}
|
||||
matches := func(base, premium Rates) bool {
|
||||
return closeRate(base.Input*factor, premium.Input) &&
|
||||
closeRate(base.Output*factor, premium.Output) &&
|
||||
closeRate(base.CacheRead*factor, premium.CacheRead) &&
|
||||
closeRate(base.CacheWrite*factor, premium.CacheWrite)
|
||||
}
|
||||
if !matches(standard.RatesFor(0), fast.RatesFor(0)) {
|
||||
return 0
|
||||
}
|
||||
for _, tier := range fast.Tiers {
|
||||
if tier.Tier.Size < 0 || tier.Tier.Size == math.MaxInt64 || (tier.Tier.Type != "" && tier.Tier.Type != "context") {
|
||||
return 0
|
||||
}
|
||||
// Gateway thresholds may name the first long-context token, rather than the last short one.
|
||||
if !matches(standard.RatesFor(tier.Tier.Size+1), tier.Rates) {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
return factor
|
||||
}
|
||||
|
||||
func closeRate(a, b float64) bool {
|
||||
return !math.IsNaN(a) && !math.IsNaN(b) && !math.IsInf(a, 0) && !math.IsInf(b, 0) &&
|
||||
math.Abs(a-b) <= 1e-9*math.Max(1, math.Max(math.Abs(a), math.Abs(b)))
|
||||
}
|
||||
@@ -35,17 +35,18 @@ func TestCostForServiceTier(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCostForServiceTierModels(t *testing.T) {
|
||||
func TestCostForServiceTierIndependentOfModel(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cost := &Cost{Input: 1}
|
||||
for _, model := range []string{"gpt-5.6", "gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna", "gpt-6-astra", "gpt-6-sol", "gpt-6-luna", "gpt-6.1-sol"} {
|
||||
for _, tier := range []string{"fast", "priority"} {
|
||||
assert.InDelta(t, 2.0, cost.ForServiceTier(NewID("openai", model), tier).Input, 1e-9, "%s/%s", model, tier)
|
||||
assert.InDelta(t, 2.0, cost.ForServiceTier(NewID("openai", model+"-2026-09-04"), tier).Input, 1e-9, "dated %s/%s", model, tier)
|
||||
for _, model := range []string{"gpt-6.1-sol", "gpt-6-astra-2026-09-04", "future-model", "future-model-fast"} {
|
||||
for _, tc := range []struct {
|
||||
tier string
|
||||
factor float64
|
||||
}{{"fast", 2}, {"priority", 2}, {"ultrafast", 6}} {
|
||||
assert.InDelta(t, tc.factor, cost.ForServiceTier(NewID("openai", model), tc.tier).Input, 1e-9, "%s/%s", model, tc.tier)
|
||||
}
|
||||
}
|
||||
assert.InDelta(t, 6.0, cost.ForServiceTier(NewID("openai", "gpt-6-astra-2026-09-04"), "ultrafast").Input, 1e-9)
|
||||
}
|
||||
|
||||
func TestCostForServiceTierUnchanged(t *testing.T) {
|
||||
@@ -61,13 +62,6 @@ func TestCostForServiceTierUnchanged(t *testing.T) {
|
||||
{"openai", "gpt-6-astra", "flex"},
|
||||
{"openai", "gpt-6-astra", "scale"},
|
||||
{"openai", "gpt-6-astra", "future-tier"},
|
||||
{"openai", "gpt-5.6-luna", "ultrafast"},
|
||||
{"openai", "gpt-6.1-sol", "ultrafast"},
|
||||
{"openai", "gpt-5.5", "fast"},
|
||||
{"openai", "gpt-7-astra", "fast"},
|
||||
{"openai", "gpt-6-astra-fast", "fast"},
|
||||
{"openai", "gpt-6-astra-extra", "fast"},
|
||||
{"openai", "gpt-6-astra-2026-02-30", "fast"},
|
||||
{"vercel", "openai/gpt-6-astra-fast", "fast"},
|
||||
{"azure", "gpt-6-astra", "fast"},
|
||||
{"chatgpt", "gpt-6-astra", "ultrafast"},
|
||||
@@ -78,3 +72,49 @@ func TestCostForServiceTierUnchanged(t *testing.T) {
|
||||
var missing *Cost
|
||||
assert.Nil(t, missing.ForServiceTier(NewID("openai", "gpt-6-astra"), "ultrafast"))
|
||||
}
|
||||
|
||||
func TestCatalogFastPremium(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
base := &Cost{
|
||||
Input: 2, Output: 10, CacheRead: 0.2, CacheWrite: 2.5,
|
||||
Tiers: []CostTier{{Rates: Rates{Input: 4, Output: 15, CacheRead: 0.4, CacheWrite: 5}, Tier: TierSpec{Type: "context", Size: 272_000}}},
|
||||
}
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
standard *Cost
|
||||
fast *Cost
|
||||
factor float64
|
||||
}{
|
||||
{name: "uniform premium", standard: base, fast: &Cost{Input: 5, Output: 25, CacheRead: 0.5, CacheWrite: 6.25}, factor: 2.5},
|
||||
{name: "missing fast", standard: base, factor: 2},
|
||||
{name: "missing standard", fast: &Cost{Input: 5, Output: 25, CacheRead: 0.5, CacheWrite: 6.25}, factor: 2},
|
||||
{name: "different gateway base rates", standard: &Cost{Input: 1}, fast: &Cost{Input: 3}, factor: 2},
|
||||
{name: "different token premiums", standard: base, fast: &Cost{Input: 5, Output: 20, CacheRead: 0.5, CacheWrite: 6.25}, factor: 2},
|
||||
{name: "inconsistent context premium", standard: base, fast: &Cost{
|
||||
Input: 5, Output: 25, CacheRead: 0.5, CacheWrite: 6.25,
|
||||
Tiers: []CostTier{{Rates: Rates{Input: 8, Output: 30, CacheRead: 0.8, CacheWrite: 10}, Tier: TierSpec{Type: "context", Size: 272_001}}},
|
||||
}, factor: 2},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
store := NewDatabaseStore(&Database{Providers: map[string]Provider{
|
||||
"openai": {Models: map[string]Model{"future-model": {Cost: base}, "future-model-2026-10-17": {Cost: base}}},
|
||||
"vercel": {Models: map[string]Model{"openai/future-model": {Cost: tc.standard}, "openai/future-model-fast": {Cost: tc.fast}}},
|
||||
}})
|
||||
for _, name := range []string{"future-model", "future-model-2026-10-17"} {
|
||||
id := NewID("openai", name)
|
||||
model, err := store.GetModel(t.Context(), id)
|
||||
require.NoError(t, err)
|
||||
for _, tier := range []string{"fast", "priority"} {
|
||||
priced := model.Cost.ForServiceTier(id, tier)
|
||||
assert.InDelta(t, 2*tc.factor, priced.RatesFor(100).Input, 1e-9)
|
||||
assert.InDelta(t, 4*tc.factor, priced.RatesFor(300_000).Input, 1e-9)
|
||||
}
|
||||
assert.InDelta(t, 12, model.Cost.ForServiceTier(id, "ultrafast").Input, 1e-9)
|
||||
assert.Same(t, model.Cost, model.Cost.ForServiceTier(id, "default"))
|
||||
assert.Zero(t, base.fastMultiplier, "catalogue metadata must not be mutated")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -297,6 +297,7 @@ func (s *Store) GetModel(ctx context.Context, id ID) (*Model, error) {
|
||||
|
||||
provider, exists := db.Providers[id.Provider]
|
||||
if model, ok := lookupModel(provider, id); ok {
|
||||
model.Cost = model.Cost.withFastPricing(db, id)
|
||||
return &model, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -45,6 +45,9 @@ type Cost struct {
|
||||
CacheWrite float64 `json:"cache_write,omitempty"`
|
||||
|
||||
Tiers []CostTier `json:"tiers,omitempty"`
|
||||
|
||||
// fastMultiplier is derived at lookup from matching catalogue price tables.
|
||||
fastMultiplier float64
|
||||
}
|
||||
|
||||
// Rates is a single price band, in USD per 1M tokens.
|
||||
|
||||
@@ -152,6 +152,10 @@ func TestApplyModelCostServiceTier(t *testing.T) {
|
||||
cfg latest.ModelConfig
|
||||
short, long float64
|
||||
}{
|
||||
{name: "GPT-4o fast", provider: "openai", model: "gpt-4o", tier: "fast", short: 0.34, long: 2.2525},
|
||||
{name: "dated GPT-4o priority", provider: "openai", model: "gpt-4o-2024-08-06", tier: "priority", short: 0.34, long: 2.2525},
|
||||
{name: "GPT-5.5 fast", provider: "openai", model: "gpt-5.5", tier: "fast", short: 1.025, long: 13.1125},
|
||||
{name: "GPT-5.5 downgrade", provider: "openai", model: "gpt-5.5", tier: "default", cfg: latest.ModelConfig{ProviderOpts: map[string]any{"service_tier": "fast"}}, short: 0.41, long: 5.245},
|
||||
{name: "Luna fast", provider: "openai", model: "gpt-5.6-luna", tier: "fast", short: 0.0478, long: 0.4496},
|
||||
{name: "Luna priority alias", provider: "openai", model: "gpt-5.6-luna", tier: "priority", short: 0.0478, long: 0.4496},
|
||||
{name: "Astra fast", provider: "openai", model: "gpt-6-astra", tier: "fast", short: 2.29, long: 22.33},
|
||||
@@ -160,7 +164,12 @@ func TestApplyModelCostServiceTier(t *testing.T) {
|
||||
{name: "actual fast without requested tier", provider: "openai", model: "gpt-6-astra", tier: "fast", short: 2.29, long: 22.33},
|
||||
{name: "missing actual tier", provider: "openai", model: "gpt-6-astra", cfg: latest.ModelConfig{ProviderOpts: map[string]any{"service_tier": "ultrafast"}}, short: 1.145, long: 11.165},
|
||||
{name: "unknown tier", provider: "openai", model: "gpt-6-astra", tier: "future-tier", short: 1.145, long: 11.165},
|
||||
{name: "unsupported Luna ultrafast", provider: "openai", model: "gpt-5.6-luna", tier: "ultrafast", short: 0.0239, long: 0.2248},
|
||||
{name: "Luna reported ultrafast", provider: "openai", model: "gpt-5.6-luna", tier: "ultrafast", short: 0.1434, long: 1.3488},
|
||||
{name: "Sol 6.1 fast", provider: "openai", model: "gpt-6.1-sol", tier: "fast", short: 0.454, long: 4.458},
|
||||
{name: "Sol 6.1 ultrafast", provider: "openai", model: "gpt-6.1-sol", tier: "ultrafast", short: 1.362, long: 13.374},
|
||||
{name: "Sol 6.1 downgraded to fast", provider: "openai", model: "gpt-6.1-sol", tier: "fast", cfg: latest.ModelConfig{ProviderOpts: map[string]any{"service_tier": "ultrafast"}}, short: 0.454, long: 4.458},
|
||||
{name: "Sol 6.1 downgraded to default", provider: "openai", model: "gpt-6.1-sol", tier: "default", cfg: latest.ModelConfig{ProviderOpts: map[string]any{"service_tier": "ultrafast"}}, short: 0.227, long: 2.229},
|
||||
{name: "Sol 6.1 missing actual tier", provider: "openai", model: "gpt-6.1-sol", cfg: latest.ModelConfig{ProviderOpts: map[string]any{"service_tier": "ultrafast"}}, short: 0.227, long: 2.229},
|
||||
{name: "gateway fast already priced", provider: "vercel", model: "openai/gpt-6-astra-fast", tier: "fast", short: 2.29, long: 22.33},
|
||||
{name: "Azure unchanged", provider: "azure", model: "gpt-6-astra", tier: "fast", short: 1.145, long: 11.165},
|
||||
{name: "custom OpenAI endpoint unchanged", provider: "openai", model: "gpt-6-astra", tier: "ultrafast", cfg: latest.ModelConfig{BaseURL: "https://example.com/v1"}, short: 1.145, long: 11.165},
|
||||
@@ -233,7 +242,7 @@ func TestApplyModelCostEndpointEligibility(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestLunaAstraPublishedCostBoundary(t *testing.T) {
|
||||
func TestPublishedCostBoundary(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
store := modelsdev.NewDatabaseStore(modelsdev.EmbeddedSnapshot())
|
||||
@@ -249,6 +258,9 @@ func TestLunaAstraPublishedCostBoundary(t *testing.T) {
|
||||
{"Astra fast", "openai", "gpt-6-astra", "fast", modelsdev.Rates{Input: 20, CacheRead: 2, CacheWrite: 25, Output: 100}, modelsdev.Rates{Input: 40, CacheRead: 4, CacheWrite: 50, Output: 150}},
|
||||
{"Astra priority", "openai", "gpt-6-astra", "priority", modelsdev.Rates{Input: 20, CacheRead: 2, CacheWrite: 25, Output: 100}, modelsdev.Rates{Input: 40, CacheRead: 4, CacheWrite: 50, Output: 150}},
|
||||
{"Astra ultrafast", "openai", "gpt-6-astra", "ultrafast", modelsdev.Rates{Input: 60, CacheRead: 6, CacheWrite: 75, Output: 300}, modelsdev.Rates{Input: 120, CacheRead: 12, CacheWrite: 150, Output: 450}},
|
||||
{"Sol 6.1 standard", "openai", "gpt-6.1-sol", "default", modelsdev.Rates{Input: 2, CacheRead: 0.1, CacheWrite: 2.5, Output: 10}, modelsdev.Rates{Input: 4, CacheRead: 0.2, CacheWrite: 5, Output: 15}},
|
||||
{"Sol 6.1 fast", "openai", "gpt-6.1-sol", "fast", modelsdev.Rates{Input: 4, CacheRead: 0.2, CacheWrite: 5, Output: 20}, modelsdev.Rates{Input: 8, CacheRead: 0.4, CacheWrite: 10, Output: 30}},
|
||||
{"Sol 6.1 ultrafast", "openai", "gpt-6.1-sol", "ultrafast", modelsdev.Rates{Input: 12, CacheRead: 0.6, CacheWrite: 15, Output: 60}, modelsdev.Rates{Input: 24, CacheRead: 1.2, CacheWrite: 30, Output: 90}},
|
||||
{"gateway Astra", "vercel", "openai/gpt-6-astra", "default", modelsdev.Rates{Input: 10, CacheRead: 1, CacheWrite: 12.5, Output: 50}, modelsdev.Rates{Input: 20, CacheRead: 2, CacheWrite: 25, Output: 75}},
|
||||
{"gateway Astra fast", "vercel", "openai/gpt-6-astra-fast", "fast", modelsdev.Rates{Input: 20, CacheRead: 2, CacheWrite: 25, Output: 100}, modelsdev.Rates{Input: 40, CacheRead: 4, CacheWrite: 50, Output: 150}},
|
||||
} {
|
||||
|
||||
@@ -31,6 +31,9 @@ func TestRunStreamServiceTierCost(t *testing.T) {
|
||||
{name: "Luna priority", model: "gpt-5.6-luna", tier: "priority", want: 0.0478},
|
||||
{name: "Astra fast", model: "gpt-6-astra", tier: "fast", want: 2.29},
|
||||
{name: "Astra ultrafast", model: "gpt-6-astra", tier: "ultrafast", want: 6.87},
|
||||
{name: "Sol 6.1 fast", model: "gpt-6.1-sol", tier: "fast", want: 0.454},
|
||||
{name: "Sol 6.1 ultrafast", model: "gpt-6.1-sol", tier: "ultrafast", want: 1.362},
|
||||
{name: "Sol 6.1 fallback ultrafast", model: "gpt-6.1-sol", tier: "ultrafast", fallback: true, want: 1.362},
|
||||
{name: "standard downgrade", model: "gpt-6-astra", tier: "default", want: 1.145},
|
||||
{name: "fallback ultrafast", model: "gpt-6-astra", tier: "ultrafast", fallback: true, want: 6.87},
|
||||
{name: "fallback override", model: "gpt-6-astra", tier: "ultrafast", fallback: true, cost: &latest.CostConfig{Input: 1, Output: 2, CacheRead: 0.1, CacheWrite: 1.25}, want: 0.0995},
|
||||
@@ -108,7 +111,7 @@ func TestRunStreamServiceTierCost(t *testing.T) {
|
||||
func TestServiceTierMixedTurnsPersistCost(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
for _, modelID := range []string{"gpt-5.6-luna", "gpt-6-astra"} {
|
||||
for _, modelID := range []string{"gpt-5.6-luna", "gpt-6-astra", "gpt-6.1-sol"} {
|
||||
t.Run(modelID, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
model := &configuredProvider{cfg: latest.ModelConfig{Provider: "openai", Model: modelID, BaseURL: "https://api.openai.com/v1", ProviderOpts: map[string]any{"service_tier": "fast"}}}
|
||||
@@ -128,10 +131,12 @@ func TestServiceTierMixedTurnsPersistCost(t *testing.T) {
|
||||
input int64
|
||||
luna float64
|
||||
astra float64
|
||||
sol float64
|
||||
}{
|
||||
{"fast", 50_000, 0.0478, 2.29},
|
||||
{"default", 500_000, 0.2248, 11.165},
|
||||
{"priority", 500_000, 0.4496, 22.33},
|
||||
{"fast", 50_000, 0.0478, 2.29, 0.454},
|
||||
{"default", 500_000, 0.2248, 11.165, 2.229},
|
||||
{"priority", 500_000, 0.4496, 22.33, 4.458},
|
||||
{"ultrafast", 500_000, 1.3488, 66.99, 13.374},
|
||||
} {
|
||||
stream := newStreamBuilder().AddContent("ok").AddStopWithUsage(0, 0).Build()
|
||||
stream.responses[len(stream.responses)-1].Usage = &chat.Usage{InputTokens: turn.input, CachedInputTokens: 20_000, CacheWriteTokens: 30_000, OutputTokens: 5_000, ReasoningTokens: 3_000, ServiceTier: turn.tier}
|
||||
@@ -142,8 +147,11 @@ func TestServiceTierMixedTurnsPersistCost(t *testing.T) {
|
||||
}
|
||||
}
|
||||
want := turn.luna
|
||||
if modelID == "gpt-6-astra" {
|
||||
switch modelID {
|
||||
case "gpt-6-astra":
|
||||
want = turn.astra
|
||||
case "gpt-6.1-sol":
|
||||
want = turn.sol
|
||||
}
|
||||
wantTotal += want
|
||||
assert.InDelta(t, wantTotal, sess.TotalCost(), 1e-9)
|
||||
@@ -162,7 +170,7 @@ func TestServiceTierMixedTurnsPersistCost(t *testing.T) {
|
||||
tiers = append(tiers, message.Message.Usage.ServiceTier)
|
||||
}
|
||||
}
|
||||
assert.Equal(t, []string{"fast", "default", "priority"}, tiers)
|
||||
assert.Equal(t, []string{"fast", "default", "priority", "ultrafast"}, tiers)
|
||||
summaries, err := reopened.GetSessionSummaries(t.Context())
|
||||
require.NoError(t, err)
|
||||
require.Len(t, summaries, 1)
|
||||
|
||||
Reference in new issue
Block a user