Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions evalboard/lib/pricing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ export const PRICING: Record<string, Pricing> = {
// alias must never inherit the older tier. Undated aliases each need their
// own key: resolvePricing's fallback only strips a trailing date (dated →
// undated), it cannot invent one.
// Fable 5.1 prices cache hits at 0.025x input, not the 0.1x every other
// Claude model uses. Fable 5 pays $1 on the identical $10 base.
"claude-fable-5-1": p(10, 50, 12.5, 0.25),
"claude-fable-5": p(10, 50, 12.5, 1),
"claude-opus-5": p(5, 25, 6.25, 0.5),
"claude-opus-4-8": p(5, 25, 6.25, 0.5),
Expand All @@ -34,7 +37,8 @@ export const PRICING: Record<string, Pricing> = {
"claude-opus-4-1": p(15, 75, 18.75, 1.5),
"claude-opus-4": p(15, 75, 18.75, 1.5),
"claude-opus-4-20250514": p(15, 75, 18.75, 1.5),
"claude-sonnet-5": p(3, 15, 3.75, 0.3),
// $2/$10, not the $3/$15 that 4.6 and earlier pay.
"claude-sonnet-5": p(2, 10, 2.5, 0.2),
"claude-sonnet-4-6": p(3, 15, 3.75, 0.3),
"claude-sonnet-4-5": p(3, 15, 3.75, 0.3),
"claude-sonnet-4-5-20250929": p(3, 15, 3.75, 0.3),
Expand Down Expand Up @@ -64,8 +68,8 @@ export const PRICING: Record<string, Pricing> = {
"gpt-5.2-codex": p(1.75, 14, 1.75, 0.175),
"gpt-5.4": p(2.5, 15, 2.5, 0.25),
"gpt-5.5": p(5, 30, 5, 0.5),
// Terra and Luna repriced 2026-07-30 (-20% / -80%); post-cut rates.
"gpt-5.6-sol": p(5, 30, 5, 0.5),
// Sol's rate is promotional through at least 2026-11-21.
"gpt-5.6-sol": p(4, 20, 4, 0.4),
"gpt-5.6-terra": p(2, 12, 2, 0.2),
"gpt-5.6-luna": p(0.2, 1.2, 0.2, 0.02),
// Google Gemini (AntigravityAgent). Gemini bills no separate cache-write
Expand All @@ -75,6 +79,10 @@ export const PRICING: Record<string, Pricing> = {
"gemini-3-pro-preview": p(2, 12, 2, 0.2),
"gemini-3.1-pro-preview": p(2, 12, 2, 0.2),
"gemini-3.1-pro-preview-customtools": p(2, 12, 2, 0.2),
// 3.6 / 3.7 / 3.8 Flash share one rate card. List rates; Google is
// discounting all three by half through 2026-12-31.
"gemini-3.8-flash": p(1.5, 7.5, 1.5, 0.15),
"gemini-3.7-flash": p(1.5, 7.5, 1.5, 0.15),
"gemini-3.6-flash": p(1.5, 7.5, 1.5, 0.15),
"gemini-3.5-flash": p(1.5, 9, 1.5, 0.15),
"gemini-3.5-flash-lite": p(0.3, 2.5, 0.3, 0.03),
Expand Down
44 changes: 27 additions & 17 deletions src/coder_eval/pricing.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

Anthropic/OpenAI/Google built-in rates; plugins contribute additional rates via
``register_pricing()``. Prices are per million tokens (MTok).
Sources: https://claude.com/pricing#api, https://developers.openai.com/api/docs/pricing,
https://ai.google.dev/gemini-api/docs/pricing (all verified 2026-07-29;
the GPT-5.6 rows re-verified 2026-08-09 after the 2026-07-30 Terra/Luna cut).
Sources: https://platform.claude.com/docs/en/about-claude/pricing,
https://developers.openai.com/api/docs/pricing,
https://ai.google.dev/gemini-api/docs/pricing, and OpenRouter's live
``/api/v1/models`` (every row re-verified 2026-09-03, except the Bedrock
open-weight block: AWS publishes no eu-north-1 figures for those three).
"""

from collections.abc import Iterable
Expand All @@ -21,9 +23,12 @@ class ModelPricing:
cache_read_per_mtok: float # prompt caching read


# Official vendor rate cards, verified 2026-07-29 (GPT-5.6 rows: 2026-08-09).
# Official vendor rate cards, verified 2026-09-03.
# Key: CLI model name (before gateway mapping)
_PRICING: dict[str, ModelPricing] = {
# Fable 5.1 (and Mythos 5.1) price cache hits at 0.025x input, not the 0.1x
# every other Claude model uses. Fable 5 pays $1 on the identical $10 base.
"claude-fable-5-1": ModelPricing(10.0, 50.0, 12.50, 0.25),
"claude-fable-5": ModelPricing(10.0, 50.0, 12.50, 1.0),
# Opus 4.5 and later dropped to $5/$25; 4.1 and 4 keep the old $15/$75. The
# version boundary is the price boundary: a newer Opus is not the dearer one.
Expand All @@ -36,10 +41,9 @@ class ModelPricing:
"claude-opus-4-1": ModelPricing(15.0, 75.0, 18.75, 1.50),
"claude-opus-4": ModelPricing(15.0, 75.0, 18.75, 1.50),
"claude-opus-4-20250514": ModelPricing(15.0, 75.0, 18.75, 1.50),
# Standard $3/$15, not the $2/$10 promo running through 2026-08-31: a static
# table cannot express a window, and overstating for a few weeks beats
# understating indefinitely after it lapses.
"claude-sonnet-5": ModelPricing(3.0, 15.0, 3.75, 0.30),
# $2/$10, NOT the $3/$15 that Sonnet 4.6 and earlier pay. Do not copy the
# 4.x row onto it.
"claude-sonnet-5": ModelPricing(2.0, 10.0, 2.50, 0.20),
"claude-sonnet-4-6": ModelPricing(3.0, 15.0, 3.75, 0.30),
"claude-sonnet-4-5": ModelPricing(3.0, 15.0, 3.75, 0.30),
"claude-sonnet-4-5-20250929": ModelPricing(3.0, 15.0, 3.75, 0.30),
Expand Down Expand Up @@ -80,19 +84,21 @@ class ModelPricing:
"gpt-5.4-mini": ModelPricing(0.75, 4.5, 0.75, 0.075),
"gpt-5.4-nano": ModelPricing(0.20, 1.25, 0.20, 0.02),
# GPT-5.6: sol flagship / terra balanced (Codex default) / luna economy.
# Terra and Luna were REPRICED on 2026-07-30 (-20% and -80%); these are the
# post-cut rates. The pre-cut $2.50/$15 and $1.00/$6 are what a historical run
# was actually billed, but this table is a single current-rate card with no
# notion of an effective date — so old runs re-price low, the same tradeoff
# the Sonnet promo comment above already accepts.
"gpt-5.6-sol": ModelPricing(5.0, 30.0, 5.0, 0.50),
# This table is a single current-rate card with no notion of an effective
# date, so a repriced model makes historical runs re-price at today's rate.
# Sol's rate is promotional through at least 2026-11-21; re-check then.
"gpt-5.6-sol": ModelPricing(4.0, 20.0, 4.0, 0.40),
"gpt-5.6-terra": ModelPricing(2.0, 12.0, 2.0, 0.20),
"gpt-5.6-luna": ModelPricing(0.20, 1.20, 0.20, 0.02),
# Google Gemini (AntigravityAgent, via the Gemini Developer API), keyed on the
# literal ids the ListModels endpoint returns. No cache-write fee, so
# cache_write == input (unused: the agent maps cache_creation_tokens to 0).
# CAVEAT: Pro's >200K-token tier costs more ($4/$18, $0.40 cached), so a
# very-large-context run reads low.
# 3.6 / 3.7 / 3.8 Flash share one rate card. These are list rates; Google is
# discounting all three by half through 2026-12-31.
"gemini-3.8-flash": ModelPricing(1.5, 7.5, 1.5, 0.15),
"gemini-3.7-flash": ModelPricing(1.5, 7.5, 1.5, 0.15),
"gemini-3.6-flash": ModelPricing(1.5, 7.5, 1.5, 0.15),
"gemini-3.5-flash": ModelPricing(1.5, 9.0, 1.5, 0.15),
"gemini-3.5-flash-lite": ModelPricing(0.30, 2.5, 0.30, 0.03),
Expand All @@ -113,10 +119,14 @@ class ModelPricing:
"moonshotai.kimi-k2.5": ModelPricing(0.72, 3.6, 0.72, 0.0),
# OpenRouter models. These providers cache prefixes implicitly (no
# cache_control, no write fee), so cache-creation is priced at input (unused)
# and cache-read at OpenRouter's published input_cache_read rate.
# and cache-read at OpenRouter's published input_cache_read rate, read from
# the live /api/v1/models catalogue. Headline rates only: OpenRouter routes
# per request, so the real bill depends on the provider a call lands on —
# which is why the litellm path captures actual per-call cost proxy-side and
# overrides these (litellm_cost.apply_actual_cost). Static fallback.
"moonshotai/kimi-k3": ModelPricing(3.0, 15.0, 3.0, 0.30),
"z-ai/glm-5.2": ModelPricing(0.7168, 2.2528, 0.7168, 0.13312),
"deepseek/deepseek-v4-pro": ModelPricing(0.435, 0.87, 0.435, 0.003625),
"z-ai/glm-5.2": ModelPricing(0.966, 3.036, 0.966, 0.1932),
"deepseek/deepseek-v4-pro": ModelPricing(1.030776, 2.061552, 1.030776, 0.085898),
}


Expand Down
2 changes: 2 additions & 0 deletions tests/test_antigravity_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,8 @@ def test_tool_name_map_covers_core_builtins():
"gemini-3.1-pro-preview",
"gemini-3.1-pro-preview-customtools",
"gemini-3-pro-preview",
"gemini-3.8-flash",
"gemini-3.7-flash",
"gemini-3.6-flash",
"gemini-3.5-flash",
"gemini-3.5-flash-lite",
Expand Down
6 changes: 3 additions & 3 deletions tests/test_cost_accounting_paths.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ def test_simulator_prices_at_the_route_model_not_the_subject(self):
"""UserSimulator pins model=None, so it bills at BEDROCK_MODEL.

A task that pins ``agent.model`` would otherwise mis-bill every simulated
row. Here the subject is sonnet-5 ($3/$15) while the route is haiku-4.5
row. Here the subject is sonnet-5 ($2/$10) while the route is haiku-4.5
($1/$5), so the simulator must cost the haiku rate.
"""
result = self._simulated(bedrock_model="claude-haiku-4-5-20251001")
Expand All @@ -269,8 +269,8 @@ def test_simulator_prices_at_the_route_model_not_the_subject(self):
def test_simulator_falls_back_to_the_subject_model_off_bedrock(self):
"""A non-Bedrock route names no model on the record; the subject's is the best available."""
result = self._simulated()
# sonnet-5 at $3/$15 per MTok.
assert eval_result_to_task_dict(result)["simulator_cost_usd"] == pytest.approx(3.0 + 1.5)
# sonnet-5 at $2/$10 per MTok: 1M uncached input + 100K output.
assert eval_result_to_task_dict(result)["simulator_cost_usd"] == pytest.approx(2.0 + 1.0)

def test_single_shot_row_has_no_simulator_cost(self):
result = _result([_turn(1, TokenUsage(uncached_input_tokens=10, output_tokens=1, total_cost_usd=0.1))])
Expand Down
Loading