metergraph-core 0.7.0__tar.gz → 0.7.4__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.
Files changed (24) hide show
  1. {metergraph_core-0.7.0/src/metergraph_core.egg-info → metergraph_core-0.7.4}/PKG-INFO +17 -3
  2. {metergraph_core-0.7.0 → metergraph_core-0.7.4}/README.md +16 -2
  3. {metergraph_core-0.7.0 → metergraph_core-0.7.4}/pyproject.toml +1 -1
  4. {metergraph_core-0.7.0 → metergraph_core-0.7.4}/src/metergraph_core/__init__.py +3 -1
  5. {metergraph_core-0.7.0 → metergraph_core-0.7.4}/src/metergraph_core/catalog.py +24 -2
  6. {metergraph_core-0.7.0 → metergraph_core-0.7.4}/src/metergraph_core/catalog_delta.py +1 -0
  7. {metergraph_core-0.7.0 → metergraph_core-0.7.4}/src/metergraph_core/data/prices.yaml +29 -6
  8. {metergraph_core-0.7.0 → metergraph_core-0.7.4}/src/metergraph_core/loader.py +12 -0
  9. {metergraph_core-0.7.0 → metergraph_core-0.7.4}/src/metergraph_core/responses.py +50 -18
  10. {metergraph_core-0.7.0 → metergraph_core-0.7.4/src/metergraph_core.egg-info}/PKG-INFO +17 -3
  11. {metergraph_core-0.7.0 → metergraph_core-0.7.4}/MANIFEST.in +0 -0
  12. {metergraph_core-0.7.0 → metergraph_core-0.7.4}/setup.cfg +0 -0
  13. {metergraph_core-0.7.0 → metergraph_core-0.7.4}/src/metergraph_core/billing.py +0 -0
  14. {metergraph_core-0.7.0 → metergraph_core-0.7.4}/src/metergraph_core/data/models.yaml +0 -0
  15. {metergraph_core-0.7.0 → metergraph_core-0.7.4}/src/metergraph_core/data/sources.yaml +0 -0
  16. {metergraph_core-0.7.0 → metergraph_core-0.7.4}/src/metergraph_core/extractors.py +0 -0
  17. {metergraph_core-0.7.0 → metergraph_core-0.7.4}/src/metergraph_core/models.py +0 -0
  18. {metergraph_core-0.7.0 → metergraph_core-0.7.4}/src/metergraph_core/refresh.py +0 -0
  19. {metergraph_core-0.7.0 → metergraph_core-0.7.4}/src/metergraph_core/retrieval.py +0 -0
  20. {metergraph_core-0.7.0 → metergraph_core-0.7.4}/src/metergraph_core/sources.py +0 -0
  21. {metergraph_core-0.7.0 → metergraph_core-0.7.4}/src/metergraph_core.egg-info/SOURCES.txt +0 -0
  22. {metergraph_core-0.7.0 → metergraph_core-0.7.4}/src/metergraph_core.egg-info/dependency_links.txt +0 -0
  23. {metergraph_core-0.7.0 → metergraph_core-0.7.4}/src/metergraph_core.egg-info/requires.txt +0 -0
  24. {metergraph_core-0.7.0 → metergraph_core-0.7.4}/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.7.0
3
+ Version: 0.7.4
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
@@ -179,6 +179,14 @@ of the applied price. The bundled catalog is USD. `cost` and
179
179
  `components.input`, `output`, `cache_read`, `cache_write` and `request` are in
180
180
  that currency; the older `cost_usd` and `*_usd` names return the same values.
181
181
 
182
+ `ModelUsage(batch=True)`, or `batch=True` on `price` and `cost`, bills at the
183
+ row's batch input and output rates. Its cache reads bill at
184
+ `Price.batch_cache_read_per_mtok` when the row has one, and otherwise at the
185
+ standard `cache_read_per_mtok`, as before. A standard call always uses the
186
+ standard cache-read rate. A row with only a batch cache rate fully prices a
187
+ cached batch call, while a cached standard call on it is `partial` with
188
+ `cache_read_rate_unavailable`.
189
+
182
190
  A `ModelUsage` count left `None` is unknown, not zero. A missing input or
183
191
  output count keeps the subtotal that is known, marks the result `partial`, and
184
192
  lists `input_tokens` or `output_tokens` in `missing_dimensions`.
