Skip to content

Commit f9e0777

Browse files
mattgodboltclaude
andcommitted
Update model costs with latest Anthropic pricing
- Add opus-4.1 model support with $15/$75 pricing - Add claude-family-X-Y-date pattern support for opus-4.1 naming - Document that Anthropic doesn't provide programmatic pricing API - Add test coverage for new opus-4.1 model pattern - All existing pricing verified as current per claude.com/pricing 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 7fb1781 commit f9e0777

File tree

2 files changed

+25
-1
lines changed

2 files changed

+25
-1
lines changed

app/model_costs.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
33
This module provides a flexible way to look up model costs based on
44
model names, using pattern matching to handle various naming schemes.
5+
6+
Note: Anthropic does not provide a programmatic API for retrieving pricing
7+
information, so costs are maintained manually based on published pricing.
58
"""
69

710
import re
@@ -16,7 +19,9 @@ class ModelCost(NamedTuple):
1619

1720

1821
# Model family costs in USD per million tokens
22+
# Updated: 2025-09-16 based on https://claude.com/pricing
1923
MODEL_FAMILIES = {
24+
"opus-4.1": ModelCost(15.0, 75.0),
2025
"opus-4": ModelCost(15.0, 75.0),
2126
"sonnet-4": ModelCost(3.0, 15.0),
2227
"sonnet-3.7": ModelCost(3.0, 15.0),
@@ -35,6 +40,7 @@ def normalize_model_name(model: str) -> str:
3540
claude-3-opus-20240229 -> opus-3
3641
claude-sonnet-4-0 -> sonnet-4
3742
claude-3-5-sonnet-20241022 -> sonnet-3.5
43+
claude-opus-4-1-20250805 -> opus-4.1
3844
"""
3945
# Convert to lowercase for consistent matching
4046
model = model.lower()
@@ -59,7 +65,15 @@ def normalize_model_name(model: str) -> str:
5965
return f"{family}-{major}"
6066
return f"{family}-{major}.{minor}"
6167

62-
# Pattern 4: claude-family-X (e.g., claude-opus-4)
68+
# Pattern 4: claude-family-X-Y-date (e.g., claude-opus-4-1-20250805)
69+
match = re.match(r"claude-(\w+)-(\d+)-(\d+)-\d+", model)
70+
if match:
71+
family, major, minor = match.groups()
72+
if minor == "0":
73+
return f"{family}-{major}"
74+
return f"{family}-{major}.{minor}"
75+
76+
# Pattern 5: claude-family-X (e.g., claude-opus-4)
6377
match = re.match(r"claude-(\w+)-(\d+)$", model)
6478
if match:
6579
family, major = match.groups()

app/test_model_costs.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ def test_claude_family_major_pattern(self):
2929
assert normalize_model_name("claude-opus-4") == "opus-4"
3030
assert normalize_model_name("claude-haiku-3") == "haiku-3"
3131

32+
def test_claude_family_version_date_pattern(self):
33+
"""Test claude-family-X-Y-date pattern."""
34+
assert normalize_model_name("claude-opus-4-1-20250805") == "opus-4.1"
35+
3236
def test_case_insensitive(self):
3337
"""Test that normalization is case-insensitive."""
3438
assert normalize_model_name("Claude-3-5-Haiku-20241022") == "haiku-3.5"
@@ -90,6 +94,12 @@ def test_opus_4_cost(self):
9094
assert input_cost == 15.0 / 1_000_000 # $15 per million
9195
assert output_cost == 75.0 / 1_000_000 # $75 per million
9296

97+
def test_opus_4_1_cost(self):
98+
"""Test Claude 4.1 Opus costs."""
99+
input_cost, output_cost = get_model_cost("claude-opus-4-1-20250805")
100+
assert input_cost == 15.0 / 1_000_000 # $15 per million
101+
assert output_cost == 75.0 / 1_000_000 # $75 per million
102+
93103
def test_unknown_model(self):
94104
"""Test that unknown models raise ValueError."""
95105
with pytest.raises(ValueError, match=r"Model family .* not found"):

0 commit comments

Comments
 (0)