Add Anthropic fast_mode pricing multiplier
PR 5715 wires the fast-mode-2026-02-01 beta header + speed:"fast" field through to Anthropic, but the cost calculator never learnt about the matching 6x premium documented at https://platform.claude.com/docs/en/build-with-claude/fast-mode (Opus 4.7 standard $5/$25 per MTok, fast $30/$150). This adds: - ANTHROPIC_FAST_MODE_MULT = 6.0 constant. - calculate_cost(..., fast_mode=True) applies the 6x to base input AND output rates before any cache multipliers (cache mults stack on top of fast per Anthropic docs). - Provider+model gate: silently no-op on every model that is not claude-opus-4-6 / claude-opus-4-7 so a stray fast_mode=True on Sonnet/Haiku can never over-charge. - model_priced label tagged "(fast)" so the cost tooltip can surface which rate fired. - pricing_snapshot now exposes fast_mode_mult so the frontend cost panel doesn't have to hard-code 6. 7 new edge tests pin the math; existing 55 still pass.
This commit is contained in:
parent
df1b980de7
commit
f66df7ba38
2 changed files with 110 additions and 0 deletions
|
|
@ -106,6 +106,15 @@ ANTHROPIC_CACHE_5M_WRITE_MULT = 1.25
|
|||
ANTHROPIC_CACHE_1H_WRITE_MULT = 2.0
|
||||
ANTHROPIC_CACHE_READ_MULT = 0.1
|
||||
|
||||
# Anthropic fast_mode (Opus 4.6/4.7 only) bills BOTH input AND output
|
||||
# at 6x standard rates per
|
||||
# https://platform.claude.com/docs/en/build-with-claude/fast-mode
|
||||
# (Opus 4.7 standard: $5/$25 per MTok; fast: $30/$150 per MTok).
|
||||
# Cache multipliers stack on top of the fast premium. Exposed via
|
||||
# pricing_snapshot so the frontend cost panel can apply it when
|
||||
# inferenceParams.fastMode was set on the request (see #5715).
|
||||
ANTHROPIC_FAST_MODE_MULT = 6.0
|
||||
|
||||
# OpenAI: cache reads are 0.1x base input, cache writes are not billed
|
||||
# separately (the first prefix-write request just pays normal input).
|
||||
OPENAI_CACHE_READ_MULT = 0.1
|
||||
|
|
@ -165,6 +174,8 @@ def calculate_cost(
|
|||
provider: str,
|
||||
model: str,
|
||||
usage: dict[str, Any],
|
||||
*,
|
||||
fast_mode: bool = False,
|
||||
) -> dict[str, float]:
|
||||
"""Return a per-turn USD cost breakdown.
|
||||
|
||||
|
|
@ -296,6 +307,23 @@ def calculate_cost(
|
|||
base = prices["input_per_mtok"]
|
||||
out_per = prices["output_per_mtok"]
|
||||
|
||||
# Anthropic fast_mode bills BOTH input AND output at 6x; the
|
||||
# premium stacks on top of the cache multipliers (Anthropic docs:
|
||||
# "Prompt caching multipliers apply on top of fast mode pricing").
|
||||
# Restricted to claude-opus-4-6 / 4-7 -- silently no-op on every
|
||||
# other model so a stray ``fast_mode=True`` on a Sonnet can't
|
||||
# over-charge the user.
|
||||
if (
|
||||
fast_mode
|
||||
and provider == "anthropic"
|
||||
and (model.startswith("claude-opus-4-6") or model.startswith("claude-opus-4-7"))
|
||||
):
|
||||
base = base * ANTHROPIC_FAST_MODE_MULT
|
||||
out_per = out_per * ANTHROPIC_FAST_MODE_MULT
|
||||
out["model_priced"] = (
|
||||
f"{out['model_priced'] or model} (fast)"
|
||||
)
|
||||
|
||||
out["input_usd"] = (input_tokens / 1_000_000.0) * base
|
||||
out["output_usd"] = (output_tokens / 1_000_000.0) * out_per
|
||||
|
||||
|
|
@ -379,6 +407,7 @@ def pricing_snapshot() -> dict[str, Any]:
|
|||
"cache_5m_write_mult": ANTHROPIC_CACHE_5M_WRITE_MULT,
|
||||
"cache_1h_write_mult": ANTHROPIC_CACHE_1H_WRITE_MULT,
|
||||
"cache_read_mult": ANTHROPIC_CACHE_READ_MULT,
|
||||
"fast_mode_mult": ANTHROPIC_FAST_MODE_MULT,
|
||||
"web_search_usd_per_1k": ANTHROPIC_WEB_SEARCH_USD_PER_1K,
|
||||
"code_execution_usd_per_hour": ANTHROPIC_CODE_EXEC_USD_PER_HOUR,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -32,11 +32,13 @@ import math
|
|||
from core.inference.pricing import (
|
||||
ANTHROPIC_CACHE_5M_WRITE_MULT,
|
||||
ANTHROPIC_CACHE_READ_MULT,
|
||||
ANTHROPIC_FAST_MODE_MULT,
|
||||
ANTHROPIC_PRICING,
|
||||
OPENAI_CACHE_READ_MULT,
|
||||
OPENAI_PRICING,
|
||||
_lookup,
|
||||
calculate_cost,
|
||||
pricing_snapshot,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -499,3 +501,82 @@ def test_calculate_cost_uses_forwarded_cache_creation_for_1h_premium():
|
|||
# 1M tokens at 1h-premium (2x of $5 = $10/M = $10). 5m baseline
|
||||
# would be 1.25x ($6.25). 2x means cache_write_usd ~= $10.
|
||||
assert math.isclose(r["cache_write_usd"], 10.0, rel_tol = 1e-2), r
|
||||
|
||||
|
||||
# ── Anthropic fast_mode pricing ───────────────────────────────────
|
||||
|
||||
|
||||
def test_fast_mode_bills_input_and_output_at_6x_on_opus_47():
|
||||
"""Opus 4.7 standard: $5/$25; fast: $30/$150."""
|
||||
standard = calculate_cost(
|
||||
"anthropic", "claude-opus-4-7",
|
||||
{"input_tokens": 1_000_000, "output_tokens": 1_000_000},
|
||||
)
|
||||
fast = calculate_cost(
|
||||
"anthropic", "claude-opus-4-7",
|
||||
{"input_tokens": 1_000_000, "output_tokens": 1_000_000},
|
||||
fast_mode = True,
|
||||
)
|
||||
assert math.isclose(standard["input_usd"], 5.0)
|
||||
assert math.isclose(standard["output_usd"], 25.0)
|
||||
assert math.isclose(fast["input_usd"], 30.0)
|
||||
assert math.isclose(fast["output_usd"], 150.0)
|
||||
assert "fast" in fast["model_priced"]
|
||||
|
||||
|
||||
def test_fast_mode_applies_to_opus_46_too():
|
||||
fast = calculate_cost(
|
||||
"anthropic", "claude-opus-4-6",
|
||||
{"input_tokens": 1_000_000, "output_tokens": 0},
|
||||
fast_mode = True,
|
||||
)
|
||||
assert math.isclose(fast["input_usd"], 30.0), fast
|
||||
|
||||
|
||||
def test_fast_mode_silently_dropped_on_non_opus_46_47():
|
||||
"""Stray fast_mode=True on Sonnet/Haiku must not over-charge."""
|
||||
for model in ("claude-sonnet-4-5", "claude-sonnet-4-6", "claude-haiku-4-5"):
|
||||
std = calculate_cost(
|
||||
"anthropic", model,
|
||||
{"input_tokens": 1_000_000, "output_tokens": 0},
|
||||
)
|
||||
fast = calculate_cost(
|
||||
"anthropic", model,
|
||||
{"input_tokens": 1_000_000, "output_tokens": 0},
|
||||
fast_mode = True,
|
||||
)
|
||||
assert math.isclose(std["input_usd"], fast["input_usd"]), (model, std, fast)
|
||||
|
||||
|
||||
def test_fast_mode_ignored_on_openai():
|
||||
"""Provider gate: fast_mode is Anthropic-only."""
|
||||
std = calculate_cost(
|
||||
"openai", "gpt-5.4",
|
||||
{"input_tokens": 1_000_000, "output_tokens": 0},
|
||||
)
|
||||
fast = calculate_cost(
|
||||
"openai", "gpt-5.4",
|
||||
{"input_tokens": 1_000_000, "output_tokens": 0},
|
||||
fast_mode = True,
|
||||
)
|
||||
assert math.isclose(std["input_usd"], fast["input_usd"]), (std, fast)
|
||||
|
||||
|
||||
def test_fast_mode_stacks_with_cache_read_discount():
|
||||
"""Cache reads stay at 0.1x base * fast_mult per Anthropic docs."""
|
||||
r = calculate_cost(
|
||||
"anthropic", "claude-opus-4-7",
|
||||
{
|
||||
"input_tokens": 0,
|
||||
"output_tokens": 0,
|
||||
"cache_read_input_tokens": 1_000_000,
|
||||
},
|
||||
fast_mode = True,
|
||||
)
|
||||
# 1M cache_read at 0.1x base (where base = $5 * 6 = $30) = $3.
|
||||
assert math.isclose(r["cache_read_usd"], 3.0, rel_tol = 1e-3), r
|
||||
|
||||
|
||||
def test_pricing_snapshot_exposes_fast_mode_mult():
|
||||
snap = pricing_snapshot()
|
||||
assert snap["anthropic"]["fast_mode_mult"] == 6.0
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue