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:
Daniel Han 2026-05-25 13:27:51 +00:00
commit f66df7ba38
2 changed files with 110 additions and 0 deletions

View file

@ -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,
},

View file

@ -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