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.
- llm_costs-0.1.0/.gitignore +16 -0
- llm_costs-0.1.0/.python-version +1 -0
- llm_costs-0.1.0/PKG-INFO +201 -0
- llm_costs-0.1.0/README.md +189 -0
- llm_costs-0.1.0/pyproject.toml +32 -0
- llm_costs-0.1.0/src/llm_costs/__init__.py +5 -0
- llm_costs-0.1.0/src/llm_costs/calculator.py +176 -0
- llm_costs-0.1.0/src/llm_costs/models.py +22 -0
- llm_costs-0.1.0/src/llm_costs/pricing/anthropic.yaml +106 -0
- llm_costs-0.1.0/src/llm_costs/pricing/google.yaml +65 -0
- llm_costs-0.1.0/src/llm_costs/pricing/openai.yaml +168 -0
- llm_costs-0.1.0/src/llm_costs/providers/__init__.py +7 -0
- llm_costs-0.1.0/src/llm_costs/providers/anthropic.py +111 -0
- llm_costs-0.1.0/src/llm_costs/providers/google.py +104 -0
- llm_costs-0.1.0/src/llm_costs/providers/openai.py +93 -0
- llm_costs-0.1.0/tests/__init__.py +1 -0
- llm_costs-0.1.0/tests/test_calculator.py +272 -0
- llm_costs-0.1.0/uv.lock +148 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.13.0
|
llm_costs-0.1.0/PKG-INFO
ADDED
|
@@ -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,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]
|