4b6817381b
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
CI / coverage-report (push) Has been cancelled
CI / test-kubernetes (push) Has been cancelled
CI / should-run-thorough (push) Has been cancelled
CI / test-thorough (cloudwatch-demo) (push) Has been cancelled
CI / test-thorough (flink-ecs) (push) Has been cancelled
CI / test-thorough (upstream-lambda) (push) Has been cancelled
CI / test-thorough (prefect-ecs-fargate) (push) Has been cancelled
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Has been cancelled
Benchmark image — build + push to ECR (any adapter) / build + push (push) Has been cancelled
CI / quality (ubuntu-latest) (push) Has been cancelled
CI / test (tools-runtime) (push) Has been cancelled
CI / test (e2e-general) (push) Has been cancelled
CI / test (cli-runtime) (push) Has been cancelled
CI / test (e2e-provider-and-openclaw) (push) Has been cancelled
CI / test (integrations-and-misc) (push) Has been cancelled
Release / verify (push) Has been cancelled
Release / build-python-dist (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Has been cancelled
Release / publish-release (push) Has been cancelled
Release / publish-main-release (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Has been cancelled
Release / prepare (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Has been cancelled
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Has been cancelled
463 lines
17 KiB
Python
463 lines
17 KiB
Python
"""Per-model token pricing for the dashboard's ``$/hr`` column.
|
|
|
|
``$/hr`` is a *projected hourly burn rate* derived from the trailing
|
|
60 s usage window, not the actual spend over the last hour. The
|
|
sampler now keeps input/output/cache buckets, so pricing applies the
|
|
right rate to each bucket instead of using the legacy 70/30 blend.
|
|
|
|
The local price table is a vendored snapshot of the ``models.dev``
|
|
catalog for the Claude Code / Codex models this dashboard supports,
|
|
following CodexBar's offline-first approach. Unknown models return
|
|
``None`` so the dashboard renders ``-`` rather than inventing a rate.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
import re
|
|
from dataclasses import dataclass
|
|
from functools import lru_cache
|
|
|
|
from tools.system.fleet_monitoring.meters import TokenUsage
|
|
|
|
#: ``models.dev`` pricing snapshot last refreshed for this vendored table.
|
|
RATES_VERIFIED_AT = "2026-05-17"
|
|
|
|
_USD_PER_M = 1_000_000
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ModelPrice:
|
|
usd_per_input_token: float
|
|
usd_per_output_token: float
|
|
usd_per_cached_input_token: float | None = None
|
|
usd_per_cache_read_input_token: float | None = None
|
|
usd_per_cache_creation_input_token: float | None = None
|
|
|
|
@property
|
|
def cached_input_rate(self) -> float:
|
|
return (
|
|
self.usd_per_cached_input_token
|
|
if self.usd_per_cached_input_token is not None
|
|
else self.cache_read_rate
|
|
)
|
|
|
|
@property
|
|
def cache_read_rate(self) -> float:
|
|
return (
|
|
self.usd_per_cache_read_input_token
|
|
if self.usd_per_cache_read_input_token is not None
|
|
else self.usd_per_input_token
|
|
)
|
|
|
|
@property
|
|
def cache_creation_rate(self) -> float:
|
|
return (
|
|
self.usd_per_cache_creation_input_token
|
|
if self.usd_per_cache_creation_input_token is not None
|
|
else self.usd_per_input_token
|
|
)
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class PriceOverride:
|
|
"""Per-agent rate override loaded from ``agents.yaml``.
|
|
|
|
Overrides are USD per 1M input/output tokens. Cache rates keep the
|
|
base model's ratios when the model is known; for custom unknown
|
|
models they fall back to the effective input rate.
|
|
"""
|
|
|
|
input_usd_per_million: float | None = None
|
|
output_usd_per_million: float | None = None
|
|
|
|
|
|
def _price(
|
|
input_usd_per_million: float,
|
|
output_usd_per_million: float,
|
|
*,
|
|
cache_read_usd_per_million: float | None = None,
|
|
cache_write_usd_per_million: float | None = None,
|
|
) -> ModelPrice:
|
|
input_rate = input_usd_per_million / _USD_PER_M
|
|
cache_read_rate = (
|
|
cache_read_usd_per_million / _USD_PER_M if cache_read_usd_per_million is not None else None
|
|
)
|
|
return ModelPrice(
|
|
usd_per_input_token=input_rate,
|
|
usd_per_output_token=output_usd_per_million / _USD_PER_M,
|
|
usd_per_cached_input_token=cache_read_rate,
|
|
usd_per_cache_read_input_token=cache_read_rate,
|
|
usd_per_cache_creation_input_token=(
|
|
cache_write_usd_per_million / _USD_PER_M
|
|
if cache_write_usd_per_million is not None
|
|
else None
|
|
),
|
|
)
|
|
|
|
|
|
MODEL_PRICES: dict[str, ModelPrice] = {
|
|
# Anthropic / Claude Code. Values are USD per 1M tokens in
|
|
# models.dev; _price converts them to USD per token.
|
|
"claude-3-5-sonnet-20240620": _price(
|
|
3.00, 15.00, cache_read_usd_per_million=0.30, cache_write_usd_per_million=3.75
|
|
),
|
|
"claude-3-5-sonnet-20241022": _price(
|
|
3.00, 15.00, cache_read_usd_per_million=0.30, cache_write_usd_per_million=3.75
|
|
),
|
|
"claude-3-5-haiku-20241022": _price(
|
|
0.80, 4.00, cache_read_usd_per_million=0.08, cache_write_usd_per_million=1.00
|
|
),
|
|
"claude-3-5-haiku-latest": _price(
|
|
0.80, 4.00, cache_read_usd_per_million=0.08, cache_write_usd_per_million=1.00
|
|
),
|
|
"claude-haiku-4-5": _price(
|
|
1.00, 5.00, cache_read_usd_per_million=0.10, cache_write_usd_per_million=1.25
|
|
),
|
|
"claude-haiku-4-5-20251001": _price(
|
|
1.00, 5.00, cache_read_usd_per_million=0.10, cache_write_usd_per_million=1.25
|
|
),
|
|
"claude-sonnet-4": _price(
|
|
3.00, 15.00, cache_read_usd_per_million=0.30, cache_write_usd_per_million=3.75
|
|
),
|
|
"claude-sonnet-4-0": _price(
|
|
3.00, 15.00, cache_read_usd_per_million=0.30, cache_write_usd_per_million=3.75
|
|
),
|
|
"claude-sonnet-4-20250514": _price(
|
|
3.00, 15.00, cache_read_usd_per_million=0.30, cache_write_usd_per_million=3.75
|
|
),
|
|
"claude-sonnet-4-5": _price(
|
|
3.00, 15.00, cache_read_usd_per_million=0.30, cache_write_usd_per_million=3.75
|
|
),
|
|
"claude-sonnet-4-5-20250929": _price(
|
|
3.00, 15.00, cache_read_usd_per_million=0.30, cache_write_usd_per_million=3.75
|
|
),
|
|
"claude-sonnet-4-6": _price(
|
|
3.00, 15.00, cache_read_usd_per_million=0.30, cache_write_usd_per_million=3.75
|
|
),
|
|
"claude-opus-4": _price(
|
|
15.00, 75.00, cache_read_usd_per_million=1.50, cache_write_usd_per_million=18.75
|
|
),
|
|
"claude-opus-4-0": _price(
|
|
15.00, 75.00, cache_read_usd_per_million=1.50, cache_write_usd_per_million=18.75
|
|
),
|
|
"claude-opus-4-20250514": _price(
|
|
15.00, 75.00, cache_read_usd_per_million=1.50, cache_write_usd_per_million=18.75
|
|
),
|
|
"claude-opus-4-1": _price(
|
|
15.00, 75.00, cache_read_usd_per_million=1.50, cache_write_usd_per_million=18.75
|
|
),
|
|
"claude-opus-4-1-20250805": _price(
|
|
15.00, 75.00, cache_read_usd_per_million=1.50, cache_write_usd_per_million=18.75
|
|
),
|
|
"claude-opus-4-5": _price(
|
|
5.00, 25.00, cache_read_usd_per_million=0.50, cache_write_usd_per_million=6.25
|
|
),
|
|
"claude-opus-4-5-20251101": _price(
|
|
5.00, 25.00, cache_read_usd_per_million=0.50, cache_write_usd_per_million=6.25
|
|
),
|
|
"claude-opus-4-6": _price(
|
|
5.00, 25.00, cache_read_usd_per_million=0.50, cache_write_usd_per_million=6.25
|
|
),
|
|
"claude-opus-4-7": _price(
|
|
5.00, 25.00, cache_read_usd_per_million=0.50, cache_write_usd_per_million=6.25
|
|
),
|
|
"claude-fable-5": _price(
|
|
10.00, 50.00, cache_read_usd_per_million=1.00, cache_write_usd_per_million=12.50
|
|
),
|
|
# OpenAI / Codex.
|
|
"gpt-4o": _price(2.50, 10.00, cache_read_usd_per_million=1.25),
|
|
"gpt-4o-2024-05-13": _price(5.00, 15.00),
|
|
"gpt-4o-2024-08-06": _price(2.50, 10.00, cache_read_usd_per_million=1.25),
|
|
"gpt-4o-2024-11-20": _price(2.50, 10.00, cache_read_usd_per_million=1.25),
|
|
"gpt-4o-mini": _price(0.15, 0.60, cache_read_usd_per_million=0.08),
|
|
"gpt-5": _price(1.25, 10.00, cache_read_usd_per_million=0.125),
|
|
"gpt-5-chat-latest": _price(1.25, 10.00),
|
|
"gpt-5-codex": _price(1.25, 10.00, cache_read_usd_per_million=0.125),
|
|
"gpt-5-mini": _price(0.25, 2.00, cache_read_usd_per_million=0.025),
|
|
"gpt-5-nano": _price(0.05, 0.40, cache_read_usd_per_million=0.005),
|
|
"gpt-5-pro": _price(15.00, 120.00),
|
|
"gpt-5.1": _price(1.25, 10.00, cache_read_usd_per_million=0.13),
|
|
"gpt-5.1-chat-latest": _price(1.25, 10.00, cache_read_usd_per_million=0.125),
|
|
"gpt-5.1-codex": _price(1.25, 10.00, cache_read_usd_per_million=0.125),
|
|
"gpt-5.1-codex-max": _price(1.25, 10.00, cache_read_usd_per_million=0.125),
|
|
"gpt-5.1-codex-mini": _price(0.25, 2.00, cache_read_usd_per_million=0.025),
|
|
"gpt-5.2": _price(1.75, 14.00, cache_read_usd_per_million=0.175),
|
|
"gpt-5.2-chat-latest": _price(1.75, 14.00, cache_read_usd_per_million=0.175),
|
|
"gpt-5.2-codex": _price(1.75, 14.00, cache_read_usd_per_million=0.175),
|
|
"gpt-5.2-pro": _price(21.00, 168.00),
|
|
"gpt-5.3-chat-latest": _price(1.75, 14.00, cache_read_usd_per_million=0.175),
|
|
"gpt-5.3-codex": _price(1.75, 14.00, cache_read_usd_per_million=0.175),
|
|
"gpt-5.3-codex-spark": _price(1.75, 14.00, cache_read_usd_per_million=0.175),
|
|
"gpt-5.4": _price(2.50, 15.00, cache_read_usd_per_million=0.25),
|
|
"gpt-5.4-mini": _price(0.75, 4.50, cache_read_usd_per_million=0.075),
|
|
"gpt-5.4-nano": _price(0.20, 1.25, cache_read_usd_per_million=0.02),
|
|
"gpt-5.4-pro": _price(30.00, 180.00),
|
|
"gpt-5.5": _price(5.00, 30.00, cache_read_usd_per_million=0.50),
|
|
"gpt-5.5-pro": _price(30.00, 180.00),
|
|
"gpt-5.6-luna": _price(1.00, 6.00, cache_read_usd_per_million=0.10),
|
|
"gpt-5.6-sol": _price(5.00, 30.00, cache_read_usd_per_million=0.50),
|
|
"gpt-5.6-terra": _price(2.50, 15.00, cache_read_usd_per_million=0.25),
|
|
"o3": _price(2.00, 8.00, cache_read_usd_per_million=0.50),
|
|
"o3-deep-research": _price(10.00, 40.00, cache_read_usd_per_million=2.50),
|
|
"o3-mini": _price(1.10, 4.40, cache_read_usd_per_million=0.55),
|
|
"o3-pro": _price(20.00, 80.00),
|
|
}
|
|
|
|
_UNSORTED_FAMILY_FALLBACKS: tuple[tuple[str, str], ...] = (
|
|
("claude-3-5-sonnet", "claude-3-5-sonnet-20241022"),
|
|
("claude-3-5-haiku", "claude-3-5-haiku-20241022"),
|
|
("claude-haiku-4-5", "claude-haiku-4-5"),
|
|
("claude-fable-5", "claude-fable-5"),
|
|
("claude-sonnet-4-6", "claude-sonnet-4-6"),
|
|
("claude-sonnet-4-5", "claude-sonnet-4-5"),
|
|
("claude-sonnet-4", "claude-sonnet-4"),
|
|
("claude-opus-4-7", "claude-opus-4-7"),
|
|
("claude-opus-4-6", "claude-opus-4-6"),
|
|
("claude-opus-4-5", "claude-opus-4-5"),
|
|
("claude-opus-4-1", "claude-opus-4-1"),
|
|
("claude-opus-4", "claude-opus-4"),
|
|
("gpt-5.3-codex-spark", "gpt-5.3-codex-spark"),
|
|
("gpt-5.3-codex", "gpt-5.3-codex"),
|
|
("gpt-5.2-codex", "gpt-5.2-codex"),
|
|
("gpt-5.1-codex-mini", "gpt-5.1-codex-mini"),
|
|
("gpt-5.1-codex-max", "gpt-5.1-codex-max"),
|
|
("gpt-5.1-codex", "gpt-5.1-codex"),
|
|
("gpt-5-codex", "gpt-5-codex"),
|
|
("gpt-5.6-terra", "gpt-5.6-terra"),
|
|
("gpt-5.6-luna", "gpt-5.6-luna"),
|
|
("gpt-5.6-sol", "gpt-5.6-sol"),
|
|
# OpenAI routes the bare `gpt-5.6` alias to Sol server-side. The three
|
|
# rows above are longer prefixes, so they still win for terra/luna.
|
|
("gpt-5.6", "gpt-5.6-sol"),
|
|
("gpt-5.5-pro", "gpt-5.5-pro"),
|
|
("gpt-5.5", "gpt-5.5"),
|
|
("gpt-5.4-mini", "gpt-5.4-mini"),
|
|
("gpt-5.4-nano", "gpt-5.4-nano"),
|
|
("gpt-5.4-pro", "gpt-5.4-pro"),
|
|
("gpt-5.4", "gpt-5.4"),
|
|
("gpt-5.2-pro", "gpt-5.2-pro"),
|
|
("gpt-5.2", "gpt-5.2"),
|
|
("gpt-5.1", "gpt-5.1"),
|
|
("gpt-5-mini", "gpt-5-mini"),
|
|
("gpt-5-nano", "gpt-5-nano"),
|
|
("gpt-5-pro", "gpt-5-pro"),
|
|
("gpt-5", "gpt-5"),
|
|
("gpt-4o-mini", "gpt-4o-mini"),
|
|
("gpt-4o", "gpt-4o"),
|
|
("o3-deep-research", "o3-deep-research"),
|
|
("o3-mini", "o3-mini"),
|
|
("o3-pro", "o3-pro"),
|
|
("o3", "o3"),
|
|
)
|
|
|
|
# Longest-prefix-first so more specific families win. Build it
|
|
# programmatically so a future edit cannot silently shadow a longer
|
|
# family with its shorter prefix.
|
|
_FAMILY_FALLBACKS: tuple[tuple[str, str], ...] = tuple(
|
|
sorted(_UNSORTED_FAMILY_FALLBACKS, key=lambda item: len(item[0]), reverse=True)
|
|
)
|
|
|
|
|
|
def usd_for_usage(
|
|
usage: TokenUsage,
|
|
model: str | None,
|
|
override: PriceOverride | None = None,
|
|
) -> float | None:
|
|
"""Return USD for a structured usage sample.
|
|
|
|
Codex reports ``cached_input_tokens`` as a discounted subset of
|
|
``input_tokens``. If a future format reports cached input as a
|
|
disjoint counter, clamp to the current convention and log at
|
|
debug level instead of producing a negative non-cached input
|
|
total.
|
|
"""
|
|
price = _resolve_price(model, override)
|
|
if price is None:
|
|
return None
|
|
|
|
input_tokens = max(0.0, usage.input_tokens)
|
|
raw_cached_input_tokens = max(0.0, usage.cached_input_tokens)
|
|
if raw_cached_input_tokens > input_tokens:
|
|
logger.debug(
|
|
"cached_input_tokens exceeded input_tokens; clamping to input total",
|
|
extra={
|
|
"model": model,
|
|
"input_tokens": input_tokens,
|
|
"cached_input_tokens": raw_cached_input_tokens,
|
|
},
|
|
)
|
|
cached_input_tokens = min(raw_cached_input_tokens, input_tokens)
|
|
non_cached_input_tokens = input_tokens - cached_input_tokens
|
|
return (
|
|
non_cached_input_tokens * price.usd_per_input_token
|
|
+ cached_input_tokens * price.cached_input_rate
|
|
+ max(0.0, usage.output_tokens) * price.usd_per_output_token
|
|
+ max(0.0, usage.cache_read_input_tokens) * price.cache_read_rate
|
|
+ max(0.0, usage.cache_creation_input_tokens) * price.cache_creation_rate
|
|
)
|
|
|
|
|
|
def usd_per_hour_for_usage(
|
|
usage_per_min: TokenUsage,
|
|
model: str | None,
|
|
override: PriceOverride | None = None,
|
|
) -> float | None:
|
|
cost_per_min = usd_for_usage(usage_per_min, model, override)
|
|
if cost_per_min is None:
|
|
return None
|
|
return cost_per_min * 60.0
|
|
|
|
|
|
def usd_per_token_blended(model: str | None, override: PriceOverride | None = None) -> float | None:
|
|
price = _resolve_price(model, override)
|
|
if price is None:
|
|
return None
|
|
return 0.7 * price.usd_per_input_token + 0.3 * price.usd_per_output_token
|
|
|
|
|
|
def usd_per_hour(
|
|
tokens_per_min: float,
|
|
model: str | None,
|
|
override: PriceOverride | None = None,
|
|
) -> float | None:
|
|
"""Legacy blended API kept for callers/tests that only have a total."""
|
|
rate = usd_per_token_blended(model, override)
|
|
if rate is None:
|
|
return None
|
|
return tokens_per_min * 60.0 * rate
|
|
|
|
|
|
def normalize_model_name(model: str | None) -> str | None:
|
|
if model is None:
|
|
return None
|
|
candidates = _model_candidates(model)
|
|
for candidate in candidates:
|
|
if candidate in MODEL_PRICES:
|
|
return candidate
|
|
for candidate in candidates:
|
|
for prefix, canonical_id in _FAMILY_FALLBACKS:
|
|
if candidate.startswith(prefix):
|
|
return canonical_id
|
|
return candidates[0] if candidates else None
|
|
|
|
|
|
def _resolve_price(model: str | None, override: PriceOverride | None) -> ModelPrice | None:
|
|
base = _lookup_price(model) if model is not None else None
|
|
if override is None:
|
|
return base
|
|
|
|
input_rate = (
|
|
override.input_usd_per_million / _USD_PER_M
|
|
if override.input_usd_per_million is not None
|
|
else (base.usd_per_input_token if base is not None else None)
|
|
)
|
|
output_rate = (
|
|
override.output_usd_per_million / _USD_PER_M
|
|
if override.output_usd_per_million is not None
|
|
else (base.usd_per_output_token if base is not None else None)
|
|
)
|
|
if input_rate is None or output_rate is None:
|
|
return None
|
|
|
|
return ModelPrice(
|
|
usd_per_input_token=input_rate,
|
|
usd_per_output_token=output_rate,
|
|
usd_per_cached_input_token=_override_related_rate(
|
|
input_rate,
|
|
base.usd_per_cached_input_token if base is not None else None,
|
|
base.usd_per_input_token if base is not None else None,
|
|
),
|
|
usd_per_cache_read_input_token=_override_related_rate(
|
|
input_rate,
|
|
base.usd_per_cache_read_input_token if base is not None else None,
|
|
base.usd_per_input_token if base is not None else None,
|
|
),
|
|
usd_per_cache_creation_input_token=_override_related_rate(
|
|
input_rate,
|
|
base.usd_per_cache_creation_input_token if base is not None else None,
|
|
base.usd_per_input_token if base is not None else None,
|
|
),
|
|
)
|
|
|
|
|
|
def _override_related_rate(
|
|
effective_input_rate: float,
|
|
base_related_rate: float | None,
|
|
base_input_rate: float | None,
|
|
) -> float | None:
|
|
if base_related_rate is None or base_input_rate is None or base_input_rate == 0.0:
|
|
return None
|
|
return effective_input_rate * (base_related_rate / base_input_rate)
|
|
|
|
|
|
def _lookup_price(model: str) -> ModelPrice | None:
|
|
candidates = _model_candidates(model)
|
|
for candidate in candidates:
|
|
direct = MODEL_PRICES.get(candidate)
|
|
if direct is not None:
|
|
return direct
|
|
for candidate in candidates:
|
|
for prefix, canonical_id in _FAMILY_FALLBACKS:
|
|
if candidate.startswith(prefix):
|
|
resolved = MODEL_PRICES.get(canonical_id)
|
|
if resolved is not None:
|
|
return resolved
|
|
return None
|
|
|
|
|
|
@lru_cache(maxsize=512)
|
|
def _model_candidates(raw: str) -> tuple[str, ...]:
|
|
candidates: list[str] = []
|
|
|
|
def append(value: str) -> None:
|
|
normalized = value.strip().lower()
|
|
if normalized and normalized not in candidates:
|
|
candidates.append(normalized)
|
|
|
|
trimmed = raw.strip()
|
|
append(trimmed)
|
|
lower = trimmed.lower()
|
|
for prefix in ("openai/", "anthropic/", "anthropic."):
|
|
if lower.startswith(prefix):
|
|
append(trimmed[len(prefix) :])
|
|
|
|
if "claude-" in lower and "." in trimmed:
|
|
tail = trimmed.rsplit(".", maxsplit=1)[-1]
|
|
if tail.lower().startswith("claude-"):
|
|
append(tail)
|
|
|
|
index = 0
|
|
while index < len(candidates):
|
|
candidate = candidates[index]
|
|
if "@" in candidate:
|
|
base, suffix = candidate.split("@", maxsplit=1)
|
|
append(base)
|
|
if re.fullmatch(r"\d{8}", suffix):
|
|
append(f"{base}-{suffix}")
|
|
elif candidate.startswith("claude-"):
|
|
append(f"{candidate}@default")
|
|
|
|
for pattern in (r"-\d{4}-\d{2}-\d{2}$", r"-\d{8}$", r"-v\d+:\d+$"):
|
|
stripped = re.sub(pattern, "", candidate)
|
|
if stripped != candidate:
|
|
append(stripped)
|
|
index += 1
|
|
|
|
return tuple(candidates)
|
|
|
|
|
|
__all__ = [
|
|
"MODEL_PRICES",
|
|
"ModelPrice",
|
|
"PriceOverride",
|
|
"RATES_VERIFIED_AT",
|
|
"normalize_model_name",
|
|
"usd_for_usage",
|
|
"usd_per_hour",
|
|
"usd_per_hour_for_usage",
|
|
"usd_per_token_blended",
|
|
]
|