@@ -225,7 +233,9 @@ It accepts the object a provider SDK returned or the same response as JSON:
225
233
  | Bedrock Converse (pass `model=`, the response does not name one) | `bedrock` |
226
234
 
227
235
  `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
236
+ `channel` is given. The response format is detected from its fields; pass
237
+ `format=`, one of `RESPONSE_FORMATS`, for a response too sparse to detect.
238
+ `response_format(response)` returns the detected format, or `None`. Only the
229
239
  model, usage counts, service tier and creation time are read; prompt and
230
240
  output content never are.
231
241
 
@@ -240,7 +250,9 @@ The price in effect when the response was created applies, from `created`,
240
250
  creation time is priced now. `catalog=` costs against a catalog you loaded;
241
251
  the default is the bundled one.
242
252
 
243
- A batch service tier bills at batch rates. Any other tier besides standard,
253
+ A batch service tier bills at batch rates, with cache reads priced as for
254
+ `ModelUsage(batch=True)` above. Pass `service_tier=` when the
255
+ response does not state its tier, such as an OpenAI batch output line. Any other tier besides standard,
244
256
  such as `priority` or `flex`, returns `partial` with `service_tier` in
245
257
  `missing_dimensions`, since the catalog carries no tier rates. Cost a streamed
246
258
  call from its final response or final usage chunk. Fees outside token usage,
@@ -508,6 +520,7 @@ from metergraph_core import (
508
520
  PriceResult,
509
521
  PriceRowKey,
510
522
  PriceSource,
523
+ RESPONSE_FORMATS,
511
524
  RefreshResult,
512
525
  ResolvedPrice,
513
526
  SourceRegistry,
@@ -524,6 +537,7 @@ from metergraph_core import (
524
537
  parse_catalog_delta,
525
538
  plan_low_risk_refresh,
526
539
  resolve_billing,
540
+ response_format,
527
541
  structural_fingerprint,
528
542
  validate_model_registry,
529
543
  validate_source_registry,
@@ -149,6 +149,14 @@ of the applied price. The bundled catalog is USD. `cost` and
149
149
  `components.input`, `output`, `cache_read`, `cache_write` and `request` are in
150
150
  that currency; the older `cost_usd` and `*_usd` names return the same values.
151
151
 
152
+ `ModelUsage(batch=True)`, or `batch=True` on `price` and `cost`, bills at the
153
+ row's batch input and output rates. Its cache reads bill at
154
+ `Price.batch_cache_read_per_mtok` when the row has one, and otherwise at the
155
+ standard `cache_read_per_mtok`, as before. A standard call always uses the
156
+ standard cache-read rate. A row with only a batch cache rate fully prices a
157
+ cached batch call, while a cached standard call on it is `partial` with
158
+ `cache_read_rate_unavailable`.
159
+
152
160
  A `ModelUsage` count left `None` is unknown, not zero. A missing input or
153
161
  output count keeps the subtotal that is known, marks the result `partial`, and
154
162
  lists `input_tokens` or `output_tokens` in `missing_dimensions`.
@@ -195,7 +203,9 @@ It accepts the object a provider SDK returned or the same response as JSON:
195
203
  | Bedrock Converse (pass `model=`, the response does not name one) | `bedrock` |
196
204
 
197
205
  `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
206
+ `channel` is given. The response format is detected from its fields; pass
207
+ `format=`, one of `RESPONSE_FORMATS`, for a response too sparse to detect.
208
+ `response_format(response)` returns the detected format, or `None`. Only the
199
209
  model, usage counts, service tier and creation time are read; prompt and
200
210
  output content never are.
201
211
 
@@ -210,7 +220,9 @@ The price in effect when the response was created applies, from `created`,
210
220
  creation time is priced now. `catalog=` costs against a catalog you loaded;
211
221
  the default is the bundled one.
212
222
 
213
- A batch service tier bills at batch rates. Any other tier besides standard,
223
+ A batch service tier bills at batch rates, with cache reads priced as for
224
+ `ModelUsage(batch=True)` above. Pass `service_tier=` when the
225
+ response does not state its tier, such as an OpenAI batch output line. Any other tier besides standard,
214
226
  such as `priority` or `flex`, returns `partial` with `service_tier` in
215
227
  `missing_dimensions`, since the catalog carries no tier rates. Cost a streamed
216
228
  call from its final response or final usage chunk. Fees outside token usage,
