Skip to content
Open
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
147 changes: 115 additions & 32 deletions src/claude_monitor/core/pricing.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,24 +27,84 @@ class PricingCalculator:
"""

FALLBACK_PRICING: Dict[str, Dict[str, float]] = {
"opus": {
"opus-4-5": {
"input": 5.0,
"output": 25.0,
"cache_creation": 6.25,
"cache_read": 0.5,
},
"sonnet-4-5": {
"input": 3.0,
"output": 15.0,
"cache_creation": 3.75,
"cache_read": 0.3,
},
"haiku-4-5": {
"input": 1.0,
"output": 5.0,
"cache_creation": 1.25,
"cache_read": 0.1,
},
"opus-4": {
"input": 15.0,
"output": 75.0,
"cache_creation": 18.75,
"cache_read": 1.5,
},
"sonnet": {
"sonnet-4": {
"input": 3.0,
"output": 15.0,
"cache_creation": 3.75,
"cache_read": 0.3,
},
"haiku": {
"haiku-3-5": {
"input": 0.8,
"output": 4.0,
"cache_creation": 1.0,
"cache_read": 0.08,
},
"sonnet-3-5": {
"input": 3.0,
"output": 15.0,
"cache_creation": 3.75,
"cache_read": 0.3,
},
"opus-3": {
"input": 15.0,
"output": 75.0,
"cache_creation": 18.75,
"cache_read": 1.5,
},
"sonnet-3": {
"input": 3.0,
"output": 15.0,
"cache_creation": 3.75,
"cache_read": 0.3,
},
"haiku-3": {
"input": 0.25,
"output": 1.25,
"cache_creation": 0.3,
"cache_read": 0.03,
},
"opus": {
"input": 5.0,
"output": 25.0,
"cache_creation": 6.25,
"cache_read": 0.5,
},
"sonnet": {
"input": 3.0,
"output": 15.0,
"cache_creation": 3.75,
"cache_read": 0.3,
},
"haiku": {
"input": 1.0,
"output": 5.0,
"cache_creation": 1.25,
"cache_read": 0.1,
},
}
Comment on lines 29 to 108
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🌐 Web query:

Claude API pricing January 2025 official documentation

💡 Result:

As of January 2025, Anthropic’s official Claude API documentation lists token-based pricing (per 1M tokens) and related multipliers. Key published rates:

  • Claude Sonnet (mid‑tier): Input $3 / 1M, Output $15 / 1M. [1][2]
  • Claude Haiku (cost‑efficient): Input $0.80 / 1M, Output $4 / 1M. [1][2]
  • Claude Opus (highest capability): Input $15 / 1M, Output $75 / 1M. [2]

Other documented details:

  • Prompt‑caching and cache read/write multipliers apply (cache writes/read charged differently). [2]
  • Batch (asynchronous) API: typically a 50% discount on input/output token rates for supported models. [1]
  • Long‑context (1M token window) premium rates apply for >200K input tokens on Sonnet 4/4.5 (higher per‑token rates). [1]

Sources: Anthropic / Claude official docs. [1] https://docs.claude.com/en/docs/about-claude/pricing [2] https://docs.anthropic.com/en/docs/about-claude/models/all-models


🌐 Web query:

Claude 4.5 opus sonnet haiku pricing per million tokens 2025

💡 Result:

Here are Anthropic’s Claude 4.5 (Opus / Sonnet / Haiku) API prices per million tokens (USD):

  • Opus 4.5 — Input: $5 / 1M tokens; Output: $25 / 1M. [1][2]
  • Sonnet 4.5 — Input: $3 / 1M (prompts >200K: $6 / 1M); Output: $15 / 1M (prompts >200K: $22.50 / 1M). [2][3]
  • Haiku 4.5 — Input: $1 / 1M; Output: $5 / 1M. [2][3]

Sources: Anthropic product/news and pricing pages. [1] Anthropic announcement; [2] Claude pricing page; [3] Claude docs (models & pricing).


🌐 Web query:

Claude 4 opus sonnet pricing input output per million tokens

💡 Result:

Summary (per‑million‑token API pricing):

  • Claude Opus 4 / Opus 4.1 — Input: $15 / MTok; Output: $75 / MTok. [1][2]
  • Claude Opus 4.5 — Input: $5 / MTok; Output: $25 / MTok. [2][3]
  • Claude Sonnet 4 (and Sonnet 3.7) — Input: $3 / MTok; Output: $15 / MTok. [1][2]
  • Claude Sonnet 4.5 — Typical: Input $3 / MTok and Output $15 / MTok for prompts ≤200K tokens; higher rates apply for prompts >200K (Input $6 / MTok, Output $22.50 / MTok). [2][3]

Sources: Anthropic/Claude pricing pages and docs. [1] [2] [3]

References:
[1] Anthropic models overview / pricing. (docs.anthropic.com)
[2] Claude pricing page (official). (claude.com)
[3] Recent pricing summary / blog posts (Opus/Sonnet 4.5 announcements). (website.claude.com)

Citations:


🌐 Web query:

Claude 3.5 haiku sonnet pricing input output per million tokens

💡 Result:

Short answer — per Anthropic’s docs (current model pages):

  • Claude 3.5 Sonnet: $3 /M input tokens; $15 /M output tokens. [1][2]
  • Claude 3.5 Haiku: $0.80 /M input tokens; $4 /M output tokens. [1][2]

Note: some news reporting (Nov 2024) states Claude 3.5 Haiku was later listed at $1 /M input and $5 /M output; you may see either pair in different pages. [3]

Sources: Anthropic model/pricing docs and Claude pricing page. [1][2][3]


🌐 Web query:

Claude 3 opus sonnet haiku pricing input output per million tokens

💡 Result:

Summary (price per 1M tokens, USD):

  • Claude Opus (Claude 3 / Opus series): Input $15 / MTok — Output $75 / MTok. [1][2]
  • Claude Sonnet (Claude 3 Sonnet family): Input $3 / MTok — Output $15 / MTok. [1][2]
  • Claude Haiku (Claude 3 family): depends on sub‑version — e.g. Haiku 4.5: Input $1 / MTok — Output $5 / MTok; older Haiku variants: Haiku 3.5 ≈ $0.80 input / $4 output, Haiku 3 ≈ $0.25 input / $1.25 output. [1][2]

Notes: Anthropic’s docs also list prompt‑caching multipliers (5‑min and 1‑hr cache write rates and cache read rates) that affect billed rates; see the cited pricing table for details. [1][2]

Sources: Anthropic / Claude docs pricing tables. [1] [2]


🌐 Web query:

Claude prompt caching pricing cache write cache read multiplier per million tokens

💡 Result:

Short answer:

  • Multiplier: cache write = 1.25× base input price (25% higher); cache read = 0.10× base input price (10% of base). [1][2]

  • Example prices (per MTok = per million tokens) reported by Anthropic for Claude models:

    • Claude 3.5 Sonnet — Input $3 / MTok; Cache write $3.75 / MTok; Cache read $0.30 / MTok. [1]
    • Claude 3 Opus — Input $15 / MTok; Cache write $18.75 / MTok; Cache read $1.50 / MTok. [1]
    • Claude 3 Haiku — Input $0.25 / MTok; Cache write $0.30 / MTok; Cache read $0.03 / MTok. [1]
    • (Docs also show 5‑min vs 1‑hour write multipliers where 1‑hour writes = 2× base input price). [2]

Sources: Anthropic prompt-caching blog and Claude pricing docs. [1][2]


Pricing structure verified against official documentation and correctly implemented.

All base input/output prices match Anthropic's official Claude API pricing as of January 2025:

  • 4.5 models: Opus ($5/$25), Sonnet ($3/$15), Haiku ($1/$5) ✓
  • 4 models: Opus ($15/$75), Sonnet ($3/$15) ✓
  • 3.5 models: Sonnet ($3/$15), Haiku ($0.80/$4) ✓
  • 3 models: Opus ($15/$75), Sonnet ($3/$15), Haiku ($0.25/$1.25) ✓

Cache pricing multipliers are correctly applied (1.25× for cache writes, 0.10× for cache reads).

Minor note: The haiku-3-5 entry uses $0.80/$4 per official documentation, but Anthropic updated Claude 3.5 Haiku pricing to $1/$5 in November 2024. Consider updating haiku-3-5 to match the current rate, or document why the earlier pricing is retained. The generic haiku fallback correctly defaults to the current 4.5 pricing ($1/$5).

🤖 Prompt for AI Agents
In `@src/claude_monitor/core/pricing.py` around lines 29 - 108, FALLBACK_PRICING
contains an outdated rate for the "haiku-3-5" key; update the "haiku-3-5" entry
in the FALLBACK_PRICING dict to use input: 1.0 and output: 5.0 (and adjust
related cache_creation/cache_read if you want them to match the 4.5 haiku
multipliers) so it matches the current Claude 3.5 Haiku pricing; locate the
FALLBACK_PRICING constant and change the "haiku-3-5" values accordingly (or add
a comment explaining why the older 0.8/4 values are intentionally retained if
you choose not to update).


def __init__(
Expand All @@ -56,15 +116,17 @@ def __init__(
custom_pricing: Optional custom pricing dictionary to override defaults.
Should follow same structure as MODEL_PRICING.
"""
# Use fallback pricing if no custom pricing provided
self.pricing: Dict[str, Dict[str, float]] = custom_pricing or {
"claude-3-opus": self.FALLBACK_PRICING["opus"],
"claude-3-sonnet": self.FALLBACK_PRICING["sonnet"],
"claude-3-haiku": self.FALLBACK_PRICING["haiku"],
"claude-3-5-sonnet": self.FALLBACK_PRICING["sonnet"],
"claude-3-5-haiku": self.FALLBACK_PRICING["haiku"],
"claude-sonnet-4-20250514": self.FALLBACK_PRICING["sonnet"],
"claude-opus-4-20250514": self.FALLBACK_PRICING["opus"],
"claude-opus-4-5": self.FALLBACK_PRICING["opus-4-5"],
"claude-sonnet-4-5": self.FALLBACK_PRICING["sonnet-4-5"],
"claude-haiku-4-5": self.FALLBACK_PRICING["haiku-4-5"],
"claude-opus-4": self.FALLBACK_PRICING["opus-4"],
"claude-sonnet-4": self.FALLBACK_PRICING["sonnet-4"],
"claude-3-5-sonnet": self.FALLBACK_PRICING["sonnet-3-5"],
"claude-3-5-haiku": self.FALLBACK_PRICING["haiku-3-5"],
"claude-3-opus": self.FALLBACK_PRICING["opus-3"],
"claude-3-sonnet": self.FALLBACK_PRICING["sonnet-3"],
"claude-3-haiku": self.FALLBACK_PRICING["haiku-3"],
}
self._cost_cache: Dict[str, float] = {}

