metergraph-core 0.8.4__tar.gz → 0.8.6__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.8.4/src/metergraph_core.egg-info → metergraph_core-0.8.6}/PKG-INFO +31 -1
- {metergraph_core-0.8.4 → metergraph_core-0.8.6}/README.md +30 -0
- {metergraph_core-0.8.4 → metergraph_core-0.8.6}/pyproject.toml +1 -1
- {metergraph_core-0.8.4 → metergraph_core-0.8.6}/src/metergraph_core/__init__.py +6 -0
- {metergraph_core-0.8.4 → metergraph_core-0.8.6}/src/metergraph_core/billing.py +5 -1
- metergraph_core-0.8.6/src/metergraph_core/calls.py +188 -0
- {metergraph_core-0.8.4 → metergraph_core-0.8.6}/src/metergraph_core/data/models.yaml +32 -1
- {metergraph_core-0.8.4 → metergraph_core-0.8.6}/src/metergraph_core/data/prices.yaml +163 -2
- {metergraph_core-0.8.4 → metergraph_core-0.8.6}/src/metergraph_core/extractors.py +7 -2
- {metergraph_core-0.8.4 → metergraph_core-0.8.6}/src/metergraph_core/loader.py +17 -8
- {metergraph_core-0.8.4 → metergraph_core-0.8.6}/src/metergraph_core/models.py +6 -2
- {metergraph_core-0.8.4 → metergraph_core-0.8.6}/src/metergraph_core/retrieval.py +2 -1
- {metergraph_core-0.8.4 → metergraph_core-0.8.6/src/metergraph_core.egg-info}/PKG-INFO +31 -1
- {metergraph_core-0.8.4 → metergraph_core-0.8.6}/src/metergraph_core.egg-info/SOURCES.txt +1 -0
- {metergraph_core-0.8.4 → metergraph_core-0.8.6}/MANIFEST.in +0 -0
- {metergraph_core-0.8.4 → metergraph_core-0.8.6}/setup.cfg +0 -0
- {metergraph_core-0.8.4 → metergraph_core-0.8.6}/src/metergraph_core/catalog.py +0 -0
- {metergraph_core-0.8.4 → metergraph_core-0.8.6}/src/metergraph_core/catalog_delta.py +0 -0
- {metergraph_core-0.8.4 → metergraph_core-0.8.6}/src/metergraph_core/data/sources.yaml +0 -0
- {metergraph_core-0.8.4 → metergraph_core-0.8.6}/src/metergraph_core/refresh.py +0 -0
- {metergraph_core-0.8.4 → metergraph_core-0.8.6}/src/metergraph_core/responses.py +0 -0
- {metergraph_core-0.8.4 → metergraph_core-0.8.6}/src/metergraph_core/sources.py +0 -0
- {metergraph_core-0.8.4 → metergraph_core-0.8.6}/src/metergraph_core.egg-info/dependency_links.txt +0 -0
- {metergraph_core-0.8.4 → metergraph_core-0.8.6}/src/metergraph_core.egg-info/requires.txt +0 -0
- {metergraph_core-0.8.4 → metergraph_core-0.8.6}/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.8.
|
|
3
|
+
Version: 0.8.6
|
|
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
|
|
@@ -603,3 +603,33 @@ lifecycles.
|
|
|
603
603
|
The model registry version is an ISO date. When more than one registry ships on
|
|
604
604
|
the same date, append a positive revision such as `2026-09-25.2` so consumers
|
|
605
605
|
can distinguish the policies while pricing validation still uses that date.
|
|
606
|
+
|
|
607
|
+
|
|
608
|
+
## Complete call pricing
|
|
609
|
+
|
|
610
|
+
`price_call_from_response` combines token costs with supported retrieval fees.
|
|
611
|
+
`cost_from_response` remains token-only. Retrieval pricing supports Anthropic
|
|
612
|
+
`web_search` and `web_fetch` on `anthropic-api`; other observed scopes are unresolved.
|
|
613
|
+
|
|
614
|
+
```python
|
|
615
|
+
from metergraph_core import price_call_from_response
|
|
616
|
+
|
|
617
|
+
result = price_call_from_response(
|
|
618
|
+
"anthropic",
|
|
619
|
+
{"type": "message", "model": "claude-sonnet-5-5", "usage": {
|
|
620
|
+
"input_tokens": 1000, "output_tokens": 100,
|
|
621
|
+
"server_tool_use": {"web_search_requests": 2},
|
|
622
|
+
}},
|
|
623
|
+
at="2026-10-20T00:00:00Z",
|
|
624
|
+
)
|
|
625
|
+
assert str(result.cost_usd) == "0.02300000"
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
`cost_usd` is set only when the call is fully priced. `known_subtotal_usd`
|
|
629
|
+
contains exact components; `estimated_total_usd` separates token fallback
|
|
630
|
+
estimates when all retrieval fees are known. Missing charges are reported in
|
|
631
|
+
`missing_dimensions`. Counts must be nonnegative integers.
|
|
632
|
+
|
|
633
|
+
For normalized usage, use `price_call` with `ModelUsage` and `RetrievalUsage`.
|
|
634
|
+
To compose an existing token result from the same catalog, use `compose_call_cost`.
|
|
635
|
+
`resolve_billing` accepts complete-call results for gateway reconciliation.
|
|
@@ -573,3 +573,33 @@ lifecycles.
|
|
|
573
573
|
The model registry version is an ISO date. When more than one registry ships on
|
|
574
574
|
the same date, append a positive revision such as `2026-09-25.2` so consumers
|
|
575
575
|
can distinguish the policies while pricing validation still uses that date.
|
|
576
|
+
|
|
577
|
+
|
|
578
|
+
## Complete call pricing
|
|
579
|
+
|
|
580
|
+
`price_call_from_response` combines token costs with supported retrieval fees.
|
|
581
|
+
`cost_from_response` remains token-only. Retrieval pricing supports Anthropic
|
|
582
|
+
`web_search` and `web_fetch` on `anthropic-api`; other observed scopes are unresolved.
|
|
583
|
+
|
|
584
|
+
```python
|
|
585
|
+
from metergraph_core import price_call_from_response
|
|
586
|
+
|
|
587
|
+
result = price_call_from_response(
|
|
588
|
+
"anthropic",
|
|
589
|
+
{"type": "message", "model": "claude-sonnet-5-5", "usage": {
|
|
590
|
+
"input_tokens": 1000, "output_tokens": 100,
|
|
591
|
+
"server_tool_use": {"web_search_requests": 2},
|
|
592
|
+
}},
|
|
593
|
+
at="2026-10-20T00:00:00Z",
|
|
594
|
+
)
|
|
595
|
+
assert str(result.cost_usd) == "0.02300000"
|
|
596
|
+
```
|
|
597
|
+
|
|
598
|
+
`cost_usd` is set only when the call is fully priced. `known_subtotal_usd`
|
|
599
|
+
contains exact components; `estimated_total_usd` separates token fallback
|
|
600
|
+
estimates when all retrieval fees are known. Missing charges are reported in
|
|
601
|
+
`missing_dimensions`. Counts must be nonnegative integers.
|
|
602
|
+
|
|
603
|
+
For normalized usage, use `price_call` with `ModelUsage` and `RetrievalUsage`.
|
|
604
|
+
To compose an existing token result from the same catalog, use `compose_call_cost`.
|
|
605
|
+
`resolve_billing` accepts complete-call results for gateway reconciliation.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "metergraph-core"
|
|
3
|
-
version = "0.8.
|
|
3
|
+
version = "0.8.6"
|
|
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"
|
|
@@ -155,3 +155,9 @@ __all__ = [
|
|
|
155
155
|
"search_operation_for_channel",
|
|
156
156
|
"validate_model_registry",
|
|
157
157
|
]
|
|
158
|
+
|
|
159
|
+
from .calls import (CallCostResult, RetrievalComponent, RetrievalUsage, compose_call_cost,
|
|
160
|
+
price_call, price_call_from_response, response_retrieval_usage)
|
|
161
|
+
|
|
162
|
+
__all__ += ["CallCostResult", "RetrievalComponent", "RetrievalUsage", "compose_call_cost",
|
|
163
|
+
"price_call", "price_call_from_response", "response_retrieval_usage"]
|
|
@@ -168,7 +168,7 @@ def _disputed(
|
|
|
168
168
|
|
|
169
169
|
|
|
170
170
|
def resolve_billing(
|
|
171
|
-
catalog_result: CostResult,
|
|
171
|
+
catalog_result: "CostResult | CallCostResult",
|
|
172
172
|
evidence: GatewayBillingEvidence,
|
|
173
173
|
*,
|
|
174
174
|
disputes: Iterable[Dispute] = (),
|
|
@@ -231,3 +231,7 @@ def resolve_billing(
|
|
|
231
231
|
catalog_price_id=catalog_result.price_id,
|
|
232
232
|
catalog_reasons=reasons,
|
|
233
233
|
)
|
|
234
|
+
|
|
235
|
+
from typing import TYPE_CHECKING
|
|
236
|
+
if TYPE_CHECKING:
|
|
237
|
+
from .calls import CallCostResult
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"""Compose token and counted retrieval costs from one catalog snapshot."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from decimal import Decimal
|
|
6
|
+
from typing import Any, Mapping, Sequence
|
|
7
|
+
|
|
8
|
+
from .catalog import CostResult, ModelUsage, direct_channel_for_provider, normalize_provider
|
|
9
|
+
from .loader import LoadedCatalog
|
|
10
|
+
from .responses import _as_mapping, _bundled_catalog, _first, cost_from_response, response_format
|
|
11
|
+
|
|
12
|
+
_ZERO = Decimal('0')
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass(frozen=True, slots=True)
|
|
16
|
+
class RetrievalUsage:
|
|
17
|
+
operation: str
|
|
18
|
+
units: Any
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(frozen=True, slots=True)
|
|
22
|
+
class RetrievalComponent:
|
|
23
|
+
operation: str
|
|
24
|
+
units: Any
|
|
25
|
+
cost_usd: Decimal | None
|
|
26
|
+
price_id: str | None
|
|
27
|
+
basis: str
|
|
28
|
+
reasons: tuple[str, ...] = ()
|
|
29
|
+
source_url: str | None = None
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass(frozen=True, slots=True)
|
|
33
|
+
class CallCostResult:
|
|
34
|
+
token_cost: CostResult
|
|
35
|
+
retrieval: tuple[RetrievalComponent, ...]
|
|
36
|
+
cost_usd: Decimal | None
|
|
37
|
+
known_subtotal_usd: Decimal
|
|
38
|
+
estimated_total_usd: Decimal | None
|
|
39
|
+
status: str
|
|
40
|
+
token_basis: str
|
|
41
|
+
missing_dimensions: tuple[str, ...]
|
|
42
|
+
catalog_hash: str
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def publisher(self) -> str | None:
|
|
47
|
+
return self.token_cost.publisher
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def price_id(self) -> str | None:
|
|
51
|
+
return self.token_cost.price_id
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def reasons(self) -> tuple[str, ...]:
|
|
55
|
+
return tuple(dict.fromkeys((*self.token_cost.reasons,
|
|
56
|
+
*(f"missing_{d}" for d in self.missing_dimensions))))
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def compose_call_cost(
|
|
60
|
+
token_cost: CostResult, *, catalog: LoadedCatalog,
|
|
61
|
+
retrieval: Sequence[RetrievalUsage] = (), provider: str | None = None,
|
|
62
|
+
channel: str | None = None, region: str = 'global',
|
|
63
|
+
) -> CallCostResult:
|
|
64
|
+
"""Combine retrieval with a token result from the same catalog and time.
|
|
65
|
+
|
|
66
|
+
Partial token results remain estimates, excluded from the known subtotal.
|
|
67
|
+
"""
|
|
68
|
+
if token_cost.effective_at is None:
|
|
69
|
+
raise ValueError('token_cost must carry its effective_at')
|
|
70
|
+
if token_cost.price is not None:
|
|
71
|
+
selected = catalog.lookup_price(model=token_cost.canonical_model,
|
|
72
|
+
channel=token_cost.price.pricing_channel, region=token_cost.price.region,
|
|
73
|
+
at=token_cost.effective_at)
|
|
74
|
+
if selected.price != token_cost.price:
|
|
75
|
+
raise ValueError('token price does not match the catalog snapshot')
|
|
76
|
+
if len({item.operation for item in retrieval}) != len(retrieval):
|
|
77
|
+
raise ValueError('retrieval operations must be aggregated once per call')
|
|
78
|
+
resolved_channel = token_cost.price.pricing_channel if token_cost.price else (channel or direct_channel_for_provider(provider))
|
|
79
|
+
publisher = token_cost.publisher or normalize_provider(provider or '')
|
|
80
|
+
missing = list(token_cost.missing_dimensions)
|
|
81
|
+
token_basis = ('exact' if token_cost.status == 'priced' else
|
|
82
|
+
'fallback_estimate' if token_cost.cost_usd is not None else 'unknown')
|
|
83
|
+
# Retrieval rates are USD. Do not add them to a different currency.
|
|
84
|
+
currency_ok = token_cost.currency in (None, 'USD')
|
|
85
|
+
known = token_cost.cost_usd if token_basis == 'exact' and currency_ok else _ZERO
|
|
86
|
+
if not currency_ok:
|
|
87
|
+
missing.append('currency')
|
|
88
|
+
token_basis = 'unknown'
|
|
89
|
+
parts = []
|
|
90
|
+
for usage in retrieval:
|
|
91
|
+
if resolved_channel == 'anthropic-api' and publisher == 'anthropic' and usage.operation in {'web_search', 'web_fetch'}:
|
|
92
|
+
fee = catalog.price_retrieval(channel=resolved_channel, operation=usage.operation,
|
|
93
|
+
units=usage.units, at=token_cost.effective_at, region=region)
|
|
94
|
+
part = RetrievalComponent(usage.operation, usage.units, fee.cost_usd,
|
|
95
|
+
fee.price_id, 'exact' if fee.status == 'priced' else 'unknown', fee.reasons, fee.source_url)
|
|
96
|
+
else:
|
|
97
|
+
part = RetrievalComponent(usage.operation, usage.units, None, None,
|
|
98
|
+
'unknown', ('unsupported_retrieval_scope',))
|
|
99
|
+
part = RetrievalComponent(part.operation,
|
|
100
|
+
part.units if type(part.units) is int and part.units >= 0 else None,
|
|
101
|
+
part.cost_usd, part.price_id, part.basis, part.reasons, part.source_url)
|
|
102
|
+
parts.append(part)
|
|
103
|
+
if part.cost_usd is None:
|
|
104
|
+
missing.append(f'retrieval.{usage.operation}')
|
|
105
|
+
else:
|
|
106
|
+
known += part.cost_usd
|
|
107
|
+
unresolved = any(p.basis == 'unknown' for p in parts)
|
|
108
|
+
status = ('unpriced' if token_cost.price is None or token_cost.status == 'unpriced' else
|
|
109
|
+
'partial' if token_basis != 'exact' or unresolved or missing else 'priced')
|
|
110
|
+
estimated = None
|
|
111
|
+
if token_basis == 'fallback_estimate' and not unresolved:
|
|
112
|
+
estimated = token_cost.cost_usd + sum((p.cost_usd or _ZERO for p in parts), _ZERO)
|
|
113
|
+
return CallCostResult(token_cost, tuple(parts), known if status == 'priced' else None,
|
|
114
|
+
known, estimated, status, token_basis, tuple(dict.fromkeys(missing)), catalog.content_hash)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def price_call(
|
|
118
|
+
*, model: Any, usage: ModelUsage, at: Any, catalog: LoadedCatalog | None = None,
|
|
119
|
+
provider: str | None = None, channel: str | None = None, region: str | None = None,
|
|
120
|
+
retrieval: Sequence[RetrievalUsage] = (),
|
|
121
|
+
) -> CallCostResult:
|
|
122
|
+
"""Price normalized token and retrieval usage against one snapshot."""
|
|
123
|
+
loaded = catalog if catalog is not None else _bundled_catalog()
|
|
124
|
+
tokens = loaded.calculate_cost(model=model, usage=usage, at=at, provider=provider,
|
|
125
|
+
channel=channel, region=region)
|
|
126
|
+
return compose_call_cost(tokens, catalog=loaded, retrieval=retrieval, provider=provider,
|
|
127
|
+
channel=channel or direct_channel_for_provider(provider), region=region or 'global')
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def response_retrieval_usage(response: Any, *, format: str | None = None) -> tuple[RetrievalUsage, ...]:
|
|
131
|
+
"""Read operation metadata without reading prompt or response text.
|
|
132
|
+
|
|
133
|
+
Tool evidence without a valid usage counter remains unresolved.
|
|
134
|
+
"""
|
|
135
|
+
data = _as_mapping(response)
|
|
136
|
+
detected = format or response_format(data)
|
|
137
|
+
if detected == 'anthropic.messages':
|
|
138
|
+
usage = data.get('usage')
|
|
139
|
+
usage = usage if isinstance(usage, Mapping) else {}
|
|
140
|
+
server = usage.get('server_tool_use')
|
|
141
|
+
if server is not None and not isinstance(server, Mapping):
|
|
142
|
+
return (RetrievalUsage('web_search', None),)
|
|
143
|
+
server = server or {}
|
|
144
|
+
found = [RetrievalUsage(op, server[key]) for key, op in (
|
|
145
|
+
('web_search_requests', 'web_search'), ('web_fetch_requests', 'web_fetch')) if key in server]
|
|
146
|
+
observed = set()
|
|
147
|
+
content = data.get('content')
|
|
148
|
+
if isinstance(content, list):
|
|
149
|
+
for block in content:
|
|
150
|
+
if isinstance(block, Mapping) and block.get('type') == 'server_tool_use':
|
|
151
|
+
name = block.get('name')
|
|
152
|
+
if name in ('web_search', 'web_fetch'):
|
|
153
|
+
observed.add(name)
|
|
154
|
+
found.extend(RetrievalUsage(op, None) for op in sorted(observed) if op not in {f.operation for f in found})
|
|
155
|
+
return tuple(found)
|
|
156
|
+
if detected == 'gemini.generate_content':
|
|
157
|
+
candidates = data.get('candidates')
|
|
158
|
+
if isinstance(candidates, list):
|
|
159
|
+
for item in candidates:
|
|
160
|
+
metadata = _first(item, 'groundingMetadata', 'grounding_metadata') if isinstance(item, Mapping) else None
|
|
161
|
+
if isinstance(metadata, Mapping) and any(
|
|
162
|
+
_first(metadata, camel, snake) is not None
|
|
163
|
+
for camel, snake in (("webSearchQueries", "web_search_queries"),
|
|
164
|
+
("groundingChunks", "grounding_chunks"))
|
|
165
|
+
):
|
|
166
|
+
# Grounding evidence does not establish billable query count.
|
|
167
|
+
return (RetrievalUsage('google_search_grounding', None),)
|
|
168
|
+
if detected == 'openai.responses':
|
|
169
|
+
output = data.get('output')
|
|
170
|
+
if isinstance(output, list):
|
|
171
|
+
count = sum(1 for item in output if isinstance(item, Mapping) and item.get('type') == 'web_search_call')
|
|
172
|
+
if count:
|
|
173
|
+
return (RetrievalUsage('web_search', count),)
|
|
174
|
+
return ()
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
def price_call_from_response(
|
|
178
|
+
provider: str, response: Any, *, catalog: LoadedCatalog | None = None,
|
|
179
|
+
model: str | None = None, at: Any = None, channel: str | None = None,
|
|
180
|
+
region: str | None = None, service_tier: str | None = None, format: str | None = None,
|
|
181
|
+
) -> CallCostResult:
|
|
182
|
+
"""Price a provider response, including supported counted retrieval fees."""
|
|
183
|
+
loaded = catalog if catalog is not None else _bundled_catalog()
|
|
184
|
+
tokens = cost_from_response(provider, response, catalog=loaded, model=model, at=at,
|
|
185
|
+
channel=channel, region=region, service_tier=service_tier, format=format)
|
|
186
|
+
return compose_call_cost(tokens, catalog=loaded,
|
|
187
|
+
retrieval=response_retrieval_usage(response, format=format), provider=provider,
|
|
188
|
+
channel=channel or direct_channel_for_provider(provider), region=region or 'global')
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
version: '2026-10-
|
|
1
|
+
version: '2026-10-29'
|
|
2
2
|
models:
|
|
3
3
|
- canonical_id: openai/gpt-5.6-sol
|
|
4
4
|
display_name: GPT-5.6 Sol
|
|
@@ -888,6 +888,30 @@ models:
|
|
|
888
888
|
execution_profiles:
|
|
889
889
|
- default
|
|
890
890
|
display_name: Kimi K3.1 Pro Highspeed (direct)
|
|
891
|
+
- canonical_id: minimax/minimax-m3.1-flash
|
|
892
|
+
display_name: MiniMax M3.1 Flash
|
|
893
|
+
publisher: minimax
|
|
894
|
+
released: '2026-10-19' # https://www.minimax.io/blog/minimax-m31f
|
|
895
|
+
routes:
|
|
896
|
+
- key: default:minimax/minimax-m3.1-flash
|
|
897
|
+
id: minimax/minimax-m3.1-flash
|
|
898
|
+
provider: vercel
|
|
899
|
+
model_id: minimax/minimax-m3.1-flash
|
|
900
|
+
pricing_channel: vercel-ai-gateway
|
|
901
|
+
execution_profiles:
|
|
902
|
+
- default
|
|
903
|
+
- canonical_id: mistral/mistral-small-4
|
|
904
|
+
display_name: Mistral Small 4
|
|
905
|
+
publisher: mistral
|
|
906
|
+
released: '2026-03-16' # https://docs.mistral.ai/models/mistral-small-4-0-26-03
|
|
907
|
+
routes:
|
|
908
|
+
- key: default:mistral:mistral-small-2603
|
|
909
|
+
id: mistral:mistral-small-2603
|
|
910
|
+
provider: mistral
|
|
911
|
+
model_id: mistral-small-2603
|
|
912
|
+
pricing_channel: mistral-api
|
|
913
|
+
execution_profiles:
|
|
914
|
+
- default
|
|
891
915
|
execution_profiles:
|
|
892
916
|
- id: default
|
|
893
917
|
routes:
|
|
@@ -963,6 +987,8 @@ execution_profiles:
|
|
|
963
987
|
- default:openai:gpt-6.1-terra
|
|
964
988
|
- default:moonshotai:kimi-k3.1-pro
|
|
965
989
|
- default:moonshotai:kimi-k3.1-pro-highspeed
|
|
990
|
+
- default:minimax/minimax-m3.1-flash
|
|
991
|
+
- default:mistral:mistral-small-2603
|
|
966
992
|
- id: bedrock
|
|
967
993
|
routes:
|
|
968
994
|
- bedrock:anthropic/claude-sonnet-5
|
|
@@ -1029,6 +1055,7 @@ offer_groups:
|
|
|
1029
1055
|
- default:deepseek/deepseek-v4.1-flash-1017
|
|
1030
1056
|
- default:deepseek/deepseek-v4.1-flash-1017-fast
|
|
1031
1057
|
- default:inception/mercury-3
|
|
1058
|
+
- default:minimax/minimax-m3.1-flash
|
|
1032
1059
|
- id: fireworks
|
|
1033
1060
|
credential: FIREWORKS_API_KEY
|
|
1034
1061
|
routes:
|
|
@@ -1059,6 +1086,10 @@ offer_groups:
|
|
|
1059
1086
|
routes:
|
|
1060
1087
|
- default:moonshotai:kimi-k3.1-pro
|
|
1061
1088
|
- default:moonshotai:kimi-k3.1-pro-highspeed
|
|
1089
|
+
- id: mistral-direct
|
|
1090
|
+
credential: MISTRAL_API_KEY
|
|
1091
|
+
routes:
|
|
1092
|
+
- default:mistral:mistral-small-2603
|
|
1062
1093
|
- id: bedrock
|
|
1063
1094
|
credential: null
|
|
1064
1095
|
routes:
|
|
@@ -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-29"
|
|
9
|
+
version: "2026-10-29.3"
|
|
10
10
|
currency: USD
|
|
11
|
-
pricing_verified_at: "2026-10-
|
|
11
|
+
pricing_verified_at: "2026-10-29"
|
|
12
12
|
models:
|
|
13
13
|
- canonical_id: openai/gpt-6-sol
|
|
14
14
|
publisher: openai
|
|
@@ -1579,6 +1579,7 @@ models:
|
|
|
1579
1579
|
- channel: anthropic-api
|
|
1580
1580
|
region: global
|
|
1581
1581
|
effective_from: "2026-10-07"
|
|
1582
|
+
effective_to: "2026-10-29"
|
|
1582
1583
|
input_per_mtok: 2.00
|
|
1583
1584
|
output_per_mtok: 10.00
|
|
1584
1585
|
cache_read_per_mtok: 0.10
|
|
@@ -1587,6 +1588,17 @@ models:
|
|
|
1587
1588
|
batch_input_per_mtok: 1.00
|
|
1588
1589
|
batch_output_per_mtok: 5.00
|
|
1589
1590
|
source_url: https://www.anthropic.com/claude-haiku-5-5
|
|
1591
|
+
- channel: anthropic-api
|
|
1592
|
+
region: global
|
|
1593
|
+
effective_from: "2026-10-29"
|
|
1594
|
+
input_per_mtok: 2.00
|
|
1595
|
+
output_per_mtok: 10.00
|
|
1596
|
+
cache_read_per_mtok: 0.10
|
|
1597
|
+
cache_write_5m_per_mtok: 2.50
|
|
1598
|
+
cache_write_1h_per_mtok: 4.00
|
|
1599
|
+
batch_input_per_mtok: 1.00
|
|
1600
|
+
batch_output_per_mtok: 5.00
|
|
1601
|
+
source_url: https://platform.claude.com/docs/en/about-claude/pricing
|
|
1590
1602
|
- channel: vercel-ai-gateway
|
|
1591
1603
|
region: global
|
|
1592
1604
|
effective_from: "2026-10-07"
|
|
@@ -2436,12 +2448,24 @@ models:
|
|
|
2436
2448
|
- channel: mistral-api
|
|
2437
2449
|
region: global
|
|
2438
2450
|
effective_from: "2026-10-20"
|
|
2451
|
+
effective_to: "2026-10-29"
|
|
2439
2452
|
input_per_mtok: 1.36
|
|
2440
2453
|
output_per_mtok: 4.18
|
|
2441
2454
|
cache_read_per_mtok: 0.14
|
|
2442
2455
|
batch_input_per_mtok: 0.68
|
|
2443
2456
|
batch_output_per_mtok: 2.09
|
|
2444
2457
|
source_url: https://docs.mistral.ai/models/mistral-large
|
|
2458
|
+
# Batch input/output use the documented 50% discount on all models:
|
|
2459
|
+
# https://docs.mistral.ai/studio/batch-processing
|
|
2460
|
+
- channel: mistral-api
|
|
2461
|
+
region: global
|
|
2462
|
+
effective_from: "2026-10-29"
|
|
2463
|
+
input_per_mtok: 0.70
|
|
2464
|
+
output_per_mtok: 2.10
|
|
2465
|
+
cache_read_per_mtok: 0.07
|
|
2466
|
+
batch_input_per_mtok: 0.35
|
|
2467
|
+
batch_output_per_mtok: 1.05
|
|
2468
|
+
source_url: https://docs.mistral.ai/models/mistral-large
|
|
2445
2469
|
- channel: vercel-ai-gateway
|
|
2446
2470
|
region: global
|
|
2447
2471
|
effective_from: "2026-10-06"
|
|
@@ -2915,6 +2939,9 @@ models:
|
|
|
2915
2939
|
rules:
|
|
2916
2940
|
uncaptured_fees: true
|
|
2917
2941
|
source_url: https://docs.perplexity.ai/docs/getting-started/pricing
|
|
2942
|
+
# The Agent API's model list is a separate product: its Sonar rates,
|
|
2943
|
+
# including its cache read rate, do not price this channel, so the
|
|
2944
|
+
# direct token rates above stay the last known ones.
|
|
2918
2945
|
- channel: vercel-ai-gateway
|
|
2919
2946
|
region: global
|
|
2920
2947
|
effective_from: "2026-10-11"
|
|
@@ -3661,10 +3688,22 @@ models:
|
|
|
3661
3688
|
- channel: moonshot-api
|
|
3662
3689
|
region: global
|
|
3663
3690
|
effective_from: "2026-07-16"
|
|
3691
|
+
effective_to: "2026-10-29"
|
|
3664
3692
|
input_per_mtok: 3.0
|
|
3665
3693
|
output_per_mtok: 15.0
|
|
3666
3694
|
cache_read_per_mtok: 0.3
|
|
3667
3695
|
source_url: https://platform.kimi.ai/docs/pricing/chat
|
|
3696
|
+
# The platform's cache-write figure is stored for the default 5m TTL;
|
|
3697
|
+
# the separately billed 1h tier remains unknown.
|
|
3698
|
+
- channel: moonshot-api
|
|
3699
|
+
region: global
|
|
3700
|
+
effective_from: "2026-10-29"
|
|
3701
|
+
input_per_mtok: 3.0
|
|
3702
|
+
output_per_mtok: 15.0
|
|
3703
|
+
cache_read_per_mtok: 0.3
|
|
3704
|
+
cache_write_5m_per_mtok: 3.0
|
|
3705
|
+
rules: {input_includes_cache_read: true, input_includes_cache_write: true}
|
|
3706
|
+
source_url: https://platform.kimi.ai/
|
|
3668
3707
|
- channel: fireworks-api
|
|
3669
3708
|
region: global
|
|
3670
3709
|
effective_from: "2026-07-19"
|
|
@@ -4574,6 +4613,22 @@ models:
|
|
|
4574
4613
|
- channel: google-api
|
|
4575
4614
|
region: global
|
|
4576
4615
|
effective_from: "2026-08-27"
|
|
4616
|
+
effective_to: "2026-10-28"
|
|
4617
|
+
input_per_mtok: 1.50
|
|
4618
|
+
output_per_mtok: 17.50
|
|
4619
|
+
source_url: https://ai.google.dev/gemini-api/docs/pricing
|
|
4620
|
+
- channel: google-api
|
|
4621
|
+
region: global
|
|
4622
|
+
effective_from: "2026-10-28"
|
|
4623
|
+
effective_to: "2026-10-29"
|
|
4624
|
+
# Reconcile the already-published live interval; this was an automated
|
|
4625
|
+
# text-rate misclassification, corrected by the successor below.
|
|
4626
|
+
input_per_mtok: 1.5
|
|
4627
|
+
output_per_mtok: 9
|
|
4628
|
+
source_url: https://ai.google.dev/gemini-api/docs/pricing
|
|
4629
|
+
- channel: google-api
|
|
4630
|
+
region: global
|
|
4631
|
+
effective_from: "2026-10-29"
|
|
4577
4632
|
input_per_mtok: 1.50
|
|
4578
4633
|
output_per_mtok: 17.50
|
|
4579
4634
|
source_url: https://ai.google.dev/gemini-api/docs/pricing
|
|
@@ -5085,6 +5140,7 @@ models:
|
|
|
5085
5140
|
- channel: anthropic-api
|
|
5086
5141
|
region: global
|
|
5087
5142
|
effective_from: "2026-06-09"
|
|
5143
|
+
effective_to: "2026-10-29"
|
|
5088
5144
|
input_per_mtok: 10
|
|
5089
5145
|
output_per_mtok: 50
|
|
5090
5146
|
cache_read_per_mtok: 1
|
|
@@ -5093,6 +5149,17 @@ models:
|
|
|
5093
5149
|
batch_input_per_mtok: 5
|
|
5094
5150
|
batch_output_per_mtok: 25
|
|
5095
5151
|
source_url: https://ai-gateway.vercel.sh/v1/models
|
|
5152
|
+
- channel: anthropic-api
|
|
5153
|
+
region: global
|
|
5154
|
+
effective_from: "2026-10-29"
|
|
5155
|
+
input_per_mtok: 10
|
|
5156
|
+
output_per_mtok: 50
|
|
5157
|
+
cache_read_per_mtok: 1
|
|
5158
|
+
cache_write_5m_per_mtok: 12.5
|
|
5159
|
+
cache_write_1h_per_mtok: 20
|
|
5160
|
+
batch_input_per_mtok: 5
|
|
5161
|
+
batch_output_per_mtok: 25
|
|
5162
|
+
source_url: https://platform.claude.com/docs/en/about-claude/pricing
|
|
5096
5163
|
- canonical_id: anthropic/claude-opus-4
|
|
5097
5164
|
publisher: anthropic
|
|
5098
5165
|
aliases:
|
|
@@ -5113,6 +5180,7 @@ models:
|
|
|
5113
5180
|
- channel: anthropic-api
|
|
5114
5181
|
region: global
|
|
5115
5182
|
effective_from: "2025-05-14"
|
|
5183
|
+
effective_to: "2026-10-29"
|
|
5116
5184
|
input_per_mtok: 15
|
|
5117
5185
|
output_per_mtok: 75
|
|
5118
5186
|
cache_read_per_mtok: 1.5
|
|
@@ -5121,6 +5189,17 @@ models:
|
|
|
5121
5189
|
batch_input_per_mtok: 7.5
|
|
5122
5190
|
batch_output_per_mtok: 37.5
|
|
5123
5191
|
source_url: https://ai-gateway.vercel.sh/v1/models
|
|
5192
|
+
- channel: anthropic-api
|
|
5193
|
+
region: global
|
|
5194
|
+
effective_from: "2026-10-29"
|
|
5195
|
+
input_per_mtok: 15
|
|
5196
|
+
output_per_mtok: 75
|
|
5197
|
+
cache_read_per_mtok: 1.5
|
|
5198
|
+
cache_write_5m_per_mtok: 18.75
|
|
5199
|
+
cache_write_1h_per_mtok: 30
|
|
5200
|
+
batch_input_per_mtok: 7.5
|
|
5201
|
+
batch_output_per_mtok: 37.5
|
|
5202
|
+
source_url: https://platform.claude.com/docs/en/about-claude/pricing
|
|
5124
5203
|
- channel: vercel-ai-gateway
|
|
5125
5204
|
region: global
|
|
5126
5205
|
effective_from: "2026-10-02"
|
|
@@ -5148,6 +5227,7 @@ models:
|
|
|
5148
5227
|
- channel: anthropic-api
|
|
5149
5228
|
region: global
|
|
5150
5229
|
effective_from: "2025-08-05"
|
|
5230
|
+
effective_to: "2026-10-29"
|
|
5151
5231
|
input_per_mtok: 15
|
|
5152
5232
|
output_per_mtok: 75
|
|
5153
5233
|
cache_read_per_mtok: 1.5
|
|
@@ -5156,6 +5236,17 @@ models:
|
|
|
5156
5236
|
batch_input_per_mtok: 7.5
|
|
5157
5237
|
batch_output_per_mtok: 37.5
|
|
5158
5238
|
source_url: https://ai-gateway.vercel.sh/v1/models
|
|
5239
|
+
- channel: anthropic-api
|
|
5240
|
+
region: global
|
|
5241
|
+
effective_from: "2026-10-29"
|
|
5242
|
+
input_per_mtok: 15
|
|
5243
|
+
output_per_mtok: 75
|
|
5244
|
+
cache_read_per_mtok: 1.5
|
|
5245
|
+
cache_write_5m_per_mtok: 18.75
|
|
5246
|
+
cache_write_1h_per_mtok: 30
|
|
5247
|
+
batch_input_per_mtok: 7.5
|
|
5248
|
+
batch_output_per_mtok: 37.5
|
|
5249
|
+
source_url: https://platform.claude.com/docs/en/about-claude/pricing
|
|
5159
5250
|
- canonical_id: anthropic/claude-opus-4.5
|
|
5160
5251
|
publisher: anthropic
|
|
5161
5252
|
aliases:
|
|
@@ -5166,6 +5257,7 @@ models:
|
|
|
5166
5257
|
- channel: anthropic-api
|
|
5167
5258
|
region: global
|
|
5168
5259
|
effective_from: "2025-11-01"
|
|
5260
|
+
effective_to: "2026-10-29"
|
|
5169
5261
|
input_per_mtok: 5
|
|
5170
5262
|
output_per_mtok: 25
|
|
5171
5263
|
cache_read_per_mtok: 0.5
|
|
@@ -5174,6 +5266,17 @@ models:
|
|
|
5174
5266
|
batch_input_per_mtok: 2.5
|
|
5175
5267
|
batch_output_per_mtok: 12.5
|
|
5176
5268
|
source_url: https://ai-gateway.vercel.sh/v1/models
|
|
5269
|
+
- channel: anthropic-api
|
|
5270
|
+
region: global
|
|
5271
|
+
effective_from: "2026-10-29"
|
|
5272
|
+
input_per_mtok: 5
|
|
5273
|
+
output_per_mtok: 25
|
|
5274
|
+
cache_read_per_mtok: 0.5
|
|
5275
|
+
cache_write_5m_per_mtok: 6.25
|
|
5276
|
+
cache_write_1h_per_mtok: 10
|
|
5277
|
+
batch_input_per_mtok: 2.5
|
|
5278
|
+
batch_output_per_mtok: 12.5
|
|
5279
|
+
source_url: https://platform.claude.com/docs/en/about-claude/pricing
|
|
5177
5280
|
- channel: vercel-ai-gateway
|
|
5178
5281
|
region: global
|
|
5179
5282
|
effective_from: "2025-11-01"
|
|
@@ -5236,6 +5339,7 @@ models:
|
|
|
5236
5339
|
- channel: anthropic-api
|
|
5237
5340
|
region: global
|
|
5238
5341
|
effective_from: "2025-05-14"
|
|
5342
|
+
effective_to: "2026-10-29"
|
|
5239
5343
|
input_per_mtok: 3
|
|
5240
5344
|
output_per_mtok: 15
|
|
5241
5345
|
cache_read_per_mtok: 0.3
|
|
@@ -5244,6 +5348,17 @@ models:
|
|
|
5244
5348
|
batch_input_per_mtok: 1.5
|
|
5245
5349
|
batch_output_per_mtok: 7.5
|
|
5246
5350
|
source_url: https://ai-gateway.vercel.sh/v1/models
|
|
5351
|
+
- channel: anthropic-api
|
|
5352
|
+
region: global
|
|
5353
|
+
effective_from: "2026-10-29"
|
|
5354
|
+
input_per_mtok: 3
|
|
5355
|
+
output_per_mtok: 15
|
|
5356
|
+
cache_read_per_mtok: 0.3
|
|
5357
|
+
cache_write_5m_per_mtok: 3.75
|
|
5358
|
+
cache_write_1h_per_mtok: 6
|
|
5359
|
+
batch_input_per_mtok: 1.5
|
|
5360
|
+
batch_output_per_mtok: 7.5
|
|
5361
|
+
source_url: https://platform.claude.com/docs/en/about-claude/pricing
|
|
5247
5362
|
- channel: vercel-ai-gateway
|
|
5248
5363
|
region: global
|
|
5249
5364
|
effective_from: "2025-05-14"
|
|
@@ -5416,6 +5531,9 @@ models:
|
|
|
5416
5531
|
input_per_mtok: 0.15
|
|
5417
5532
|
output_per_mtok: 0.6
|
|
5418
5533
|
cache_read_per_mtok: 0.015
|
|
5534
|
+
# prompt_tokens includes cached_tokens: https://docs.mistral.ai/studio/conversations/advanced/prompt-caching
|
|
5535
|
+
rules:
|
|
5536
|
+
input_includes_cache_read: true
|
|
5419
5537
|
source_url: https://docs.mistral.ai/inference/pricing
|
|
5420
5538
|
- channel: openrouter-api
|
|
5421
5539
|
region: global
|
|
@@ -5743,12 +5861,22 @@ models:
|
|
|
5743
5861
|
- channel: xai-api
|
|
5744
5862
|
region: global
|
|
5745
5863
|
effective_from: "2026-03-10"
|
|
5864
|
+
effective_to: "2026-10-29"
|
|
5746
5865
|
input_per_mtok: 1.25
|
|
5747
5866
|
output_per_mtok: 2.5
|
|
5748
5867
|
cache_read_per_mtok: 0.2
|
|
5749
5868
|
rules:
|
|
5750
5869
|
long_context: {threshold: 200000, input_multiplier: 2.0, output_multiplier: 2.0}
|
|
5751
5870
|
source_url: https://ai-gateway.vercel.sh/v1/models
|
|
5871
|
+
- channel: xai-api
|
|
5872
|
+
region: global
|
|
5873
|
+
effective_from: "2026-10-29"
|
|
5874
|
+
input_per_mtok: 1.25
|
|
5875
|
+
output_per_mtok: 2.5
|
|
5876
|
+
cache_read_per_mtok: 0.2
|
|
5877
|
+
rules:
|
|
5878
|
+
long_context: {threshold: 200000, input_multiplier: 2.0, output_multiplier: 2.0}
|
|
5879
|
+
source_url: https://docs.x.ai/developers/models
|
|
5752
5880
|
- canonical_id: xai/grok-4.20-reasoning
|
|
5753
5881
|
publisher: xai
|
|
5754
5882
|
aliases:
|
|
@@ -5781,12 +5909,22 @@ models:
|
|
|
5781
5909
|
- channel: xai-api
|
|
5782
5910
|
region: global
|
|
5783
5911
|
effective_from: "2026-03-10"
|
|
5912
|
+
effective_to: "2026-10-29"
|
|
5784
5913
|
input_per_mtok: 1.25
|
|
5785
5914
|
output_per_mtok: 2.5
|
|
5786
5915
|
cache_read_per_mtok: 0.2
|
|
5787
5916
|
rules:
|
|
5788
5917
|
long_context: {threshold: 200000, input_multiplier: 2.0, output_multiplier: 2.0}
|
|
5789
5918
|
source_url: https://ai-gateway.vercel.sh/v1/models
|
|
5919
|
+
- channel: xai-api
|
|
5920
|
+
region: global
|
|
5921
|
+
effective_from: "2026-10-29"
|
|
5922
|
+
input_per_mtok: 1.25
|
|
5923
|
+
output_per_mtok: 2.5
|
|
5924
|
+
cache_read_per_mtok: 0.2
|
|
5925
|
+
rules:
|
|
5926
|
+
long_context: {threshold: 200000, input_multiplier: 2.0, output_multiplier: 2.0}
|
|
5927
|
+
source_url: https://docs.x.ai/developers/models
|
|
5790
5928
|
- channel: openrouter-api
|
|
5791
5929
|
region: global
|
|
5792
5930
|
effective_from: "2026-10-29"
|
|
@@ -6147,6 +6285,10 @@ models:
|
|
|
6147
6285
|
alias: minimax/minimax-m3.1-flash
|
|
6148
6286
|
channel: vercel-ai-gateway
|
|
6149
6287
|
rules: {input_includes_cache_read: true}
|
|
6288
|
+
- provider: vercel
|
|
6289
|
+
alias: minimax/minimax-m3.1-flash
|
|
6290
|
+
channel: vercel-ai-gateway
|
|
6291
|
+
rules: {input_includes_cache_read: true}
|
|
6150
6292
|
prices:
|
|
6151
6293
|
- channel: minimax-api
|
|
6152
6294
|
region: global
|
|
@@ -6321,6 +6463,14 @@ models:
|
|
|
6321
6463
|
input_includes_cache_write: true
|
|
6322
6464
|
effective_from: "2026-11-02"
|
|
6323
6465
|
source_url: https://openrouter.ai/qwen/qwen3-coder-30b-a3b-instruct
|
|
6466
|
+
- provider: openrouter
|
|
6467
|
+
alias: alibaba/qwen3-coder-30b-a3b-instruct
|
|
6468
|
+
channel: openrouter-api
|
|
6469
|
+
effective_from: "2026-11-02"
|
|
6470
|
+
rules:
|
|
6471
|
+
input_includes_cache_read: true
|
|
6472
|
+
input_includes_cache_write: true
|
|
6473
|
+
source_url: https://openrouter.ai/qwen/qwen3-coder-30b-a3b-instruct
|
|
6324
6474
|
prices:
|
|
6325
6475
|
- channel: vercel-ai-gateway
|
|
6326
6476
|
region: global
|
|
@@ -12111,11 +12261,22 @@ models:
|
|
|
12111
12261
|
- channel: openai-api
|
|
12112
12262
|
region: global
|
|
12113
12263
|
effective_from: "2025-06-10"
|
|
12264
|
+
effective_to: "2026-10-29"
|
|
12265
|
+
input_per_mtok: 2
|
|
12266
|
+
output_per_mtok: 8
|
|
12267
|
+
cache_read_per_mtok: 0.50
|
|
12268
|
+
batch_input_per_mtok: 1
|
|
12269
|
+
batch_output_per_mtok: 4
|
|
12270
|
+
source_url: https://developers.openai.com/api/docs/models/o3
|
|
12271
|
+
- channel: openai-api
|
|
12272
|
+
region: global
|
|
12273
|
+
effective_from: "2026-10-29"
|
|
12114
12274
|
input_per_mtok: 2
|
|
12115
12275
|
output_per_mtok: 8
|
|
12116
12276
|
cache_read_per_mtok: 0.50
|
|
12117
12277
|
batch_input_per_mtok: 1
|
|
12118
12278
|
batch_output_per_mtok: 4
|
|
12279
|
+
batch_cache_read_per_mtok: 0.25
|
|
12119
12280
|
source_url: https://developers.openai.com/api/docs/models/o3
|
|
12120
12281
|
- channel: vercel-ai-gateway
|
|
12121
12282
|
region: global
|
|
@@ -215,7 +215,7 @@ def parse_google(html_text: str, *, duplicates: list[str] | None = None) -> Tabl
|
|
|
215
215
|
the one in effect; per-image output is not a token rate. A label may name
|
|
216
216
|
the media it covers, as in "Input price (text, image, video)"."""
|
|
217
217
|
parts = page_text(html_text).split("Try it in Google AI Studio")
|
|
218
|
-
tier = r"(?:Free of charge \| |Not available \| )?([^|]*\$[^|]*)"
|
|
218
|
+
tier = r"(?:Free of charge \| |Not available \| )?([^|]*\$[^|]*(?:\| \$[^|]*)*)"
|
|
219
219
|
table: Table = {}
|
|
220
220
|
for before, block in zip(parts, parts[1:]):
|
|
221
221
|
ids = _google_ids([c.strip() for c in before.split("|") if c.strip()])
|
|
@@ -226,7 +226,12 @@ def parse_google(html_text: str, *, duplicates: list[str] | None = None) -> Tabl
|
|
|
226
226
|
if not (inp and out):
|
|
227
227
|
continue
|
|
228
228
|
cache = re.search(r"Context caching price[^|]*\| " + tier, block)
|
|
229
|
-
|
|
229
|
+
# A scalar output rate cannot express separate text/video/audio prices.
|
|
230
|
+
# Leave it missing so refresh escalates rather than selecting the first
|
|
231
|
+
# modality and overwriting a manually verified catalog rate.
|
|
232
|
+
output_cell = out.group(1)
|
|
233
|
+
modalities = set(re.findall(r"\((text|video|audio)\)", output_cell, re.I))
|
|
234
|
+
output = None if "per image" in output_cell or len(modalities) > 1 else dollars(output_cell)
|
|
230
235
|
rates = {"input": dollars(inp.group(1)), "output": output, "cache_read": dollars(cache.group(1)) if cache else None}
|
|
231
236
|
for model_id in ids:
|
|
232
237
|
_keep_first(table, model_id, {k: v for k, v in rates.items() if v is not None}, duplicates)
|
|
@@ -89,16 +89,25 @@ class LoadedCatalog:
|
|
|
89
89
|
# Gateways whose amount billing declines per publisher; see Dispute.
|
|
90
90
|
disputes: tuple[Dispute, ...] = ()
|
|
91
91
|
|
|
92
|
-
def canonical_model_id(self, provider: str, model_id: str) -> str:
|
|
93
|
-
"""Resolve a
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
92
|
+
def canonical_model_id(self, provider: str, model_id: str, *, at: Any = None) -> str:
|
|
93
|
+
"""Resolve a provider's model name, using ``at`` (default now) for migrations.
|
|
94
|
+
|
|
95
|
+
Unknown or ambiguous names are returned unchanged.
|
|
96
|
+
"""
|
|
97
97
|
if not isinstance(model_id, str) or not isinstance(provider, str):
|
|
98
98
|
return model_id
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
provider = normalize_provider(provider)
|
|
100
|
+
canonical = self.canonical_ids.get((provider, model_id))
|
|
101
|
+
if canonical is not None:
|
|
102
|
+
return canonical
|
|
103
|
+
when = _coerce_datetime(at) if at is not None else datetime.now(timezone.utc)
|
|
104
|
+
matches = {
|
|
105
|
+
alias.canonical_id
|
|
106
|
+
for alias in self.snapshot._aliases.get((provider, model_id.lower()), ())
|
|
107
|
+
if (alias.effective_from is None or alias.effective_from <= when)
|
|
108
|
+
and (alias.effective_to is None or when < alias.effective_to)
|
|
109
|
+
}
|
|
110
|
+
return next(iter(matches)) if len(matches) == 1 else model_id
|
|
102
111
|
|
|
103
112
|
def infer_direct_channel(self, model: Any) -> str | None:
|
|
104
113
|
"""Infer a direct pricing channel only when the catalog is unambiguous."""
|
|
@@ -19,13 +19,14 @@ EXECUTION_PROFILES = frozenset({"default", "bedrock"})
|
|
|
19
19
|
# "moonshotai", the name its catalog aliases carry ("moonshot" is only an
|
|
20
20
|
# input alias the catalog normalizes).
|
|
21
21
|
ROUTE_PROVIDERS = frozenset(
|
|
22
|
-
{"anthropic", "bedrock", "fireworks", "moonshotai", "openai", "vercel"}
|
|
22
|
+
{"anthropic", "bedrock", "fireworks", "mistral", "moonshotai", "openai", "vercel"}
|
|
23
23
|
)
|
|
24
24
|
ROUTE_PROVIDER_CHANNELS = MappingProxyType(
|
|
25
25
|
{
|
|
26
26
|
"anthropic": "anthropic-api",
|
|
27
27
|
"bedrock": "aws-bedrock",
|
|
28
28
|
"fireworks": "fireworks-api",
|
|
29
|
+
"mistral": "mistral-api",
|
|
29
30
|
"moonshotai": "moonshot-api",
|
|
30
31
|
"openai": "openai-api",
|
|
31
32
|
"vercel": "vercel-ai-gateway",
|
|
@@ -38,6 +39,7 @@ OFFER_GROUP_PROFILES = MappingProxyType(
|
|
|
38
39
|
"openai-direct": "default",
|
|
39
40
|
"anthropic-direct": "default",
|
|
40
41
|
"moonshot-direct": "default",
|
|
42
|
+
"mistral-direct": "default",
|
|
41
43
|
"bedrock": "bedrock",
|
|
42
44
|
}
|
|
43
45
|
)
|
|
@@ -48,6 +50,7 @@ OFFER_GROUP_CREDENTIALS = MappingProxyType(
|
|
|
48
50
|
"openai-direct": "OPENAI_API_KEY",
|
|
49
51
|
"anthropic-direct": "ANTHROPIC_API_KEY",
|
|
50
52
|
"moonshot-direct": "MOONSHOT_API_KEY",
|
|
53
|
+
"mistral-direct": "MISTRAL_API_KEY",
|
|
51
54
|
"bedrock": None,
|
|
52
55
|
}
|
|
53
56
|
)
|
|
@@ -58,6 +61,7 @@ OFFER_GROUP_PROVIDERS = MappingProxyType(
|
|
|
58
61
|
"openai-direct": "openai",
|
|
59
62
|
"anthropic-direct": "anthropic",
|
|
60
63
|
"moonshot-direct": "moonshotai",
|
|
64
|
+
"mistral-direct": "mistral",
|
|
61
65
|
"bedrock": "bedrock",
|
|
62
66
|
}
|
|
63
67
|
)
|
|
@@ -475,7 +479,7 @@ def validate_model_registry(
|
|
|
475
479
|
f"{route.id}: model {route.model_id!r} has no pricing channel "
|
|
476
480
|
f"{route.pricing_channel!r}"
|
|
477
481
|
)
|
|
478
|
-
canonical = catalog.canonical_model_id(route.provider, route.model_id)
|
|
482
|
+
canonical = catalog.canonical_model_id(route.provider, route.model_id, at=validation_time)
|
|
479
483
|
if canonical == route.model_id:
|
|
480
484
|
resolved = catalog.snapshot.resolve_price(
|
|
481
485
|
model=route.model_id,
|
|
@@ -52,6 +52,7 @@ class RetrievalCostResult:
|
|
|
52
52
|
cost_usd: Decimal | None
|
|
53
53
|
status: str
|
|
54
54
|
reasons: tuple[str, ...] = ()
|
|
55
|
+
source_url: str | None = None
|
|
55
56
|
|
|
56
57
|
|
|
57
58
|
# The retrieval operation a counted search or fetch is billed as on each
|
|
@@ -231,4 +232,4 @@ class RetrievalCatalog:
|
|
|
231
232
|
cost = (Decimal(count) * price.per_1k_usd / _THOUSAND).quantize(
|
|
232
233
|
_COST_QUANTUM, rounding=ROUND_HALF_UP
|
|
233
234
|
)
|
|
234
|
-
return RetrievalCostResult(price.id, cost, "priced", ())
|
|
235
|
+
return RetrievalCostResult(price.id, cost, "priced", (), price.source_url)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: metergraph-core
|
|
3
|
-
Version: 0.8.
|
|
3
|
+
Version: 0.8.6
|
|
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
|
|
@@ -603,3 +603,33 @@ lifecycles.
|
|
|
603
603
|
The model registry version is an ISO date. When more than one registry ships on
|
|
604
604
|
the same date, append a positive revision such as `2026-09-25.2` so consumers
|
|
605
605
|
can distinguish the policies while pricing validation still uses that date.
|
|
606
|
+
|
|
607
|
+
|
|
608
|
+
## Complete call pricing
|
|
609
|
+
|
|
610
|
+
`price_call_from_response` combines token costs with supported retrieval fees.
|
|
611
|
+
`cost_from_response` remains token-only. Retrieval pricing supports Anthropic
|
|
612
|
+
`web_search` and `web_fetch` on `anthropic-api`; other observed scopes are unresolved.
|
|
613
|
+
|
|
614
|
+
```python
|
|
615
|
+
from metergraph_core import price_call_from_response
|
|
616
|
+
|
|
617
|
+
result = price_call_from_response(
|
|
618
|
+
"anthropic",
|
|
619
|
+
{"type": "message", "model": "claude-sonnet-5-5", "usage": {
|
|
620
|
+
"input_tokens": 1000, "output_tokens": 100,
|
|
621
|
+
"server_tool_use": {"web_search_requests": 2},
|
|
622
|
+
}},
|
|
623
|
+
at="2026-10-20T00:00:00Z",
|
|
624
|
+
)
|
|
625
|
+
assert str(result.cost_usd) == "0.02300000"
|
|
626
|
+
```
|
|
627
|
+
|
|
628
|
+
`cost_usd` is set only when the call is fully priced. `known_subtotal_usd`
|
|
629
|
+
contains exact components; `estimated_total_usd` separates token fallback
|
|
630
|
+
estimates when all retrieval fees are known. Missing charges are reported in
|
|
631
|
+
`missing_dimensions`. Counts must be nonnegative integers.
|
|
632
|
+
|
|
633
|
+
For normalized usage, use `price_call` with `ModelUsage` and `RetrievalUsage`.
|
|
634
|
+
To compose an existing token result from the same catalog, use `compose_call_cost`.
|
|
635
|
+
`resolve_billing` accepts complete-call results for gateway reconciliation.
|
|
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.8.4 → metergraph_core-0.8.6}/src/metergraph_core.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|