Files
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 13:10:45 +08:00

294 lines
11 KiB
Python

"""Per-LLM cost accounting + hard-cap budget enforcement.
Separate input/output token pricing per model (Anthropic, OpenAI, DeepSeek)
— important because most providers charge output tokens 3-5x what they
charge input tokens, so input/output-aggregate pricing under-counts.
The framework wires this in two places:
1. Runner — after each model call, ``CostTracker.add(model, tokens_in, tokens_out)``
2. Pre-flight — ``estimate_run_cost`` gives an upper-bound estimate the
IntegrityGuard can check against ``cost_budget_usd``
If at runtime the cumulative cost exceeds the configured budget, the next
``add`` call raises ``CostBudgetExceeded`` — runner catches and halts the run,
publishes a partial-completion report (not silently overrunning the budget).
Pricing table is a frozen dict in this module. Unknown models raise
``UnknownModel`` rather than silently defaulting to a wrong number — opensre
should know what every model in the benchmark grid costs before running.
Prices below are Feb 2026 published rates; check provider pages before
running any large benchmark since rates change.
"""
from __future__ import annotations
from dataclasses import dataclass, field
from threading import Lock
# --------------------------------------------------------------------------- #
# Pricing table #
# --------------------------------------------------------------------------- #
@dataclass(frozen=True)
class TokenPricing:
"""Per-million-token cost in USD, separate for input vs output."""
input_usd_per_mtok: float
output_usd_per_mtok: float
def cost_for(self, tokens_in: int, tokens_out: int) -> float:
"""USD cost of (tokens_in input + tokens_out output) tokens."""
return (
tokens_in / 1_000_000.0 * self.input_usd_per_mtok
+ tokens_out / 1_000_000.0 * self.output_usd_per_mtok
)
# Published rates as of Feb 2026 per provider documentation. The benchmark's
# model_versions pin is what should appear here — exact snapshots, not family
# names — so the rate matches what the API actually charges.
#
# Anthropic: claude pricing pages
# OpenAI: platform.openai.com/docs/pricing
# DeepSeek: api-docs.deepseek.com/quick_start/pricing
#
# IMPORTANT: verify current rates before any large run. The
# benchmark report should record the rates used.
PRICING_TABLE: dict[str, TokenPricing] = {
# Anthropic Claude family
"claude-sonnet-4-5-20250929": TokenPricing(3.0, 15.0),
"claude-opus-4-7": TokenPricing(15.0, 75.0),
"claude-3-5-haiku-20241022": TokenPricing(0.8, 4.0),
# Claude Haiku 4.5 - used as the toolcall model for claude-4-sonnet and
# claude-4-opus specs in llm_dispatch.py. Anthropic published pricing
# at $1/MTok input, $5/MTok output. Verify before any large run.
"claude-haiku-4-5-20251001": TokenPricing(1.0, 5.0),
# OpenAI GPT family
"gpt-4o-2024-11-20": TokenPricing(2.5, 10.0),
"gpt-5-2025-08-07": TokenPricing(5.0, 20.0), # approx — verify before run
"gpt-4o-mini-2024-07-18": TokenPricing(0.15, 0.60),
# DeepSeek
"deepseek-chat-v3.2": TokenPricing(0.27, 1.10),
}
# --------------------------------------------------------------------------- #
# Errors #
# --------------------------------------------------------------------------- #
class UnknownModel(KeyError):
"""Raised when asked to price a model not in PRICING_TABLE.
Honest-results discipline: don't silently default to a wrong number.
Force the user to register pricing for the model they're running.
"""
def __init__(self, model: str) -> None:
super().__init__(
f"No pricing for model {model!r}. Known models: "
f"{sorted(PRICING_TABLE.keys())}. "
f"Add a TokenPricing entry to PRICING_TABLE or call register_pricing()."
)
self.model = model
class CostBudgetExceeded(RuntimeError):
"""Raised when adding a call would exceed the configured budget.
The CostTracker raises this BEFORE recording the call that would exceed
— so the run can halt cleanly without partial-state confusion.
"""
def __init__(self, current_usd: float, budget_usd: float, would_add_usd: float) -> None:
super().__init__(
f"Cost budget ${budget_usd:.2f} would be exceeded: "
f"current ${current_usd:.2f} + ${would_add_usd:.2f} = "
f"${current_usd + would_add_usd:.2f}. Halting run."
)
self.current_usd = current_usd
self.budget_usd = budget_usd
self.would_add_usd = would_add_usd
# --------------------------------------------------------------------------- #
# Lookup + registration #
# --------------------------------------------------------------------------- #
def lookup_pricing(model: str) -> TokenPricing:
"""Return pricing for ``model`` or raise UnknownModel."""
pricing = PRICING_TABLE.get(model)
if pricing is None:
raise UnknownModel(model)
return pricing
def register_pricing(model: str, pricing: TokenPricing) -> None:
"""Add or override pricing for a model.
Use sparingly — committed PRICING_TABLE entries are the source of truth
for benchmark reproducibility. Runtime registration is for one-off
exploration; production runs should add the entry to the table and
commit it.
"""
PRICING_TABLE[model] = pricing
def compute_run_cost(model: str, tokens_in: int, tokens_out: int) -> float:
"""Pure function: USD cost of (tokens_in + tokens_out) for the model."""
return lookup_pricing(model).cost_for(tokens_in, tokens_out)
# --------------------------------------------------------------------------- #
# CostTracker — accumulates costs and enforces budget #
# --------------------------------------------------------------------------- #
@dataclass
class ModelUsage:
"""Per-model token + cost subtotals."""
tokens_in: int = 0
tokens_out: int = 0
cost_usd: float = 0.0
call_count: int = 0
class CostTracker:
"""Aggregates costs across many model calls; enforces a hard budget cap.
Thread-safe — callable from the framework runner's parallel worker pool.
"""
def __init__(self, budget_usd: float) -> None:
if budget_usd <= 0:
raise ValueError(f"budget_usd must be positive, got {budget_usd}")
self._budget_usd = budget_usd
self._cost_usd = 0.0
self._tokens_in = 0
self._tokens_out = 0
self._call_count = 0
self._by_model: dict[str, ModelUsage] = {}
self._lock = Lock()
# ----------------------------------------------------------------------- #
# Public API #
# ----------------------------------------------------------------------- #
def add(self, model: str, tokens_in: int, tokens_out: int) -> float:
"""Record one model call.
Raises ``CostBudgetExceeded`` BEFORE recording if the new cost
would exceed budget. Returns the cost of this call in USD.
"""
if tokens_in < 0 or tokens_out < 0:
raise ValueError(f"tokens must be non-negative; got in={tokens_in} out={tokens_out}")
call_cost = compute_run_cost(model, tokens_in, tokens_out)
with self._lock:
if self._cost_usd + call_cost > self._budget_usd:
raise CostBudgetExceeded(
current_usd=self._cost_usd,
budget_usd=self._budget_usd,
would_add_usd=call_cost,
)
usage = self._by_model.setdefault(model, ModelUsage())
usage.tokens_in += tokens_in
usage.tokens_out += tokens_out
usage.cost_usd += call_cost
usage.call_count += 1
self._cost_usd += call_cost
self._tokens_in += tokens_in
self._tokens_out += tokens_out
self._call_count += 1
return call_cost
def remaining_usd(self) -> float:
"""Headroom before budget is exhausted."""
with self._lock:
return self._budget_usd - self._cost_usd
def total_cost_usd(self) -> float:
with self._lock:
return self._cost_usd
def by_model(self) -> dict[str, ModelUsage]:
"""Snapshot of per-model usage. Returned dict is a copy."""
with self._lock:
return {model: ModelUsage(**vars(u)) for model, u in self._by_model.items()}
def summary(self) -> dict[str, float | int | dict[str, dict[str, float | int]]]:
"""Machine-readable summary for reporting. Snapshot."""
with self._lock:
return {
"budget_usd": self._budget_usd,
"total_cost_usd": round(self._cost_usd, 4),
"remaining_usd": round(self._budget_usd - self._cost_usd, 4),
"total_tokens_in": self._tokens_in,
"total_tokens_out": self._tokens_out,
"total_calls": self._call_count,
"by_model": {
model: {
"tokens_in": u.tokens_in,
"tokens_out": u.tokens_out,
"cost_usd": round(u.cost_usd, 4),
"call_count": u.call_count,
}
for model, u in self._by_model.items()
},
}
# --------------------------------------------------------------------------- #
# Pre-flight estimator #
# --------------------------------------------------------------------------- #
@dataclass(frozen=True)
class RunSizeEstimate:
"""User-supplied estimate of one cell's token cost; multiplied by grid size."""
estimated_tokens_in_per_run: int
estimated_tokens_out_per_run: int
cell_count: int
runs_per_cell: int = 1
@property
def total_runs(self) -> int:
return self.cell_count * self.runs_per_cell
@dataclass(frozen=True)
class RunCostEstimate:
"""What ``estimate_run_cost`` returns: enough detail to decide go/no-go."""
upper_bound_usd: float
per_model_upper_bound_usd: dict[str, float] = field(default_factory=dict)
def estimate_run_cost(
models: list[str],
size: RunSizeEstimate,
) -> RunCostEstimate:
"""Upper-bound cost estimate for a benchmark run.
Assumes the worst case where EVERY model would handle EVERY run with
the estimated tokens. Real runs split between models, so this is a
safe upper bound for budget pre-flight.
"""
per_model: dict[str, float] = {}
for model in models:
per_run_cost = compute_run_cost(
model,
size.estimated_tokens_in_per_run,
size.estimated_tokens_out_per_run,
)
per_model[model] = per_run_cost * size.total_runs
return RunCostEstimate(
upper_bound_usd=sum(per_model.values()),
per_model_upper_bound_usd=per_model,
)