metergraph-core 0.6.1__tar.gz → 0.7.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.
- {metergraph_core-0.6.1/src/metergraph_core.egg-info → metergraph_core-0.7.0}/PKG-INFO +48 -1
- {metergraph_core-0.6.1 → metergraph_core-0.7.0}/README.md +47 -0
- {metergraph_core-0.6.1 → metergraph_core-0.7.0}/pyproject.toml +1 -1
- {metergraph_core-0.6.1 → metergraph_core-0.7.0}/src/metergraph_core/__init__.py +2 -0
- {metergraph_core-0.6.1 → metergraph_core-0.7.0}/src/metergraph_core/catalog.py +3 -1
- {metergraph_core-0.6.1 → metergraph_core-0.7.0}/src/metergraph_core/data/prices.yaml +71 -8
- {metergraph_core-0.6.1 → metergraph_core-0.7.0}/src/metergraph_core/extractors.py +2 -1
- metergraph_core-0.7.0/src/metergraph_core/responses.py +325 -0
- {metergraph_core-0.6.1 → metergraph_core-0.7.0}/src/metergraph_core/retrieval.py +2 -2
- {metergraph_core-0.6.1 → metergraph_core-0.7.0/src/metergraph_core.egg-info}/PKG-INFO +48 -1
- {metergraph_core-0.6.1 → metergraph_core-0.7.0}/src/metergraph_core.egg-info/SOURCES.txt +1 -0
- {metergraph_core-0.6.1 → metergraph_core-0.7.0}/MANIFEST.in +0 -0
- {metergraph_core-0.6.1 → metergraph_core-0.7.0}/setup.cfg +0 -0
- {metergraph_core-0.6.1 → metergraph_core-0.7.0}/src/metergraph_core/billing.py +0 -0
- {metergraph_core-0.6.1 → metergraph_core-0.7.0}/src/metergraph_core/catalog_delta.py +0 -0
- {metergraph_core-0.6.1 → metergraph_core-0.7.0}/src/metergraph_core/data/models.yaml +0 -0
- {metergraph_core-0.6.1 → metergraph_core-0.7.0}/src/metergraph_core/data/sources.yaml +0 -0
- {metergraph_core-0.6.1 → metergraph_core-0.7.0}/src/metergraph_core/loader.py +0 -0
- {metergraph_core-0.6.1 → metergraph_core-0.7.0}/src/metergraph_core/models.py +0 -0
- {metergraph_core-0.6.1 → metergraph_core-0.7.0}/src/metergraph_core/refresh.py +0 -0
- {metergraph_core-0.6.1 → metergraph_core-0.7.0}/src/metergraph_core/sources.py +0 -0
- {metergraph_core-0.6.1 → metergraph_core-0.7.0}/src/metergraph_core.egg-info/dependency_links.txt +0 -0
- {metergraph_core-0.6.1 → metergraph_core-0.7.0}/src/metergraph_core.egg-info/requires.txt +0 -0
- {metergraph_core-0.6.1 → metergraph_core-0.7.0}/src/metergraph_core.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: metergraph-core
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: Open, effective-dated LLM pricing catalog and deterministic cost calculator for popular models across major providers and gateways, including OpenAI, Anthropic, Google Gemini, AWS Bedrock, OpenRouter, Vercel AI Gateway, and more.
|
|
5
5
|
Author: Metergraph
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -200,6 +200,52 @@ A call after it ended resolves to that price, stays `priced`, and carries
|
|
|
200
200
|
applied `price.effective_to` says when it ended. A closed price followed by a
|
|
201
201
|
later one never extends, so a gap before a scheduled price stays `unpriced`.
|
|
202
202
|
|
|
203
|
+
### Costing a provider response
|
|
204
|
+
|
|
205
|
+
`cost_from_response` reads the usage out of a response you already have and
|
|
206
|
+
costs it, so you do not extract token counts yourself:
|
|
207
|
+
|
|
208
|
+
```python
|
|
209
|
+
from openai import OpenAI
|
|
210
|
+
from metergraph_core import cost_from_response
|
|
211
|
+
|
|
212
|
+
response = OpenAI().chat.completions.create(model="gpt-5.4-mini", messages=messages)
|
|
213
|
+
result = cost_from_response("openai", response)
|
|
214
|
+
print(result.status, result.model_id, result.cost, result.currency)
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
It accepts the object a provider SDK returned or the same response as JSON:
|
|
218
|
+
|
|
219
|
+
| Provider API | Example `provider` |
|
|
220
|
+
|---|---|
|
|
221
|
+
| OpenAI Chat Completions, and gateways that return its shape | `openai`, `openrouter`, `deepseek` |
|
|
222
|
+
| OpenAI Responses | `openai` |
|
|
223
|
+
| Anthropic Messages | `anthropic` |
|
|
224
|
+
| Google Gemini `generateContent` | `google` |
|
|
225
|
+
| Bedrock Converse (pass `model=`, the response does not name one) | `bedrock` |
|
|
226
|
+
|
|
227
|
+
`provider` names who served the call and selects its direct channel unless
|
|
228
|
+
`channel` is given. The response shape is detected from its fields. Only the
|
|
229
|
+
model, usage counts, service tier and creation time are read; prompt and
|
|
230
|
+
output content never are.
|
|
231
|
+
|
|
232
|
+
Cached tokens are billed once whichever convention the response uses. OpenAI
|
|
233
|
+
and Gemini count cached tokens inside the input total, so the total is
|
|
234
|
+
restated in the convention of the price row that bills it, including a
|
|
235
|
+
gateway row for another publisher's model. Gemini thinking tokens bill as
|
|
236
|
+
output.
|
|
237
|
+
|
|
238
|
+
The price in effect when the response was created applies, from `created`,
|
|
239
|
+
`created_at` or `createTime`; `at=` overrides it, and a response with no
|
|
240
|
+
creation time is priced now. `catalog=` costs against a catalog you loaded;
|
|
241
|
+
the default is the bundled one.
|
|
242
|
+
|
|
243
|
+
A batch service tier bills at batch rates. Any other tier besides standard,
|
|
244
|
+
such as `priority` or `flex`, returns `partial` with `service_tier` in
|
|
245
|
+
`missing_dimensions`, since the catalog carries no tier rates. Cost a streamed
|
|
246
|
+
call from its final response or final usage chunk. Fees outside token usage,
|
|
247
|
+
such as web search, are not included.
|
|
248
|
+
|
|
203
249
|
## Model registry
|
|
204
250
|
|
|
205
251
|
```python
|
|
@@ -466,6 +512,7 @@ from metergraph_core import (
|
|
|
466
512
|
ResolvedPrice,
|
|
467
513
|
SourceRegistry,
|
|
468
514
|
apply_catalog_delta,
|
|
515
|
+
cost_from_response,
|
|
469
516
|
discovery_evidence,
|
|
470
517
|
extract,
|
|
471
518
|
load_catalog,
|
|
@@ -170,6 +170,52 @@ A call after it ended resolves to that price, stays `priced`, and carries
|
|
|
170
170
|
applied `price.effective_to` says when it ended. A closed price followed by a
|
|
171
171
|
later one never extends, so a gap before a scheduled price stays `unpriced`.
|
|
172
172
|
|
|
173
|
+
### Costing a provider response
|
|
174
|
+
|
|
175
|
+
`cost_from_response` reads the usage out of a response you already have and
|
|
176
|
+
costs it, so you do not extract token counts yourself:
|
|
177
|
+
|
|
178
|
+
```python
|
|
179
|
+
from openai import OpenAI
|
|
180
|
+
from metergraph_core import cost_from_response
|
|
181
|
+
|
|
182
|
+
response = OpenAI().chat.completions.create(model="gpt-5.4-mini", messages=messages)
|
|
183
|
+
result = cost_from_response("openai", response)
|
|
184
|
+
print(result.status, result.model_id, result.cost, result.currency)
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
It accepts the object a provider SDK returned or the same response as JSON:
|
|
188
|
+
|
|
189
|
+
| Provider API | Example `provider` |
|
|
190
|
+
|---|---|
|
|
191
|
+
| OpenAI Chat Completions, and gateways that return its shape | `openai`, `openrouter`, `deepseek` |
|
|
192
|
+
| OpenAI Responses | `openai` |
|
|
193
|
+
| Anthropic Messages | `anthropic` |
|
|
194
|
+
| Google Gemini `generateContent` | `google` |
|
|
195
|
+
| Bedrock Converse (pass `model=`, the response does not name one) | `bedrock` |
|
|
196
|
+
|
|
197
|
+
`provider` names who served the call and selects its direct channel unless
|
|
198
|
+
`channel` is given. The response shape is detected from its fields. Only the
|
|
199
|
+
model, usage counts, service tier and creation time are read; prompt and
|
|
200
|
+
output content never are.
|
|
201
|
+
|
|
202
|
+
Cached tokens are billed once whichever convention the response uses. OpenAI
|
|
203
|
+
and Gemini count cached tokens inside the input total, so the total is
|
|
204
|
+
restated in the convention of the price row that bills it, including a
|
|
205
|
+
gateway row for another publisher's model. Gemini thinking tokens bill as
|
|
206
|
+
output.
|
|
207
|
+
|
|
208
|
+
The price in effect when the response was created applies, from `created`,
|
|
209
|
+
`created_at` or `createTime`; `at=` overrides it, and a response with no
|
|
210
|
+
creation time is priced now. `catalog=` costs against a catalog you loaded;
|
|
211
|
+
the default is the bundled one.
|
|
212
|
+
|
|
213
|
+
A batch service tier bills at batch rates. Any other tier besides standard,
|
|
214
|
+
such as `priority` or `flex`, returns `partial` with `service_tier` in
|
|
215
|
+
`missing_dimensions`, since the catalog carries no tier rates. Cost a streamed
|
|
216
|
+
call from its final response or final usage chunk. Fees outside token usage,
|
|
217
|
+
such as web search, are not included.
|
|
218
|
+
|
|
173
219
|
## Model registry
|
|
174
220
|
|
|
175
221
|
```python
|
|
@@ -436,6 +482,7 @@ from metergraph_core import (
|
|
|
436
482
|
ResolvedPrice,
|
|
437
483
|
SourceRegistry,
|
|
438
484
|
apply_catalog_delta,
|
|
485
|
+
cost_from_response,
|
|
439
486
|
discovery_evidence,
|
|
440
487
|
extract,
|
|
441
488
|
load_catalog,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "metergraph-core"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.7.0"
|
|
4
4
|
description = "Open, effective-dated LLM pricing catalog and deterministic cost calculator for popular models across major providers and gateways, including OpenAI, Anthropic, Google Gemini, AWS Bedrock, OpenRouter, Vercel AI Gateway, and more."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "Apache-2.0"
|
|
@@ -59,6 +59,7 @@ from .catalog_delta import (
|
|
|
59
59
|
apply_catalog_delta,
|
|
60
60
|
parse_catalog_delta,
|
|
61
61
|
)
|
|
62
|
+
from .responses import cost_from_response
|
|
62
63
|
from .extractors import (
|
|
63
64
|
ExtractedRate,
|
|
64
65
|
Extraction,
|
|
@@ -133,6 +134,7 @@ __all__ = [
|
|
|
133
134
|
"RetrievalPrice",
|
|
134
135
|
"count_fetch_units",
|
|
135
136
|
"count_search_units",
|
|
137
|
+
"cost_from_response",
|
|
136
138
|
"counts_cache_read_in_input",
|
|
137
139
|
"direct_channel_for_provider",
|
|
138
140
|
"normalize_provider",
|
|
@@ -522,7 +522,9 @@ def _price_tokens(
|
|
|
522
522
|
prompt_count += cache_read_count
|
|
523
523
|
if not rules.get("input_includes_cache_write"):
|
|
524
524
|
prompt_count += cache_write_count
|
|
525
|
-
|
|
525
|
+
# Some providers publish no long-context batch rate, so batch stays flat.
|
|
526
|
+
applies = not batch or long_context.get("applies_to_batch", True)
|
|
527
|
+
if applies and threshold is not None and prompt_count > threshold:
|
|
526
528
|
input_multiplier = _decimal(long_context.get("input_multiplier")) or Decimal("1")
|
|
527
529
|
output_multiplier = _decimal(
|
|
528
530
|
long_context.get("output_multiplier")
|
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
#
|
|
7
7
|
# Community updates welcome: add an alias or price entry with its provider
|
|
8
8
|
# source_url and open a PR. CI validates structure and date overlaps.
|
|
9
|
-
version: "2026-10-
|
|
9
|
+
version: "2026-10-23"
|
|
10
10
|
currency: USD
|
|
11
|
-
pricing_verified_at: "2026-10-
|
|
11
|
+
pricing_verified_at: "2026-10-23"
|
|
12
12
|
models:
|
|
13
13
|
- canonical_id: openai/gpt-6-sol
|
|
14
14
|
publisher: openai
|
|
@@ -1824,9 +1824,12 @@ models:
|
|
|
1824
1824
|
- {provider: google, alias: gemini-2.5-flash-image, channel: google-api}
|
|
1825
1825
|
- {provider: google, alias: models/gemini-2.5-flash-image, channel: google-api}
|
|
1826
1826
|
prices:
|
|
1827
|
+
# Google shut this model down on 2026-10-02 and now states its output
|
|
1828
|
+
# price per image, which no token rate can express.
|
|
1827
1829
|
- channel: google-api
|
|
1828
1830
|
region: global
|
|
1829
1831
|
effective_from: "2025-10-02"
|
|
1832
|
+
effective_to: "2026-10-02"
|
|
1830
1833
|
input_per_mtok: 0.30
|
|
1831
1834
|
output_per_mtok: 30.00
|
|
1832
1835
|
source_url: https://ai.google.dev/gemini-api/docs/pricing
|
|
@@ -2497,6 +2500,7 @@ models:
|
|
|
2497
2500
|
- channel: perplexity-api
|
|
2498
2501
|
region: global
|
|
2499
2502
|
effective_from: "2025-04-18"
|
|
2503
|
+
effective_to: "2026-10-23"
|
|
2500
2504
|
input_per_mtok: 1.00
|
|
2501
2505
|
output_per_mtok: 1.00
|
|
2502
2506
|
rules:
|
|
@@ -2729,6 +2733,9 @@ models:
|
|
|
2729
2733
|
cache_read_per_mtok: 0.075
|
|
2730
2734
|
source_url: https://ai-gateway.vercel.sh/v1/models
|
|
2731
2735
|
|
|
2736
|
+
# The pricing page stopped listing this name when Google deprecated it on
|
|
2737
|
+
# 2026-10-08 and began routing it to gemini-3.8-flash, whose rates these
|
|
2738
|
+
# rows already match.
|
|
2732
2739
|
- channel: google-api
|
|
2733
2740
|
region: global
|
|
2734
2741
|
effective_from: "2026-08-13"
|
|
@@ -3654,6 +3661,15 @@ models:
|
|
|
3654
3661
|
rules:
|
|
3655
3662
|
long_context: {threshold: 200000, input_multiplier: 2.0, output_multiplier: 2.0}
|
|
3656
3663
|
source_url: https://docs.x.ai/developers/release-notes
|
|
3664
|
+
- channel: xai-api
|
|
3665
|
+
region: us
|
|
3666
|
+
effective_from: "2026-08-12"
|
|
3667
|
+
input_per_mtok: 2.2
|
|
3668
|
+
output_per_mtok: 6.6
|
|
3669
|
+
cache_read_per_mtok: 0.55
|
|
3670
|
+
rules:
|
|
3671
|
+
long_context: {threshold: 200000, input_multiplier: 2.0, output_multiplier: 2.0}
|
|
3672
|
+
source_url: https://docs.x.ai/developers/advanced-api-usage/regions
|
|
3657
3673
|
- canonical_id: xai/grok-4.7
|
|
3658
3674
|
publisher: xai
|
|
3659
3675
|
aliases:
|
|
@@ -4015,13 +4031,30 @@ models:
|
|
|
4015
4031
|
- {provider: google, alias: gemini-omni-flash-preview, channel: google-api}
|
|
4016
4032
|
- {provider: google, alias: models/gemini-omni-flash-preview, channel: google-api}
|
|
4017
4033
|
prices:
|
|
4034
|
+
# Google shuts this preview endpoint down on 2026-10-22; its traffic moves
|
|
4035
|
+
# to google/gemini-omni-1.1-flash.
|
|
4018
4036
|
- channel: google-api
|
|
4019
4037
|
region: global
|
|
4020
4038
|
effective_from: "2026-05-19"
|
|
4039
|
+
effective_to: "2026-10-22"
|
|
4021
4040
|
input_per_mtok: 1.50
|
|
4022
4041
|
output_per_mtok: 9.00
|
|
4023
4042
|
source_url: https://ai.google.dev/gemini-api/docs/pricing
|
|
4024
4043
|
|
|
4044
|
+
- canonical_id: google/gemini-omni-1.1-flash
|
|
4045
|
+
publisher: google
|
|
4046
|
+
aliases:
|
|
4047
|
+
- {provider: google, alias: gemini-omni-1.1-flash, channel: google-api}
|
|
4048
|
+
- {provider: google, alias: models/gemini-omni-1.1-flash, channel: google-api}
|
|
4049
|
+
prices:
|
|
4050
|
+
# Uses the video output rate: video is this model's only output modality.
|
|
4051
|
+
- channel: google-api
|
|
4052
|
+
region: global
|
|
4053
|
+
effective_from: "2026-08-27"
|
|
4054
|
+
input_per_mtok: 1.50
|
|
4055
|
+
output_per_mtok: 17.50
|
|
4056
|
+
source_url: https://ai.google.dev/gemini-api/docs/pricing
|
|
4057
|
+
|
|
4025
4058
|
# Retrieval prices: USD per 1,000 counted operations (completed searches, tool
|
|
4026
4059
|
# calls, executed grounding queries), billed on the provider's direct channel.
|
|
4027
4060
|
# These sit alongside the model-token catalog above but are priced per unit, not
|
|
@@ -4618,9 +4651,12 @@ models:
|
|
|
4618
4651
|
- {provider: litellm, alias: claude-opus-5-fast, channel: anthropic-api}
|
|
4619
4652
|
- {provider: unknown, alias: claude-opus-5-fast, channel: anthropic-api}
|
|
4620
4653
|
prices:
|
|
4654
|
+
# Anthropic's own pricing and model-lifecycle pages name no
|
|
4655
|
+
# claude-opus-5-fast, so only the gateway offering continues.
|
|
4621
4656
|
- channel: anthropic-api
|
|
4622
4657
|
region: global
|
|
4623
4658
|
effective_from: "2026-07-24"
|
|
4659
|
+
effective_to: "2026-10-22"
|
|
4624
4660
|
input_per_mtok: 10
|
|
4625
4661
|
output_per_mtok: 50
|
|
4626
4662
|
source_url: https://ai-gateway.vercel.sh/v1/models
|
|
@@ -4921,7 +4957,9 @@ models:
|
|
|
4921
4957
|
output_per_mtok: 180
|
|
4922
4958
|
batch_input_per_mtok: 15
|
|
4923
4959
|
batch_output_per_mtok: 90
|
|
4924
|
-
|
|
4960
|
+
rules:
|
|
4961
|
+
long_context: {threshold: 272000, input_multiplier: 2, output_multiplier: 1.5}
|
|
4962
|
+
source_url: https://developers.openai.com/api/docs/models/gpt-5.4-pro
|
|
4925
4963
|
- channel: vercel-ai-gateway
|
|
4926
4964
|
region: global
|
|
4927
4965
|
effective_from: "2026-10-02"
|
|
@@ -4951,7 +4989,9 @@ models:
|
|
|
4951
4989
|
cache_read_per_mtok: 0.5
|
|
4952
4990
|
batch_input_per_mtok: 2.5
|
|
4953
4991
|
batch_output_per_mtok: 15
|
|
4954
|
-
|
|
4992
|
+
rules:
|
|
4993
|
+
long_context: {threshold: 272000, input_multiplier: 2, output_multiplier: 1.5}
|
|
4994
|
+
source_url: https://developers.openai.com/api/docs/models/gpt-5.5
|
|
4955
4995
|
- channel: vercel-ai-gateway
|
|
4956
4996
|
region: global
|
|
4957
4997
|
effective_from: "2026-04-24"
|
|
@@ -4983,6 +5023,7 @@ models:
|
|
|
4983
5023
|
- {provider: unknown, alias: gpt-5.5-pro, channel: openai-api}
|
|
4984
5024
|
- {provider: unknown, alias: gpt-5.5-pro-2026-04-23, channel: openai-api}
|
|
4985
5025
|
prices:
|
|
5026
|
+
# OpenAI lists no long-context batch rate, so batch stays flat.
|
|
4986
5027
|
- channel: openai-api
|
|
4987
5028
|
region: global
|
|
4988
5029
|
effective_from: "2026-04-24"
|
|
@@ -4990,7 +5031,9 @@ models:
|
|
|
4990
5031
|
output_per_mtok: 180
|
|
4991
5032
|
batch_input_per_mtok: 15
|
|
4992
5033
|
batch_output_per_mtok: 90
|
|
4993
|
-
|
|
5034
|
+
rules:
|
|
5035
|
+
long_context: {threshold: 272000, input_multiplier: 2, output_multiplier: 1.5, applies_to_batch: false}
|
|
5036
|
+
source_url: https://developers.openai.com/api/docs/pricing
|
|
4994
5037
|
- channel: vercel-ai-gateway
|
|
4995
5038
|
region: global
|
|
4996
5039
|
effective_from: "2026-04-24"
|
|
@@ -8026,22 +8069,40 @@ models:
|
|
|
8026
8069
|
- channel: vercel-ai-gateway
|
|
8027
8070
|
region: global
|
|
8028
8071
|
effective_from: "2026-10-20"
|
|
8072
|
+
effective_to: "2026-10-21"
|
|
8029
8073
|
input_per_mtok: 0.14
|
|
8030
8074
|
output_per_mtok: 0.28
|
|
8031
8075
|
cache_read_per_mtok: 0.0028
|
|
8032
8076
|
rules:
|
|
8033
8077
|
varies_by_provider: true
|
|
8034
8078
|
source_url: https://ai-gateway.vercel.sh/v1/models
|
|
8079
|
+
- channel: vercel-ai-gateway
|
|
8080
|
+
region: global
|
|
8081
|
+
effective_from: "2026-10-21"
|
|
8082
|
+
input_per_mtok: 0.119
|
|
8083
|
+
output_per_mtok: 0.238
|
|
8084
|
+
cache_read_per_mtok: 0.0025
|
|
8085
|
+
source_url: https://ai-gateway.vercel.sh/v1/models
|
|
8035
8086
|
|
|
8036
8087
|
- channel: openrouter-api
|
|
8037
8088
|
region: global
|
|
8038
8089
|
effective_from: "2026-10-20"
|
|
8090
|
+
effective_to: "2026-10-21"
|
|
8039
8091
|
input_per_mtok: 0.14
|
|
8040
8092
|
output_per_mtok: 0.28
|
|
8041
8093
|
cache_read_per_mtok: 0.0028
|
|
8042
8094
|
rules:
|
|
8043
8095
|
varies_by_provider: true
|
|
8044
8096
|
source_url: https://openrouter.ai/api/v1/models
|
|
8097
|
+
- channel: openrouter-api
|
|
8098
|
+
region: global
|
|
8099
|
+
effective_from: "2026-10-21"
|
|
8100
|
+
input_per_mtok: 0.119
|
|
8101
|
+
output_per_mtok: 0.238
|
|
8102
|
+
cache_read_per_mtok: 0.00255
|
|
8103
|
+
rules:
|
|
8104
|
+
varies_by_provider: true
|
|
8105
|
+
source_url: https://openrouter.ai/api/v1/models
|
|
8045
8106
|
- canonical_id: xiaomi/mimo-v2.5-pro
|
|
8046
8107
|
publisher: xiaomi
|
|
8047
8108
|
aliases:
|
|
@@ -11014,15 +11075,15 @@ retrieval:
|
|
|
11014
11075
|
per_1k_usd: 14.00
|
|
11015
11076
|
effective_from: "2026-01-05"
|
|
11016
11077
|
source_url: https://cloud.google.com/gemini-enterprise-agent-platform/generative-ai/pricing
|
|
11017
|
-
# Sonar
|
|
11018
|
-
#
|
|
11019
|
-
# callers price requests alongside tokens.
|
|
11078
|
+
# Historical Sonar request fees end before the new API-specific rates.
|
|
11079
|
+
# The current Sonar fee is unverified, so its token row stays partial.
|
|
11020
11080
|
- channel: perplexity-api
|
|
11021
11081
|
operation: search_request_low
|
|
11022
11082
|
region: global
|
|
11023
11083
|
unit: request
|
|
11024
11084
|
per_1k_usd: 5.00
|
|
11025
11085
|
effective_from: "2025-04-18"
|
|
11086
|
+
effective_to: "2026-10-23"
|
|
11026
11087
|
source_url: https://docs.perplexity.ai/docs/getting-started/pricing
|
|
11027
11088
|
- channel: perplexity-api
|
|
11028
11089
|
operation: search_request_medium
|
|
@@ -11030,6 +11091,7 @@ retrieval:
|
|
|
11030
11091
|
unit: request
|
|
11031
11092
|
per_1k_usd: 8.00
|
|
11032
11093
|
effective_from: "2025-04-18"
|
|
11094
|
+
effective_to: "2026-10-23"
|
|
11033
11095
|
source_url: https://docs.perplexity.ai/docs/getting-started/pricing
|
|
11034
11096
|
- channel: perplexity-api
|
|
11035
11097
|
operation: search_request_high
|
|
@@ -11037,6 +11099,7 @@ retrieval:
|
|
|
11037
11099
|
unit: request
|
|
11038
11100
|
per_1k_usd: 12.00
|
|
11039
11101
|
effective_from: "2025-04-18"
|
|
11102
|
+
effective_to: "2026-10-23"
|
|
11040
11103
|
source_url: https://docs.perplexity.ai/docs/getting-started/pricing
|
|
11041
11104
|
|
|
11042
11105
|
# Gateways whose reported amount billing declines for one publisher's models
|
|
@@ -247,7 +247,8 @@ def parse_deepseek(html_text: str, *, duplicates: list[str] | None = None) -> Ta
|
|
|
247
247
|
section = ""
|
|
248
248
|
for rows_ in page_tables(html_text):
|
|
249
249
|
for cells in rows_:
|
|
250
|
-
|
|
250
|
+
# MODEL VERSION is data, not a header.
|
|
251
|
+
if cells and cells[0].strip().upper() == "MODEL" and len(cells) > 1:
|
|
251
252
|
columns = [deepseek_name(c) for c in cells[1:]]
|
|
252
253
|
for column in columns:
|
|
253
254
|
table[column] = {}
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
"""Cost a provider response object as the caller received it.
|
|
2
|
+
|
|
3
|
+
Each reader copies only the fields a price depends on: model, usage counts,
|
|
4
|
+
service tier and creation time. Prompt, output and every other field are never
|
|
5
|
+
read, so nothing downstream sees content the response carried.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from dataclasses import dataclass, field, replace
|
|
11
|
+
from datetime import datetime, timezone
|
|
12
|
+
from functools import lru_cache
|
|
13
|
+
from typing import Any, Callable, Mapping
|
|
14
|
+
|
|
15
|
+
from .catalog import CostResult, ModelUsage
|
|
16
|
+
from .loader import LoadedCatalog, load_catalog
|
|
17
|
+
|
|
18
|
+
_STANDARD_TIERS = frozenset({"default", "standard", "standard_only", "auto", "on_demand"})
|
|
19
|
+
_BATCH_TIER = "batch"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass(frozen=True, slots=True)
|
|
23
|
+
class _Facts:
|
|
24
|
+
model: str | None
|
|
25
|
+
usage: Mapping[str, int | None] = field(default_factory=dict)
|
|
26
|
+
# The input total already counts cached tokens, so it is restated in the
|
|
27
|
+
# convention of the price row that bills it.
|
|
28
|
+
input_includes_cache: bool = False
|
|
29
|
+
service_tier: str | None = None
|
|
30
|
+
at: datetime | None = None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _object(value: Any) -> Mapping[str, Any]:
|
|
34
|
+
return value if isinstance(value, Mapping) else {}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _text(value: Any) -> str | None:
|
|
38
|
+
if isinstance(value, str) and value.strip():
|
|
39
|
+
return value.strip()
|
|
40
|
+
return None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _count(value: Any) -> int | None:
|
|
44
|
+
"""A token count, or None for anything that is not a whole number."""
|
|
45
|
+
if isinstance(value, bool):
|
|
46
|
+
return None
|
|
47
|
+
if isinstance(value, int):
|
|
48
|
+
return value
|
|
49
|
+
if isinstance(value, float) and value.is_integer():
|
|
50
|
+
return int(value)
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _sum(*values: Any) -> int | None:
|
|
55
|
+
counts = [_count(value) for value in values]
|
|
56
|
+
if counts[0] is None:
|
|
57
|
+
return None
|
|
58
|
+
return sum(count or 0 for count in counts)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _first(mapping: Mapping[str, Any], *keys: str) -> Any:
|
|
62
|
+
"""The first present key: SDK objects dump snake_case, REST JSON is
|
|
63
|
+
camelCase."""
|
|
64
|
+
for key in keys:
|
|
65
|
+
if mapping.get(key) is not None:
|
|
66
|
+
return mapping[key]
|
|
67
|
+
return None
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _epoch(value: Any) -> datetime | None:
|
|
71
|
+
seconds = _count(value)
|
|
72
|
+
if seconds is None:
|
|
73
|
+
return None
|
|
74
|
+
try:
|
|
75
|
+
return datetime.fromtimestamp(seconds, timezone.utc)
|
|
76
|
+
except (OverflowError, OSError, ValueError):
|
|
77
|
+
return None
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _iso(value: Any) -> datetime | None:
|
|
81
|
+
if isinstance(value, datetime):
|
|
82
|
+
return value if value.tzinfo else value.replace(tzinfo=timezone.utc)
|
|
83
|
+
if not isinstance(value, str) or not value.strip():
|
|
84
|
+
return None
|
|
85
|
+
text = value.strip().replace("Z", "+00:00")
|
|
86
|
+
# Python 3.10 parses at most six fractional digits; Vertex sends nine.
|
|
87
|
+
head, dot, rest = text.partition(".")
|
|
88
|
+
if dot:
|
|
89
|
+
digits = len(rest) - len(rest.lstrip("0123456789"))
|
|
90
|
+
text = f"{head}.{rest[:min(digits, 6)]}{rest[digits:]}"
|
|
91
|
+
try:
|
|
92
|
+
parsed = datetime.fromisoformat(text)
|
|
93
|
+
except ValueError:
|
|
94
|
+
return None
|
|
95
|
+
return parsed if parsed.tzinfo else parsed.replace(tzinfo=timezone.utc)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _anthropic_messages(response: Mapping[str, Any]) -> _Facts:
|
|
99
|
+
usage = _object(response.get("usage"))
|
|
100
|
+
creation = _object(usage.get("cache_creation"))
|
|
101
|
+
return _Facts(
|
|
102
|
+
model=_text(response.get("model")),
|
|
103
|
+
usage={
|
|
104
|
+
"input_tokens": _count(usage.get("input_tokens")),
|
|
105
|
+
"output_tokens": _count(usage.get("output_tokens")),
|
|
106
|
+
"cache_read_tokens": _count(usage.get("cache_read_input_tokens")),
|
|
107
|
+
"cache_write_tokens": _count(usage.get("cache_creation_input_tokens")),
|
|
108
|
+
"cache_write_5m_tokens": _count(creation.get("ephemeral_5m_input_tokens")),
|
|
109
|
+
"cache_write_1h_tokens": _count(creation.get("ephemeral_1h_input_tokens")),
|
|
110
|
+
},
|
|
111
|
+
service_tier=_text(usage.get("service_tier")),
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _openai_responses(response: Mapping[str, Any]) -> _Facts:
|
|
116
|
+
usage = _object(response.get("usage"))
|
|
117
|
+
details = _object(usage.get("input_tokens_details"))
|
|
118
|
+
return _Facts(
|
|
119
|
+
model=_text(response.get("model")),
|
|
120
|
+
usage={
|
|
121
|
+
"input_tokens": _count(usage.get("input_tokens")),
|
|
122
|
+
"output_tokens": _count(usage.get("output_tokens")),
|
|
123
|
+
"cache_read_tokens": _count(details.get("cached_tokens")),
|
|
124
|
+
# Gateways such as OpenRouter report cache writes beside reads.
|
|
125
|
+
"cache_write_tokens": _count(details.get("cache_write_tokens")),
|
|
126
|
+
},
|
|
127
|
+
input_includes_cache=True,
|
|
128
|
+
service_tier=_text(response.get("service_tier")),
|
|
129
|
+
at=_epoch(response.get("created_at")),
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _openai_chat_completions(response: Mapping[str, Any]) -> _Facts:
|
|
134
|
+
usage = _object(response.get("usage"))
|
|
135
|
+
details = _object(usage.get("prompt_tokens_details"))
|
|
136
|
+
return _Facts(
|
|
137
|
+
model=_text(response.get("model")),
|
|
138
|
+
usage={
|
|
139
|
+
"input_tokens": _count(usage.get("prompt_tokens")),
|
|
140
|
+
"output_tokens": _count(usage.get("completion_tokens")),
|
|
141
|
+
"cache_read_tokens": _count(details.get("cached_tokens")),
|
|
142
|
+
"cache_write_tokens": _count(details.get("cache_write_tokens")),
|
|
143
|
+
},
|
|
144
|
+
# Gateways relaying another publisher's model keep this shape, with
|
|
145
|
+
# cached tokens inside the prompt total.
|
|
146
|
+
input_includes_cache=True,
|
|
147
|
+
service_tier=_text(response.get("service_tier")),
|
|
148
|
+
at=_epoch(response.get("created")),
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def _gemini(response: Mapping[str, Any]) -> _Facts:
|
|
153
|
+
usage = _object(_first(response, "usage_metadata", "usageMetadata"))
|
|
154
|
+
return _Facts(
|
|
155
|
+
model=_text(_first(response, "model_version", "modelVersion")),
|
|
156
|
+
usage={
|
|
157
|
+
# Tool-use prompt tokens bill as input; thinking tokens bill as
|
|
158
|
+
# output but are reported outside the candidates count.
|
|
159
|
+
"input_tokens": _sum(
|
|
160
|
+
_first(usage, "prompt_token_count", "promptTokenCount"),
|
|
161
|
+
_first(usage, "tool_use_prompt_token_count", "toolUsePromptTokenCount"),
|
|
162
|
+
),
|
|
163
|
+
"output_tokens": _sum(
|
|
164
|
+
_first(usage, "candidates_token_count", "candidatesTokenCount"),
|
|
165
|
+
_first(usage, "thoughts_token_count", "thoughtsTokenCount"),
|
|
166
|
+
),
|
|
167
|
+
"cache_read_tokens": _count(
|
|
168
|
+
_first(usage, "cached_content_token_count", "cachedContentTokenCount")
|
|
169
|
+
),
|
|
170
|
+
},
|
|
171
|
+
input_includes_cache=True,
|
|
172
|
+
at=_iso(_first(response, "create_time", "createTime")),
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _bedrock_converse(response: Mapping[str, Any]) -> _Facts:
|
|
177
|
+
usage = _object(response.get("usage"))
|
|
178
|
+
return _Facts(
|
|
179
|
+
# Converse responses do not name the model; the caller supplies it.
|
|
180
|
+
model=None,
|
|
181
|
+
usage={
|
|
182
|
+
"input_tokens": _count(usage.get("inputTokens")),
|
|
183
|
+
"output_tokens": _count(usage.get("outputTokens")),
|
|
184
|
+
"cache_read_tokens": _count(usage.get("cacheReadInputTokens")),
|
|
185
|
+
"cache_write_tokens": _count(usage.get("cacheWriteInputTokens")),
|
|
186
|
+
},
|
|
187
|
+
service_tier=_text(_object(response.get("serviceTier")).get("type")),
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _reader(response: Mapping[str, Any]) -> Callable[[Mapping[str, Any]], _Facts] | None:
|
|
192
|
+
"""The reader for this response's shape, told apart by the fields each
|
|
193
|
+
API always returns."""
|
|
194
|
+
if _first(response, "usage_metadata", "usageMetadata") is not None:
|
|
195
|
+
return _gemini
|
|
196
|
+
usage = _object(response.get("usage"))
|
|
197
|
+
if "prompt_tokens" in usage:
|
|
198
|
+
return _openai_chat_completions
|
|
199
|
+
if "inputTokens" in usage:
|
|
200
|
+
return _bedrock_converse
|
|
201
|
+
if response.get("type") == "message" or "cache_read_input_tokens" in usage:
|
|
202
|
+
return _anthropic_messages
|
|
203
|
+
if response.get("object") == "response" or "input_tokens_details" in usage:
|
|
204
|
+
return _openai_responses
|
|
205
|
+
return None
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _as_mapping(response: Any) -> Mapping[str, Any]:
|
|
209
|
+
if isinstance(response, Mapping):
|
|
210
|
+
return response
|
|
211
|
+
dump = getattr(response, "model_dump", None)
|
|
212
|
+
if callable(dump):
|
|
213
|
+
# OpenAI, Anthropic and Google GenAI SDK objects are pydantic models.
|
|
214
|
+
return dump()
|
|
215
|
+
to_dict = getattr(response, "to_dict", None)
|
|
216
|
+
if callable(to_dict):
|
|
217
|
+
return to_dict()
|
|
218
|
+
raise TypeError(
|
|
219
|
+
"response must be a mapping or a provider SDK response object, "
|
|
220
|
+
f"not {type(response).__name__}"
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
@lru_cache(maxsize=1)
|
|
225
|
+
def _bundled_catalog() -> LoadedCatalog:
|
|
226
|
+
return load_catalog(region="global")
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def _native_input_tokens(
|
|
230
|
+
usage: Mapping[str, int | None], probe: Callable[..., CostResult]
|
|
231
|
+
) -> int | None:
|
|
232
|
+
"""An input total that includes cache reads and writes, restated in the
|
|
233
|
+
convention the priced row uses.
|
|
234
|
+
|
|
235
|
+
The row records whether its input total already counts cached tokens
|
|
236
|
+
(OpenAI's includes reads, Anthropic's includes neither). A response whose
|
|
237
|
+
total includes them, priced on a row that does not deduct them, would
|
|
238
|
+
otherwise bill every cached token twice. ``probe`` costs a usage on the
|
|
239
|
+
same model and channel.
|
|
240
|
+
"""
|
|
241
|
+
total = usage.get("input_tokens")
|
|
242
|
+
cache_read = usage.get("cache_read_tokens") or 0
|
|
243
|
+
cache_write = usage.get("cache_write_tokens") or 0
|
|
244
|
+
if total is None or cache_read < 0 or cache_write < 0 or not (cache_read or cache_write):
|
|
245
|
+
return total
|
|
246
|
+
# Cached tokens exceeding the total cannot have been included in it.
|
|
247
|
+
uncached = (
|
|
248
|
+
total - cache_read - cache_write
|
|
249
|
+
if cache_read + cache_write <= total
|
|
250
|
+
else total
|
|
251
|
+
)
|
|
252
|
+
# A zero-input probe reports which cached counts the row deducts from input.
|
|
253
|
+
reasons = probe(
|
|
254
|
+
input_tokens=0, output_tokens=0, cache_read_tokens=1, cache_write_tokens=1
|
|
255
|
+
).reasons
|
|
256
|
+
native = uncached
|
|
257
|
+
if "cache_read_exceeds_input" in reasons:
|
|
258
|
+
native += cache_read
|
|
259
|
+
if "cache_write_exceeds_input" in reasons:
|
|
260
|
+
native += cache_write
|
|
261
|
+
return native
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def cost_from_response(
|
|
265
|
+
provider: str,
|
|
266
|
+
response: Any,
|
|
267
|
+
*,
|
|
268
|
+
catalog: LoadedCatalog | None = None,
|
|
269
|
+
model: str | None = None,
|
|
270
|
+
at: Any = None,
|
|
271
|
+
channel: str | None = None,
|
|
272
|
+
region: str | None = None,
|
|
273
|
+
) -> CostResult:
|
|
274
|
+
"""The cost of one provider response, from the usage it reports.
|
|
275
|
+
|
|
276
|
+
``response`` is the object the provider SDK returned, or its JSON as a
|
|
277
|
+
mapping: OpenAI Chat Completions or Responses, Anthropic Messages, Google
|
|
278
|
+
Gemini ``generateContent``, or Bedrock Converse. ``provider`` names who
|
|
279
|
+
served the call and selects its direct pricing channel unless ``channel``
|
|
280
|
+
is given. ``model`` overrides the response's model id and is required for
|
|
281
|
+
Bedrock Converse, whose responses do not name one. ``at`` defaults to the
|
|
282
|
+
response's creation time when it reports one, else now. ``catalog``
|
|
283
|
+
defaults to the catalog bundled with this package.
|
|
284
|
+
|
|
285
|
+
A streamed call is costed from its final response or final usage chunk.
|
|
286
|
+
Fees outside token usage, such as web search, are not included. A service
|
|
287
|
+
tier other than standard or batch turns the result ``partial`` with
|
|
288
|
+
``service_tier`` missing, since the catalog does not carry tier rates.
|
|
289
|
+
"""
|
|
290
|
+
data = _as_mapping(response)
|
|
291
|
+
reader = _reader(data)
|
|
292
|
+
if reader is None:
|
|
293
|
+
raise ValueError("response carries no usage this function can read")
|
|
294
|
+
facts = reader(data)
|
|
295
|
+
when = at if at is not None else facts.at or datetime.now(timezone.utc)
|
|
296
|
+
model_id = _text(model) or facts.model
|
|
297
|
+
tier = (facts.service_tier or "").lower() or None
|
|
298
|
+
if model_id is None:
|
|
299
|
+
return CostResult(
|
|
300
|
+
None, None, None, "unpriced", ("missing_model",), missing_dimensions=("model",)
|
|
301
|
+
)
|
|
302
|
+
loaded = catalog if catalog is not None else _bundled_catalog()
|
|
303
|
+
batch = tier == _BATCH_TIER
|
|
304
|
+
|
|
305
|
+
def cost(**usage: Any) -> CostResult:
|
|
306
|
+
return loaded.calculate_cost(
|
|
307
|
+
model=model_id,
|
|
308
|
+
usage=ModelUsage(**usage, batch=batch),
|
|
309
|
+
at=when,
|
|
310
|
+
channel=channel,
|
|
311
|
+
provider=provider,
|
|
312
|
+
region=region,
|
|
313
|
+
)
|
|
314
|
+
|
|
315
|
+
usage = dict(facts.usage)
|
|
316
|
+
if facts.input_includes_cache:
|
|
317
|
+
usage["input_tokens"] = _native_input_tokens(usage, cost)
|
|
318
|
+
result = cost(**usage)
|
|
319
|
+
if tier is None or tier in _STANDARD_TIERS or batch or result.status == "unpriced":
|
|
320
|
+
return result
|
|
321
|
+
return replace(
|
|
322
|
+
result,
|
|
323
|
+
status="partial",
|
|
324
|
+
missing_dimensions=tuple(dict.fromkeys((*result.missing_dimensions, "service_tier"))),
|
|
325
|
+
)
|
|
@@ -61,8 +61,8 @@ SEARCH_OPERATION_BY_CHANNEL: Mapping[str, str] = MappingProxyType({
|
|
|
61
61
|
"anthropic-api": "web_search",
|
|
62
62
|
"google-api": "google_search_grounding",
|
|
63
63
|
"google-vertex-ai": "google_search_grounding",
|
|
64
|
-
#
|
|
65
|
-
#
|
|
64
|
+
# Historical Sonar request fees use this operation; no current fee is
|
|
65
|
+
# assigned until its API and billing unit can be verified.
|
|
66
66
|
"perplexity-api": "search_request_low",
|
|
67
67
|
})
|
|
68
68
|
FETCH_OPERATION_BY_CHANNEL: Mapping[str, str] = MappingProxyType({
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: metergraph-core
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.0
|
|
4
4
|
Summary: Open, effective-dated LLM pricing catalog and deterministic cost calculator for popular models across major providers and gateways, including OpenAI, Anthropic, Google Gemini, AWS Bedrock, OpenRouter, Vercel AI Gateway, and more.
|
|
5
5
|
Author: Metergraph
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -200,6 +200,52 @@ A call after it ended resolves to that price, stays `priced`, and carries
|
|
|
200
200
|
applied `price.effective_to` says when it ended. A closed price followed by a
|
|
201
201
|
later one never extends, so a gap before a scheduled price stays `unpriced`.
|
|
202
202
|
|
|
203
|
+
### Costing a provider response
|
|
204
|
+
|
|
205
|
+
`cost_from_response` reads the usage out of a response you already have and
|
|
206
|
+
costs it, so you do not extract token counts yourself:
|
|
207
|
+
|
|
208
|
+
```python
|
|
209
|
+
from openai import OpenAI
|
|
210
|
+
from metergraph_core import cost_from_response
|
|
211
|
+
|
|
212
|
+
response = OpenAI().chat.completions.create(model="gpt-5.4-mini", messages=messages)
|
|
213
|
+
result = cost_from_response("openai", response)
|
|
214
|
+
print(result.status, result.model_id, result.cost, result.currency)
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
It accepts the object a provider SDK returned or the same response as JSON:
|
|
218
|
+
|
|
219
|
+
| Provider API | Example `provider` |
|
|
220
|
+
|---|---|
|
|
221
|
+
| OpenAI Chat Completions, and gateways that return its shape | `openai`, `openrouter`, `deepseek` |
|
|
222
|
+
| OpenAI Responses | `openai` |
|
|
223
|
+
| Anthropic Messages | `anthropic` |
|
|
224
|
+
| Google Gemini `generateContent` | `google` |
|
|
225
|
+
| Bedrock Converse (pass `model=`, the response does not name one) | `bedrock` |
|
|
226
|
+
|
|
227
|
+
`provider` names who served the call and selects its direct channel unless
|
|
228
|
+
`channel` is given. The response shape is detected from its fields. Only the
|
|
229
|
+
model, usage counts, service tier and creation time are read; prompt and
|
|
230
|
+
output content never are.
|
|
231
|
+
|
|
232
|
+
Cached tokens are billed once whichever convention the response uses. OpenAI
|
|
233
|
+
and Gemini count cached tokens inside the input total, so the total is
|
|
234
|
+
restated in the convention of the price row that bills it, including a
|
|
235
|
+
gateway row for another publisher's model. Gemini thinking tokens bill as
|
|
236
|
+
output.
|
|
237
|
+
|
|
238
|
+
The price in effect when the response was created applies, from `created`,
|
|
239
|
+
`created_at` or `createTime`; `at=` overrides it, and a response with no
|
|
240
|
+
creation time is priced now. `catalog=` costs against a catalog you loaded;
|
|
241
|
+
the default is the bundled one.
|
|
242
|
+
|
|
243
|
+
A batch service tier bills at batch rates. Any other tier besides standard,
|
|
244
|
+
such as `priority` or `flex`, returns `partial` with `service_tier` in
|
|
245
|
+
`missing_dimensions`, since the catalog carries no tier rates. Cost a streamed
|
|
246
|
+
call from its final response or final usage chunk. Fees outside token usage,
|
|
247
|
+
such as web search, are not included.
|
|
248
|
+
|
|
203
249
|
## Model registry
|
|
204
250
|
|
|
205
251
|
```python
|
|
@@ -466,6 +512,7 @@ from metergraph_core import (
|
|
|
466
512
|
ResolvedPrice,
|
|
467
513
|
SourceRegistry,
|
|
468
514
|
apply_catalog_delta,
|
|
515
|
+
cost_from_response,
|
|
469
516
|
discovery_evidence,
|
|
470
517
|
extract,
|
|
471
518
|
load_catalog,
|
|
@@ -9,6 +9,7 @@ src/metergraph_core/extractors.py
|
|
|
9
9
|
src/metergraph_core/loader.py
|
|
10
10
|
src/metergraph_core/models.py
|
|
11
11
|
src/metergraph_core/refresh.py
|
|
12
|
+
src/metergraph_core/responses.py
|
|
12
13
|
src/metergraph_core/retrieval.py
|
|
13
14
|
src/metergraph_core/sources.py
|
|
14
15
|
src/metergraph_core.egg-info/PKG-INFO
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{metergraph_core-0.6.1 → metergraph_core-0.7.0}/src/metergraph_core.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|