@@ -478,6 +490,7 @@ from metergraph_core import (
478
490
  PriceResult,
479
491
  PriceRowKey,
480
492
  PriceSource,
493
+ RESPONSE_FORMATS,
481
494
  RefreshResult,
482
495
  ResolvedPrice,
483
496
  SourceRegistry,
@@ -494,6 +507,7 @@ from metergraph_core import (
494
507
  parse_catalog_delta,
495
508
  plan_low_risk_refresh,
496
509
  resolve_billing,
510
+ response_format,
497
511
  structural_fingerprint,
498
512
  validate_model_registry,
499
513
  validate_source_registry,
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "metergraph-core"
3
- version = "0.7.0"
3
+ version = "0.7.4"
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,7 +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
+ from .responses import RESPONSE_FORMATS, cost_from_response, response_format
63
63
  from .extractors import (
64
64
  ExtractedRate,
65
65
  Extraction,
@@ -135,6 +135,8 @@ __all__ = [
135
135
  "count_fetch_units",
136
136
  "count_search_units",
137
137
  "cost_from_response",
138
+ "response_format",
139
+ "RESPONSE_FORMATS",
138
140
  "counts_cache_read_in_input",
139
141
  "direct_channel_for_provider",
140
142
  "normalize_provider",
@@ -225,6 +225,22 @@ class Price:
225
225
  # The currency every rate on this row is stated in. The `_usd` names on
226
226
  # rates and results predate it and are kept for existing callers.
227
227
  currency: str = "USD"
228
+ # The rate a batch call's cache reads bill at, when the provider publishes
229
+ # one. Absent, batch cache reads keep the standard cache-read rate; it is
230
+ # never inferred from another rate.
231
+ batch_cache_read_per_mtok: Decimal | None = None
232
+
233
+ def __post_init__(self) -> None:
234
+ # The loader checks the rate it reads, but a row built or replaced
235
+ # directly must not bill a negative or undefined cache component.
236
+ rate = self.batch_cache_read_per_mtok
237
+ if rate is not None and not (
238
+ isinstance(rate, Decimal) and rate.is_finite() and rate >= 0
239
+ ):
240
+ raise ValueError(
241
+ "batch_cache_read_per_mtok must be a finite non-negative "
242
+ f"Decimal or None, not {rate!r}"
243
+ )
228
244
 
229
245
 
230
246
  @dataclass(frozen=True, slots=True)
@@ -353,6 +369,7 @@ def _billed_terms(resolved: ResolvedPrice) -> tuple[Any, ...]:
353
369
  price.cache_write_1h_per_mtok,
354
370
  price.batch_input_per_mtok,
355
371
  price.batch_output_per_mtok,
372
+ price.batch_cache_read_per_mtok,
356
373
  price.request_fee_per_1k_usd,
357
374
  resolved.rules,
358
375
  )
@@ -505,12 +522,17 @@ def _price_tokens(
505
522
 
506
523
  input_rate = price.input_per_mtok
507
524
  output_rate = price.output_per_mtok
525
+ cache_read_rate = price.cache_read_per_mtok
508
526
  if batch:
509
527
  if price.batch_input_per_mtok is None or price.batch_output_per_mtok is None:
510
528
  reasons.append("batch_rate_unavailable")
511
529
  else:
512
530
  input_rate = price.batch_input_per_mtok
513
531
  output_rate = price.batch_output_per_mtok
532
+ # A row without a batch cache rate keeps billing batch cache reads at
533
+ # the standard rate, as it always has.
534
+ if price.batch_cache_read_per_mtok is not None:
535
+ cache_read_rate = price.batch_cache_read_per_mtok
514
536
 
515
537
  input_multiplier = Decimal("1")
516
538
  output_multiplier = Decimal("1")
@@ -547,12 +569,12 @@ def _price_tokens(
547
569
  else:
548
570
  output_usd = Decimal(output_count) * output_rate * output_multiplier / _MILLION
549
571
  if cache_read_count:
550
- if price.cache_read_per_mtok is None:
572
+ if cache_read_rate is None:
551
573
  reasons.append("cache_read_rate_unavailable")
552
574
  else:
553
575
  cache_read_usd = (
554
576
  Decimal(cache_read_count)
555
- * price.cache_read_per_mtok
577
+ * cache_read_rate
556
578
  * input_multiplier
557
579
  / _MILLION
558
580
  )
@@ -45,6 +45,7 @@ RATE_FIELDS = (
45
45
  "cache_write_1h_per_mtok",
46
46
  "batch_input_per_mtok",
47
47
  "batch_output_per_mtok",
48
+ "batch_cache_read_per_mtok",
48
49
  "request_fee_per_1k_usd",
49
50
  )
50
51
  _PRICE_FIELDS = frozenset(
@@ -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-23"
9
+ version: "2026-10-25"
10
10
  currency: USD
11
- pricing_verified_at: "2026-10-23"
11
+ pricing_verified_at: "2026-10-25"
12
12
  models:
13
13
  - canonical_id: openai/gpt-6-sol
14
14
  publisher: openai
@@ -1599,18 +1599,32 @@ models:
1599
1599
  - channel: google-api
1600
1600
  region: global
1601
1601
  effective_from: "2026-10-13"
1602
- effective_to: "2026-12-01"
1602
+ effective_to: "2026-10-25"
1603
1603
  input_per_mtok: 2.00
1604
1604
  output_per_mtok: 10.00
1605
1605
  cache_read_per_mtok: 0.10
1606
1606
  source_url: https://ai.google.dev/gemini-api/docs/generate-content/latest-model
1607
+ - channel: google-api
1608
+ region: global
1609
+ effective_from: "2026-10-25"
1610
+ effective_to: "2026-12-01"
1611
+ input_per_mtok: 2.00
1612
+ output_per_mtok: 10.00
1613
+ cache_read_per_mtok: 0.10
1614
+ batch_input_per_mtok: 1.00
1615
+ batch_output_per_mtok: 5.00
1616
+ batch_cache_read_per_mtok: 0.05
1617
+ source_url: https://ai.google.dev/gemini-api/docs/pricing
1607
1618
  - channel: google-api
1608
1619
  region: global
1609
1620
  effective_from: "2026-12-01"
1610
1621
  input_per_mtok: 4.00
1611
1622
  output_per_mtok: 20.00
1612
1623
  cache_read_per_mtok: 0.20
1613
- source_url: https://ai.google.dev/gemini-api/docs/generate-content/latest-model
1624
+ batch_input_per_mtok: 2.00
1625
+ batch_output_per_mtok: 10.00
1626
+ batch_cache_read_per_mtok: 0.10
1627
+ source_url: https://ai.google.dev/gemini-api/docs/pricing
1614
1628
  - channel: vercel-ai-gateway
1615
1629
  region: global
1616
1630
  effective_from: "2026-10-13"
@@ -2506,15 +2520,24 @@ models:
2506
2520
  rules:
2507
2521
  uncaptured_fees: true
2508
2522
  source_url: https://docs.perplexity.ai/docs/getting-started/pricing
2509
- # The gateway bills Sonar's built-in search per call, so the row carries
2510
- # it as a request fee and the call is priced in full.
2511
2523
  - channel: vercel-ai-gateway
2512
2524
  region: global
2513
2525
  effective_from: "2026-10-11"
2526
+ effective_to: "2026-10-24"
2514
2527
  input_per_mtok: 0.25
2515
2528
  output_per_mtok: 2.50
2516
2529
  request_fee_per_1k_usd: 2.50
2517
2530
  source_url: https://vercel.com/ai-gateway/models/sonar
2531
+ # The gateway lists Sonar's web searches as a charge of their own, billed
2532
+ # per search and not per request, so no request fee applies.
2533
+ - channel: vercel-ai-gateway
2534
+ region: global
2535
+ effective_from: "2026-10-24"
2536
+ input_per_mtok: 0.25
2537
+ output_per_mtok: 2.50
2538
+ rules:
2539
+ uncaptured_fees: true
2540
+ source_url: https://vercel.com/ai-gateway/models/sonar
2518
2541
 
2519
2542
  - canonical_id: openai/gpt-5-mini
2520
2543
  publisher: openai
@@ -388,6 +388,17 @@ def parse_catalog(
388
388
  f"{canonical}: overlapping {channel}/{region} price windows"
389
389
  )
390
390
  seen_windows.append((channel, region, effective_from, effective_to))
391
+ batch_cache_read = price.get("batch_cache_read_per_mtok")
392
+ batch_cache_read_per_mtok = _decimal(batch_cache_read)
393
+ # A stated batch cache rate replaces the standard one on batch
394
+ # calls, so one that cannot be read must not quietly fall back.
395
+ if batch_cache_read is not None and (
396
+ batch_cache_read_per_mtok is None or batch_cache_read_per_mtok < 0
397
+ ):
398
+ raise CatalogError(
399
+ f"{canonical}: invalid batch_cache_read_per_mtok "
400
+ f"{batch_cache_read!r}"
401
+ )
391
402
  prices.append(
392
403
  Price(
393
404
  id=f"{canonical}:{channel}:{region}:{_id_date(effective_from)}",
@@ -412,6 +423,7 @@ def parse_catalog(
412
423
  source_url=str(price["source_url"]).strip(),
413
424
  publisher=publisher,
414
425
  currency=currency,
426
+ batch_cache_read_per_mtok=batch_cache_read_per_mtok,
415
427
  )
416
428
  )
417
429
  return version, {key: tuple(value) for key, value in aliases.items()}, _unique_ids(prices)
@@ -12,7 +12,7 @@ from datetime import datetime, timezone
12
12
  from functools import lru_cache
13
13
  from typing import Any, Callable, Mapping
14
14
 
15
- from .catalog import CostResult, ModelUsage
15
+ from .catalog import CostResult, ModelUsage, _coerce_datetime
16
16
  from .loader import LoadedCatalog, load_catalog
17
17
 
18
18
  _STANDARD_TIERS = frozenset({"default", "standard", "standard_only", "auto", "on_demand"})
@@ -188,23 +188,41 @@ def _bedrock_converse(response: Mapping[str, Any]) -> _Facts:
188
188
  )
189
189
 
190
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."""
191
+ _READERS: dict[str, Callable[[Mapping[str, Any]], _Facts]] = {
192
+ "openai.chat_completions": _openai_chat_completions,
193
+ "openai.responses": _openai_responses,
194
+ "anthropic.messages": _anthropic_messages,
195
+ "gemini.generate_content": _gemini,
196
+ "bedrock.converse": _bedrock_converse,
197
+ }
198
+
199
+ # The response formats :func:`cost_from_response` reads.
200
+ RESPONSE_FORMATS = tuple(_READERS)
201
+
202
+
203
+ def _detect(response: Mapping[str, Any]) -> str | None:
204
+ """The format of this response, told apart by the fields each API
205
+ always returns."""
194
206
  if _first(response, "usage_metadata", "usageMetadata") is not None:
195
- return _gemini
207
+ return "gemini.generate_content"
196
208
  usage = _object(response.get("usage"))
197
209
  if "prompt_tokens" in usage:
198
- return _openai_chat_completions
210
+ return "openai.chat_completions"
199
211
  if "inputTokens" in usage:
200
- return _bedrock_converse
212
+ return "bedrock.converse"
201
213
  if response.get("type") == "message" or "cache_read_input_tokens" in usage:
202
- return _anthropic_messages
214
+ return "anthropic.messages"
203
215
  if response.get("object") == "response" or "input_tokens_details" in usage:
204
- return _openai_responses
216
+ return "openai.responses"
205
217
  return None
206
218
 
207
219
 
220
+ def response_format(response: Any) -> str | None:
221
+ """The :data:`RESPONSE_FORMATS` entry ``response`` is in, or None when its
222
+ shape is not recognized."""
223
+ return _detect(_as_mapping(response))
224
+
225
+
208
226
  def _as_mapping(response: Any) -> Mapping[str, Any]:
209
227
  if isinstance(response, Mapping):
210
228
  return response
@@ -270,6 +288,8 @@ def cost_from_response(
270
288
  at: Any = None,
271
289
  channel: str | None = None,
272
290
  region: str | None = None,
291
+ service_tier: str | None = None,
292
+ format: str | None = None,
273
293
  ) -> CostResult:
274
294
  """The cost of one provider response, from the usage it reports.
275
295
 
@@ -280,24 +300,36 @@ def cost_from_response(
280
300
  is given. ``model`` overrides the response's model id and is required for
281
301
  Bedrock Converse, whose responses do not name one. ``at`` defaults to the
282
302
  response's creation time when it reports one, else now. ``catalog``
283
- defaults to the catalog bundled with this package.
303
+ defaults to the catalog bundled with this package. ``format``, one of
304
+ :data:`RESPONSE_FORMATS`, names the response's format; without it the
305
+ format is detected from the response's fields.
284
306
 
285
307
  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.
308
+ Fees outside token usage, such as web search, are not included.
309
+ ``service_tier`` overrides the tier the response reports, for responses
310
+ that do not state it, such as an OpenAI batch output line. A tier other
311
+ than standard or batch turns the result ``partial`` with ``service_tier``
312
+ missing, since the catalog does not carry tier rates.
289
313
  """
290
314
  data = _as_mapping(response)
291
- reader = _reader(data)
292
- if reader is None:
315
+ if format is not None and format not in _READERS:
316
+ raise ValueError(f"format must be one of {', '.join(RESPONSE_FORMATS)}")
317
+ detected = format or _detect(data)
318
+ if detected is None:
293
319
  raise ValueError("response carries no usage this function can read")
294
- facts = reader(data)
320
+ facts = _READERS[detected](data)
295
321
  when = at if at is not None else facts.at or datetime.now(timezone.utc)
296
322
  model_id = _text(model) or facts.model
297
- tier = (facts.service_tier or "").lower() or None
323
+ tier = (_text(service_tier) or facts.service_tier or "").lower() or None
298
324
  if model_id is None:
299
325
  return CostResult(
300
- None, None, None, "unpriced", ("missing_model",), missing_dimensions=("model",)
326
+ None,
327
+ None,
328
+ None,
329
+ "unpriced",
330
+ ("missing_model",),
331
+ effective_at=_coerce_datetime(when),
332
+ missing_dimensions=("model",),
301
333
  )
302
334
  loaded = catalog if catalog is not None else _bundled_catalog()
303
335
  batch = tier == _BATCH_TIER
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: metergraph-core
3
- Version: 0.7.0
3
+ Version: 0.7.4
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
@@ -179,6 +179,14 @@ of the applied price. The bundled catalog is USD. `cost` and
179
179
  `components.input`, `output`, `cache_read`, `cache_write` and `request` are in
180
180
  that currency; the older `cost_usd` and `*_usd` names return the same values.
181
181
 
182
+ `ModelUsage(batch=True)`, or `batch=True` on `price` and `cost`, bills at the
183
+ row's batch input and output rates. Its cache reads bill at
184
+ `Price.batch_cache_read_per_mtok` when the row has one, and otherwise at the
185
+ standard `cache_read_per_mtok`, as before. A standard call always uses the
186
+ standard cache-read rate. A row with only a batch cache rate fully prices a
187
+ cached batch call, while a cached standard call on it is `partial` with
188
+ `cache_read_rate_unavailable`.
189
+
182
190
  A `ModelUsage` count left `None` is unknown, not zero. A missing input or
183
191
  output count keeps the subtotal that is known, marks the result `partial`, and
184
192
  lists `input_tokens` or `output_tokens` in `missing_dimensions`.
@@ -225,7 +233,9 @@ It accepts the object a provider SDK returned or the same response as JSON:
225
233
  | Bedrock Converse (pass `model=`, the response does not name one) | `bedrock` |
226
234
 
227
235
  `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
236
+ `channel` is given. The response format is detected from its fields; pass
237
+ `format=`, one of `RESPONSE_FORMATS`, for a response too sparse to detect.
238
+ `response_format(response)` returns the detected format, or `None`. Only the
229
239
  model, usage counts, service tier and creation time are read; prompt and
230
240
  output content never are.
231
241
 
@@ -240,7 +250,9 @@ The price in effect when the response was created applies, from `created`,
240
250
  creation time is priced now. `catalog=` costs against a catalog you loaded;
241
251
  the default is the bundled one.
242
252
 
243
- A batch service tier bills at batch rates. Any other tier besides standard,
253
+ A batch service tier bills at batch rates, with cache reads priced as for
254
+ `ModelUsage(batch=True)` above. Pass `service_tier=` when the
255
+ response does not state its tier, such as an OpenAI batch output line. Any other tier besides standard,
244
256
  such as `priority` or `flex`, returns `partial` with `service_tier` in
245
257
  `missing_dimensions`, since the catalog carries no tier rates. Cost a streamed
246
258
  call from its final response or final usage chunk. Fees outside token usage,
@@ -508,6 +520,7 @@ from metergraph_core import (
508
520
  PriceResult,
509
521
  PriceRowKey,
510
522
  PriceSource,
523
+ RESPONSE_FORMATS,
511
524
  RefreshResult,
512
525
  ResolvedPrice,
513
526
  SourceRegistry,
@@ -524,6 +537,7 @@ from metergraph_core import (
524
537
  parse_catalog_delta,
525
538
  plan_low_risk_refresh,
526
539
  resolve_billing,
540
+ response_format,
527
541
  structural_fingerprint,
528
542
  validate_model_registry,
529
543
  validate_source_registry,