llm-costs 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,16 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ build/
6
+ dist/
7
+ wheels/
8
+ *.egg-info/
9
+
10
+ # Virtual environments
11
+ .venv/
12
+
13
+ # Testing/tooling
14
+ .pytest_cache/
15
+ .ruff_cache/
16
+ .mypy_cache/
@@ -0,0 +1 @@
1
+ 3.13.0
@@ -0,0 +1,201 @@
1
+ Metadata-Version: 2.4
2
+ Name: llm-costs
3
+ Version: 0.1.0
4
+ Summary: LLM cost calculator for major providers
5
+ Author-email: jason <jtan92@gmail.com>
6
+ Requires-Python: >=3.13
7
+ Requires-Dist: pyyaml>=6.0
8
+ Provides-Extra: dev
9
+ Requires-Dist: pytest>=8.0; extra == 'dev'
10
+ Requires-Dist: ruff>=0.8; extra == 'dev'
11
+ Description-Content-Type: text/markdown
12
+
13
+ # llm-costs
14
+
15
+ LLM cost calculator for major providers. Calculates API costs from token usage data.
16
+
17
+ ## Installation
18
+
19
+ ```bash
20
+ pip install llm-costs
21
+ ```
22
+
23
+ Or with uv:
24
+
25
+ ```bash
26
+ uv add llm-costs
27
+ ```
28
+
29
+ ## Usage
30
+
31
+ ```python
32
+ from llm_costs import calculate_cost, get_model_pricing
33
+
34
+ # Calculate cost from LangChain UsageMetadata structure
35
+ result = calculate_cost(
36
+ provider="anthropic",
37
+ model="claude-sonnet-4-20250514",
38
+ usage={
39
+ "input_tokens": 1000,
40
+ "output_tokens": 500,
41
+ "total_tokens": 1500,
42
+ },
43
+ )
44
+
45
+ print(f"Cost: ${result['cost']:.6f}") # Cost: $0.010500
46
+ print(f"Input: ${result['breakdown']['input_cost']:.6f}")
47
+ print(f"Output: ${result['breakdown']['output_cost']:.6f}")
48
+ ```
49
+
50
+ ### With Prompt Caching
51
+
52
+ ```python
53
+ result = calculate_cost(
54
+ provider="anthropic",
55
+ model="claude-sonnet-4-20250514",
56
+ usage={
57
+ "input_tokens": 1000,
58
+ "output_tokens": 500,
59
+ "total_tokens": 1500,
60
+ "input_token_details": {
61
+ "cache_read": 5000,
62
+ "cache_creation": 0,
63
+ },
64
+ },
65
+ )
66
+ ```
67
+
68
+ ### Batch Pricing
69
+
70
+ ```python
71
+ result = calculate_cost(
72
+ provider="anthropic",
73
+ model="claude-sonnet-4-20250514",
74
+ usage={"input_tokens": 1000, "output_tokens": 500, "total_tokens": 1500},
75
+ batch=True, # 50% discount
76
+ )
77
+ ```
78
+
79
+ ### Get Model Pricing Info
80
+
81
+ ```python
82
+ pricing = get_model_pricing("anthropic", "claude-sonnet-4-20250514")
83
+ print(f"Input: ${pricing['input']}/MTok") # Input: $3.0/MTok
84
+ print(f"Output: ${pricing['output']}/MTok") # Output: $15.0/MTok
85
+ ```
86
+
87
+ ### List Available Models
88
+
89
+ ```python
90
+ from llm_costs.calculator import list_models, list_providers
91
+
92
+ providers = list_providers() # ['anthropic', 'openai', 'google']
93
+ models = list_models("anthropic") # ['claude-opus-4-5-20251101', ...]
94
+ ```
95
+
96
+ ## Supported Providers
97
+
98
+ ### Anthropic
99
+
100
+ | Model | Input | Output | Cache Read |
101
+ |-------|-------|--------|------------|
102
+ | claude-opus-4-5-20251101 | $5.00 | $25.00 | $0.50 |
103
+ | claude-opus-4-1-20250414 | $15.00 | $75.00 | $1.50 |
104
+ | claude-opus-4-20250514 | $15.00 | $75.00 | $1.50 |
105
+ | claude-sonnet-4-5-20250514 | $3.00 | $15.00 | $0.30 |
106
+ | claude-sonnet-4-20250514 | $3.00 | $15.00 | $0.30 |
107
+ | claude-3-7-sonnet-20250219 | $3.00 | $15.00 | $0.30 |
108
+ | claude-haiku-4-5-20250514 | $1.00 | $5.00 | $0.10 |
109
+ | claude-3-5-haiku-20241022 | $0.80 | $4.00 | $0.08 |
110
+ | claude-3-opus-20240229 | $15.00 | $75.00 | $1.50 |
111
+ | claude-3-haiku-20240307 | $0.25 | $1.25 | $0.03 |
112
+
113
+ Prices per million tokens. Long context pricing (>200K tokens) applies to Sonnet models.
114
+
115
+ ### OpenAI
116
+
117
+ | Model | Input | Output | Cache Read |
118
+ |-------|-------|--------|------------|
119
+ | gpt-5.2 | $1.75 | $14.00 | $0.175 |
120
+ | gpt-5.1 | $1.25 | $10.00 | $0.125 |
121
+ | gpt-5 | $1.25 | $10.00 | $0.125 |
122
+ | gpt-5-mini | $0.25 | $2.00 | $0.025 |
123
+ | gpt-5-nano | $0.05 | $0.40 | $0.005 |
124
+ | gpt-5.2-pro | $21.00 | $168.00 | - |
125
+ | gpt-5-pro | $15.00 | $120.00 | - |
126
+ | gpt-4.1 | $2.00 | $8.00 | $0.50 |
127
+ | gpt-4.1-mini | $0.40 | $1.60 | $0.10 |
128
+ | gpt-4.1-nano | $0.10 | $0.40 | $0.025 |
129
+ | gpt-4o | $2.50 | $10.00 | $1.25 |
130
+ | gpt-4o-mini | $0.15 | $0.60 | $0.075 |
131
+ | o1 | $15.00 | $60.00 | $7.50 |
132
+ | o1-pro | $150.00 | $600.00 | - |
133
+ | o1-mini | $1.10 | $4.40 | $0.55 |
134
+ | o3 | $2.00 | $8.00 | $0.50 |
135
+ | o3-pro | $20.00 | $80.00 | - |
136
+ | o3-mini | $1.10 | $4.40 | $0.55 |
137
+ | o3-deep-research | $10.00 | $40.00 | $2.50 |
138
+ | o4-mini | $1.10 | $4.40 | $0.275 |
139
+ | o4-mini-deep-research | $2.00 | $8.00 | $0.50 |
140
+ | computer-use-preview | $3.00 | $12.00 | - |
141
+
142
+ Prices per million tokens (Standard tier).
143
+
144
+ ### Google
145
+
146
+ | Model | Input | Output | Cache Read |
147
+ |-------|-------|--------|------------|
148
+ | gemini-3-pro-preview | $2.00 | $12.00 | $0.20 |
149
+ | gemini-2.5-pro | $1.25 | $10.00 | $0.125 |
150
+ | gemini-2.5-flash | $0.30 | $2.50 | $0.03 |
151
+ | gemini-2.5-flash-lite | $0.10 | $0.40 | $0.01 |
152
+ | gemini-2.0-flash | $0.10 | $0.40 | $0.025 |
153
+ | gemini-2.0-flash-lite | $0.075 | $0.30 | - |
154
+
155
+ Prices per million tokens (Paid tier). Long context pricing (>200K tokens) applies to Pro models.
156
+
157
+ ## Usage Schema
158
+
159
+ The library accepts token usage in LangChain's `UsageMetadata` format:
160
+
161
+ ```python
162
+ {
163
+ "input_tokens": int,
164
+ "output_tokens": int,
165
+ "total_tokens": int,
166
+ "input_token_details": {
167
+ "cache_read": int, # Cached tokens read
168
+ "cache_creation": int, # Tokens written to cache
169
+ },
170
+ "output_token_details": {
171
+ "reasoning": int, # Reasoning tokens (o1/thinking models)
172
+ },
173
+ }
174
+ ```
175
+
176
+ ## Return Value
177
+
178
+ `calculate_cost()` returns a `CostResult`:
179
+
180
+ ```python
181
+ {
182
+ "cost": 0.0105, # Total cost in USD
183
+ "currency": "USD",
184
+ "breakdown": {
185
+ "input_cost": 0.003,
186
+ "output_cost": 0.0075,
187
+ "cache_read_cost": 0.0, # If applicable
188
+ "cache_creation_cost": 0.0, # If applicable
189
+ },
190
+ "pricing_used": {
191
+ "input_per_mtok": 3.0,
192
+ "output_per_mtok": 15.0,
193
+ "batch_applied": False,
194
+ "long_context_applied": False,
195
+ },
196
+ }
197
+ ```
198
+
199
+ ## License
200
+
201
+ MIT
@@ -0,0 +1,189 @@
1
+ # llm-costs
2
+
3
+ LLM cost calculator for major providers. Calculates API costs from token usage data.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ pip install llm-costs
9
+ ```
10
+
11
+ Or with uv:
12
+
13
+ ```bash
14
+ uv add llm-costs
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ ```python
20
+ from llm_costs import calculate_cost, get_model_pricing
21
+
22
+ # Calculate cost from LangChain UsageMetadata structure
23
+ result = calculate_cost(
24
+ provider="anthropic",
25
+ model="claude-sonnet-4-20250514",
26
+ usage={
27
+ "input_tokens": 1000,
28
+ "output_tokens": 500,
29
+ "total_tokens": 1500,
30
+ },
31
+ )
32
+
33
+ print(f"Cost: ${result['cost']:.6f}") # Cost: $0.010500
34
+ print(f"Input: ${result['breakdown']['input_cost']:.6f}")
35
+ print(f"Output: ${result['breakdown']['output_cost']:.6f}")
36
+ ```
37
+
38
+ ### With Prompt Caching
39
+
40
+ ```python
41
+ result = calculate_cost(
42
+ provider="anthropic",
43
+ model="claude-sonnet-4-20250514",
44
+ usage={
45
+ "input_tokens": 1000,
46
+ "output_tokens": 500,
47
+ "total_tokens": 1500,
48
+ "input_token_details": {
49
+ "cache_read": 5000,
50
+ "cache_creation": 0,
51
+ },
52
+ },
53
+ )
54
+ ```
55
+
56
+ ### Batch Pricing
57
+
58
+ ```python
59
+ result = calculate_cost(
60
+ provider="anthropic",
61
+ model="claude-sonnet-4-20250514",
62
+ usage={"input_tokens": 1000, "output_tokens": 500, "total_tokens": 1500},
63
+ batch=True, # 50% discount
64
+ )
65
+ ```
66
+
67
+ ### Get Model Pricing Info
68
+
69
+ ```python
70
+ pricing = get_model_pricing("anthropic", "claude-sonnet-4-20250514")
71
+ print(f"Input: ${pricing['input']}/MTok") # Input: $3.0/MTok
72
+ print(f"Output: ${pricing['output']}/MTok") # Output: $15.0/MTok
73
+ ```
74
+
75
+ ### List Available Models
76
+
77
+ ```python
78
+ from llm_costs.calculator import list_models, list_providers
79
+
80
+ providers = list_providers() # ['anthropic', 'openai', 'google']
81
+ models = list_models("anthropic") # ['claude-opus-4-5-20251101', ...]
82
+ ```
83
+
84
+ ## Supported Providers
85
+
86
+ ### Anthropic
87
+
88
+ | Model | Input | Output | Cache Read |
89
+ |-------|-------|--------|------------|
90
+ | claude-opus-4-5-20251101 | $5.00 | $25.00 | $0.50 |
91
+ | claude-opus-4-1-20250414 | $15.00 | $75.00 | $1.50 |
92
+ | claude-opus-4-20250514 | $15.00 | $75.00 | $1.50 |
93
+ | claude-sonnet-4-5-20250514 | $3.00 | $15.00 | $0.30 |
94
+ | claude-sonnet-4-20250514 | $3.00 | $15.00 | $0.30 |
95
+ | claude-3-7-sonnet-20250219 | $3.00 | $15.00 | $0.30 |
96
+ | claude-haiku-4-5-20250514 | $1.00 | $5.00 | $0.10 |
97
+ | claude-3-5-haiku-20241022 | $0.80 | $4.00 | $0.08 |
98
+ | claude-3-opus-20240229 | $15.00 | $75.00 | $1.50 |
99
+ | claude-3-haiku-20240307 | $0.25 | $1.25 | $0.03 |
100
+
101
+ Prices per million tokens. Long context pricing (>200K tokens) applies to Sonnet models.
102
+
103
+ ### OpenAI
104
+
105
+ | Model | Input | Output | Cache Read |
106
+ |-------|-------|--------|------------|
107
+ | gpt-5.2 | $1.75 | $14.00 | $0.175 |
108
+ | gpt-5.1 | $1.25 | $10.00 | $0.125 |
109
+ | gpt-5 | $1.25 | $10.00 | $0.125 |
110
+ | gpt-5-mini | $0.25 | $2.00 | $0.025 |
111
+ | gpt-5-nano | $0.05 | $0.40 | $0.005 |
112
+ | gpt-5.2-pro | $21.00 | $168.00 | - |
113
+ | gpt-5-pro | $15.00 | $120.00 | - |
114
+ | gpt-4.1 | $2.00 | $8.00 | $0.50 |
115
+ | gpt-4.1-mini | $0.40 | $1.60 | $0.10 |
116
+ | gpt-4.1-nano | $0.10 | $0.40 | $0.025 |
117
+ | gpt-4o | $2.50 | $10.00 | $1.25 |
118
+ | gpt-4o-mini | $0.15 | $0.60 | $0.075 |
119
+ | o1 | $15.00 | $60.00 | $7.50 |
120
+ | o1-pro | $150.00 | $600.00 | - |
121
+ | o1-mini | $1.10 | $4.40 | $0.55 |
122
+ | o3 | $2.00 | $8.00 | $0.50 |
123
+ | o3-pro | $20.00 | $80.00 | - |
124
+ | o3-mini | $1.10 | $4.40 | $0.55 |
125
+ | o3-deep-research | $10.00 | $40.00 | $2.50 |
126
+ | o4-mini | $1.10 | $4.40 | $0.275 |
127
+ | o4-mini-deep-research | $2.00 | $8.00 | $0.50 |
128
+ | computer-use-preview | $3.00 | $12.00 | - |
129
+
130
+ Prices per million tokens (Standard tier).
131
+
132
+ ### Google
133
+
134
+ | Model | Input | Output | Cache Read |
135
+ |-------|-------|--------|------------|
136
+ | gemini-3-pro-preview | $2.00 | $12.00 | $0.20 |
137
+ | gemini-2.5-pro | $1.25 | $10.00 | $0.125 |
138
+ | gemini-2.5-flash | $0.30 | $2.50 | $0.03 |
139
+ | gemini-2.5-flash-lite | $0.10 | $0.40 | $0.01 |
140
+ | gemini-2.0-flash | $0.10 | $0.40 | $0.025 |
141
+ | gemini-2.0-flash-lite | $0.075 | $0.30 | - |
142
+
143
+ Prices per million tokens (Paid tier). Long context pricing (>200K tokens) applies to Pro models.
144
+
145
+ ## Usage Schema
146
+
147
+ The library accepts token usage in LangChain's `UsageMetadata` format:
148
+
149
+ ```python
150
+ {
151
+ "input_tokens": int,
152
+ "output_tokens": int,
153
+ "total_tokens": int,
154
+ "input_token_details": {
155
+ "cache_read": int, # Cached tokens read
156
+ "cache_creation": int, # Tokens written to cache
157
+ },
158
+ "output_token_details": {
159
+ "reasoning": int, # Reasoning tokens (o1/thinking models)
160
+ },
161
+ }
162
+ ```
163
+
164
+ ## Return Value
165
+
166
+ `calculate_cost()` returns a `CostResult`:
167
+
168
+ ```python
169
+ {
170
+ "cost": 0.0105, # Total cost in USD
171
+ "currency": "USD",
172
+ "breakdown": {
173
+ "input_cost": 0.003,
174
+ "output_cost": 0.0075,
175
+ "cache_read_cost": 0.0, # If applicable
176
+ "cache_creation_cost": 0.0, # If applicable
177
+ },
178
+ "pricing_used": {
179
+ "input_per_mtok": 3.0,
180
+ "output_per_mtok": 15.0,
181
+ "batch_applied": False,
182
+ "long_context_applied": False,
183
+ },
184
+ }
185
+ ```
186
+
187
+ ## License
188
+
189
+ MIT
@@ -0,0 +1,32 @@
1
+ [project]
2
+ name = "llm-costs"
3
+ version = "0.1.0"
4
+ description = "LLM cost calculator for major providers"
5
+ authors = [
6
+ { name="jason", email="jtan92@gmail.com" },
7
+ ]
8
+ readme = "README.md"
9
+ requires-python = ">=3.13"
10
+ dependencies = [
11
+ "pyyaml>=6.0",
12
+ ]
13
+
14
+ [project.optional-dependencies]
15
+ dev = [
16
+ "pytest>=8.0",
17
+ "ruff>=0.8",
18
+ ]
19
+
20
+ [build-system]
21
+ requires = ["hatchling"]
22
+ build-backend = "hatchling.build"
23
+
24
+ [tool.hatch.build.targets.wheel]
25
+ packages = ["src/llm_costs"]
26
+
27
+ [tool.ruff]
28
+ target-version = "py313"
29
+ line-length = 100
30
+
31
+ [tool.ruff.lint]
32
+ select = ["E", "F", "I", "UP"]
@@ -0,0 +1,5 @@
1
+ """LLM cost calculator for major providers."""
2
+
3
+ from llm_costs.calculator import calculate_cost, get_model_pricing
4
+
5
+ __all__ = ["calculate_cost", "get_model_pricing"]
@@ -0,0 +1,176 @@
1
+ """Main cost calculation API."""
2
+
3
+ from pathlib import Path
4
+
5
+ import yaml
6
+
7
+ from llm_costs.models import CostResult
8
+ from llm_costs.providers.anthropic import calculate_anthropic_cost
9
+ from llm_costs.providers.google import calculate_google_cost
10
+ from llm_costs.providers.openai import calculate_openai_cost
11
+
12
+ # Cache for loaded pricing configs
13
+ _pricing_cache: dict[str, dict] = {}
14
+
15
+ # Provider to calculator function mapping
16
+ _PROVIDER_CALCULATORS = {
17
+ "anthropic": calculate_anthropic_cost,
18
+ "openai": calculate_openai_cost,
19
+ "google": calculate_google_cost,
20
+ }
21
+
22
+
23
+ def _get_pricing_dir() -> Path:
24
+ """Get the directory containing pricing YAML files."""
25
+ return Path(__file__).parent / "pricing"
26
+
27
+
28
+ def _load_pricing(provider: str) -> dict:
29
+ """Load pricing config for a provider, with caching."""
30
+ if provider in _pricing_cache:
31
+ return _pricing_cache[provider]
32
+
33
+ pricing_file = _get_pricing_dir() / f"{provider}.yaml"
34
+ if not pricing_file.exists():
35
+ raise ValueError(f"No pricing data for provider: {provider}")
36
+
37
+ with open(pricing_file) as f:
38
+ pricing = yaml.safe_load(f)
39
+
40
+ _pricing_cache[provider] = pricing
41
+ return pricing
42
+
43
+
44
+ def calculate_cost(
45
+ provider: str,
46
+ model: str,
47
+ usage: dict,
48
+ batch: bool = False,
49
+ long_context: bool | None = None,
50
+ ) -> CostResult:
51
+ """
52
+ Calculate cost for an LLM API call.
53
+
54
+ Args:
55
+ provider: Provider name ("anthropic", "openai", "google")
56
+ model: Model name (e.g., "claude-sonnet-4-20250514", "gpt-4o")
57
+ usage: Token usage in LangChain UsageMetadata format:
58
+ {
59
+ "input_tokens": int,
60
+ "output_tokens": int,
61
+ "total_tokens": int,
62
+ "input_token_details": {
63
+ "cache_read": int, # Cached input tokens read
64
+ "cache_creation": int, # Tokens written to cache
65
+ },
66
+ "output_token_details": {
67
+ "reasoning": int, # Reasoning tokens (o1/thinking models)
68
+ },
69
+ }
70
+ batch: Whether batch API pricing applies (typically 50% discount)
71
+ long_context: Force long context pricing. Auto-detected if None based on
72
+ total input tokens exceeding provider threshold (typically 200K).
73
+
74
+ Returns:
75
+ CostResult with total cost, breakdown, and pricing info used.
76
+
77
+ Raises:
78
+ ValueError: If provider or model is unknown.
79
+
80
+ Example:
81
+ >>> result = calculate_cost(
82
+ ... provider="anthropic",
83
+ ... model="claude-sonnet-4-20250514",
84
+ ... usage={
85
+ ... "input_tokens": 1000,
86
+ ... "output_tokens": 500,
87
+ ... "total_tokens": 1500,
88
+ ... },
89
+ ... )
90
+ >>> print(f"Cost: ${result['cost']:.6f}")
91
+ Cost: $0.010500
92
+ """
93
+ provider = provider.lower()
94
+
95
+ if provider not in _PROVIDER_CALCULATORS:
96
+ raise ValueError(f"Unknown provider: {provider}. Supported: {list(_PROVIDER_CALCULATORS)}")
97
+
98
+ pricing = _load_pricing(provider)
99
+ calculator = _PROVIDER_CALCULATORS[provider]
100
+
101
+ # Build kwargs - not all providers support all options
102
+ kwargs: dict = {
103
+ "model": model,
104
+ "usage": usage,
105
+ "pricing": pricing,
106
+ "batch": batch,
107
+ }
108
+
109
+ # Only pass long_context to providers that support it
110
+ if provider in ("anthropic", "google"):
111
+ kwargs["long_context"] = long_context
112
+
113
+ return calculator(**kwargs)
114
+
115
+
116
+ def get_model_pricing(provider: str, model: str) -> dict | None:
117
+ """
118
+ Get pricing info for a specific model.
119
+
120
+ Args:
121
+ provider: Provider name ("anthropic", "openai", "google")
122
+ model: Model name or alias
123
+
124
+ Returns:
125
+ Model pricing config dict, or None if not found.
126
+
127
+ Example:
128
+ >>> pricing = get_model_pricing("anthropic", "claude-sonnet-4-20250514")
129
+ >>> print(f"Input: ${pricing['input']}/MTok")
130
+ Input: $3.0/MTok
131
+ """
132
+ provider = provider.lower()
133
+
134
+ try:
135
+ pricing = _load_pricing(provider)
136
+ except ValueError:
137
+ return None
138
+
139
+ models = pricing.get("models", {})
140
+
141
+ # Direct match
142
+ if model in models:
143
+ return models[model]
144
+
145
+ # Check aliases
146
+ for model_config in models.values():
147
+ aliases = model_config.get("aliases", [])
148
+ if model in aliases:
149
+ return model_config
150
+
151
+ return None
152
+
153
+
154
+ def list_models(provider: str) -> list[str]:
155
+ """
156
+ List all known models for a provider.
157
+
158
+ Args:
159
+ provider: Provider name
160
+
161
+ Returns:
162
+ List of model names (primary names, not aliases)
163
+ """
164
+ provider = provider.lower()
165
+
166
+ try:
167
+ pricing = _load_pricing(provider)
168
+ except ValueError:
169
+ return []
170
+
171
+ return list(pricing.get("models", {}).keys())
172
+
173
+
174
+ def list_providers() -> list[str]:
175
+ """List all supported providers."""
176
+ return list(_PROVIDER_CALCULATORS.keys())
@@ -0,0 +1,22 @@
1
+ """Type definitions for LLM cost calculation."""
2
+
3
+ from typing import NotRequired, TypedDict
4
+
5
+
6
+ class CostBreakdown(TypedDict):
7
+ """Itemized cost breakdown."""
8
+
9
+ input_cost: float
10
+ output_cost: float
11
+ cache_read_cost: NotRequired[float]
12
+ cache_creation_cost: NotRequired[float]
13
+ reasoning_cost: NotRequired[float]
14
+
15
+
16
+ class CostResult(TypedDict):
17
+ """Result from calculate_cost()."""
18
+
19
+ cost: float
20
+ currency: str
21
+ breakdown: CostBreakdown
22
+ pricing_used: dict[str, float | bool]