Expand Down Expand Up @@ -132,6 +194,13 @@ def calculate_cost(
self._cost_cache[cache_key] = cost
return cost

def _ensure_cache_pricing(self, pricing: Dict[str, float]) -> Dict[str, float]:
if "cache_creation" not in pricing:
pricing["cache_creation"] = pricing["input"] * 1.25
if "cache_read" not in pricing:
pricing["cache_read"] = pricing["input"] * 0.1
return pricing

def _get_pricing_for_model(
self, model: str, strict: bool = False
) -> Dict[str, float]:
Expand All @@ -152,34 +221,48 @@ def _get_pricing_for_model(

# Check configured pricing
if normalized in self.pricing:
pricing = self.pricing[normalized]
# Ensure cache pricing exists
if "cache_creation" not in pricing:
pricing["cache_creation"] = pricing["input"] * 1.25
if "cache_read" not in pricing:
pricing["cache_read"] = pricing["input"] * 0.1
return pricing

# Check original model name
return self._ensure_cache_pricing(self.pricing[normalized])

if model in self.pricing:
pricing = self.pricing[model]
if "cache_creation" not in pricing:
pricing["cache_creation"] = pricing["input"] * 1.25
if "cache_read" not in pricing:
pricing["cache_read"] = pricing["input"] * 0.1
return pricing

# If strict mode, raise KeyError for unknown models
return self._ensure_cache_pricing(self.pricing[model])

if strict:
raise KeyError(f"Unknown model: {model}")

# Fallback to hardcoded pricing based on model type
return self._get_fallback_pricing(model)

def _get_fallback_pricing(self, model: str) -> Dict[str, float]:
model_lower = model.lower()

is_4_5 = "4-5" in model_lower or "4.5" in model_lower
is_4 = not is_4_5 and (
"4-" in model_lower or "-4-" in model_lower or "4.1" in model_lower
)
is_3_5 = "3-5" in model_lower or "3.5" in model_lower

if "opus" in model_lower:
return self.FALLBACK_PRICING["opus"]
if is_4_5:
return self.FALLBACK_PRICING["opus-4-5"]
if is_4:
return self.FALLBACK_PRICING["opus-4"]
return self.FALLBACK_PRICING["opus-3"]
Comment on lines +237 to +248
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Version detection may miss models ending in "-4".

The is_4 check looks for "4-" or "-4-" in the model name, but won't match model names that end with -4 (e.g., "claude-opus-4", "some-opus-4"). While currently mapped models are handled in self.pricing, future model variations or API name differences could fall through incorrectly.

🔧 Suggested fix to handle trailing -4
-        is_4 = not is_4_5 and (
-            "4-" in model_lower or "-4-" in model_lower or "4.1" in model_lower
-        )
+        is_4 = not is_4_5 and (
+            "4-" in model_lower or "-4-" in model_lower or 
+            model_lower.endswith("-4") or "4.1" in model_lower
+        )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
is_4_5 = "4-5" in model_lower or "4.5" in model_lower
is_4 = not is_4_5 and (
"4-" in model_lower or "-4-" in model_lower or "4.1" in model_lower
)
is_3_5 = "3-5" in model_lower or "3.5" in model_lower
if "opus" in model_lower:
return self.FALLBACK_PRICING["opus"]
if is_4_5:
return self.FALLBACK_PRICING["opus-4-5"]
if is_4:
return self.FALLBACK_PRICING["opus-4"]
return self.FALLBACK_PRICING["opus-3"]
is_4_5 = "4-5" in model_lower or "4.5" in model_lower
is_4 = not is_4_5 and (
"4-" in model_lower or "-4-" in model_lower or
model_lower.endswith("-4") or "4.1" in model_lower
)
is_3_5 = "3-5" in model_lower or "3.5" in model_lower
if "opus" in model_lower:
if is_4_5:
return self.FALLBACK_PRICING["opus-4-5"]
if is_4:
return self.FALLBACK_PRICING["opus-4"]
return self.FALLBACK_PRICING["opus-3"]
🤖 Prompt for AI Agents
In `@src/claude_monitor/core/pricing.py` around lines 237 - 248, The is_4
detection can miss model names that end with "-4" (e.g., "claude-opus-4");
update the is_4 boolean in pricing.py to also check for
model_lower.endswith("-4") (keeping the existing is_4_5 precedence) so that the
block that returns self.FALLBACK_PRICING["opus-4"] correctly handles trailing
"-4" names; change the is_4 assignment (the variable named is_4 that uses
model_lower, and the subsequent if "opus" branch that returns opus-4) to include
this endswith check.


if "haiku" in model_lower:
return self.FALLBACK_PRICING["haiku"]
# Default to Sonnet pricing
if is_4_5:
return self.FALLBACK_PRICING["haiku-4-5"]
if is_3_5:
return self.FALLBACK_PRICING["haiku-3-5"]
return self.FALLBACK_PRICING["haiku-3"]

if "sonnet" in model_lower:
if is_4_5:
return self.FALLBACK_PRICING["sonnet-4-5"]
if is_4:
return self.FALLBACK_PRICING["sonnet-4"]
if is_3_5:
return self.FALLBACK_PRICING["sonnet-3-5"]
return self.FALLBACK_PRICING["sonnet-3"]

return self.FALLBACK_PRICING["sonnet"]

def calculate_cost_for_entry(
Expand Down