gate.cost: Spend correlation#
Correlate private model request IDs with explicit-confidence spend records from a LiteLLM gateway. Only exact request identifiers cross the local ledger-to-gateway boundary. The result distinguishes provider-billed, gateway-calculated, estimated, local-zero-marginal, and unavailable costs.
Data models are frozen dataclasses; monetary amounts are decimal.Decimal.
Exceptions#
- exception gate.cost.CostLookupError#
A bounded spend lookup failed without exposing transport or secret details.
Session interface#
- class gate.cost.SessionRecord(*args, **kwargs)#
Minimal structural session interface needed by cost functions.
The real
SessionRecordlives in the corpus agentlog; we only needmodelsandmodel_request_idsfor cost correlation. Users who have the full session record can pass any object satisfying this protocol.
Spend correlation#
- gate.cost.correlate_session(record: SessionRecord, snapshot: SpendSnapshot) SessionCost#
Correlate one session only when every observed request has an exact spend row.
- Parameters:
record – Structural agent session with private request IDs.
snapshot – Content-free results from exact LiteLLM spend queries.
- Returns:
A complete gateway-calculated total, a local-zero-marginal result, or an unavailable result with coverage counts. Partial numeric totals are discarded.
- gate.cost.approximate_session_cost(record: SessionRecord, window: SpendWindow, *, buffer_minutes: int = 5, model_min_score: float = 0.3) SessionCost#
Correlate a session to gateway spend by time window + model proximity.
Matches every gateway row whose
start_timefalls within the session’s time span (padded bybuffer_minutes) and whose model label has at leastmodel_min_scoresimilarity to one of the session’s recorded models.Returns an
estimatedcost with observed/matched request coverage. This is the approximate join: not exact (no generation ID), but defensible as an estimate when the exact path is harness-blocked.
- gate.cost.baseline_session_cost(record: SessionRecord) SessionCost#
Return the best content-free cost known without any network access.
Return an explicitly unavailable cost without inventing a numeric estimate.
Data models#
- class gate.cost.SpendRecord(request_id: str, spend: Decimal = Decimal('0'))#
The only LiteLLM spend-log fields permitted past the network adapter.
- class gate.cost.SpendSnapshot(requested_ids: set[str] = <factory>, rows: list[SpendRecord] = <factory>)#
Exact request IDs queried and the content-free rows returned for them.
- class gate.cost.SessionCost(status: 'billed' | 'gateway_calculated' | 'estimated' | 'local_no_marginal_cost' | 'local_energy' | 'unavailable' = 'unavailable', amount_microusd: int | None = None, currency: 'USD' = 'USD', source: 'none' | 'litellm' | 'local-vllm' | 'openrouter' | 'openai' | 'anthropic' | 'modal' = 'none', observed_requests: int = 0, matched_requests: int = 0)#
One session’s cost with provenance and request-level coverage.
- class gate.cost.GatewayRow(start_time: datetime, request_id: str = '', model: str = '', total_tokens: int = 0, spend: Decimal = Decimal('0'))#
A content-free LiteLLM spend row for time-window approximate correlation.
Only the fields needed for time + model + token approximate joins survive; request_id is kept for dedup but never used as an exact join key here.
- class gate.cost.SpendWindow(rows: list[GatewayRow] = <factory>, start: datetime | None = None, end: datetime | None = None, count: int = 0, total_spend: Decimal = Decimal('0'))#
A bounded time window of content-free gateway rows for approximate cost join.
- rows#
All gateway spend rows within the window (no request-level access).
- Type:
Spend API#
- gate.cost.fetch_spend_snapshot(request_ids: Sequence[str], *, base_url: str, token: str, max_requests: int = 256, timeout: float = 5.0) SpendSnapshot#
Fetch exact LiteLLM spend rows with a bounded, caller-supplied scoped key.
The token should be a LiteLLM virtual key restricted to spend-read routes. The adapter intentionally avoids the gateway master key and never performs an unbounded all-history scan.
- Parameters:
request_ids – Opaque IDs captured from local agent response metadata.
base_url – LiteLLM gateway root, such as
http://127.0.0.1:4000.token – A scoped spend-read virtual key.
max_requests – Hard cap on exact HTTP lookups in one attempt.
timeout – Per-request transport timeout in seconds.
- Returns:
The queried IDs and content-free spend rows.
- Raises:
CostLookupError – If bounds, identifiers, response shape, or transport fail.
- gate.cost.fetch_spend_window(since: datetime, until: datetime, *, base_url: str = 'http://localhost:4000', token: str = '') SpendWindow#
Fetch a bounded time window from the LiteLLM gateway (date-granular).
Returns content-free
GatewayRowobjects; raw content-bearing fields (messages, response, api_key, etc.) are dropped at the parse boundary. On any transport/parse failure returns an emptySpendWindow.
- gate.cost.fetch_spend_summary(*, base_url: str, token: str, since: datetime | None = None, until: datetime | None = None) SpendSummary#
Fetch a spend window and return both per-model and per-project aggregations.
- Parameters:
base_url – LiteLLM gateway root, such as
http://127.0.0.1:4000.token – A scoped spend-read virtual key.
since – Window start (defaults to 24 hours ago).
until – Window end (defaults to now).
- Returns:
A SpendSummary with per-model and per-project aggregations.
Energy estimation#
- gate.cost.local_energy_cost(record: SessionRecord, node_name: str, *, electricity_rate_per_kwh: Decimal | None = None) SessionCost#
Compute the electricity cost of a local-inference session.
Delegates to
local_energy()for the canonical energy quantity, then derives USD from it via the electricity rate. Energy is the root; USD is the derived view.This is a usable metric, not an audit-grade one: the wattage is a typical-load estimate from vendor specs, not a measured RAPL/NVML reading, and the rate is the published energy charge before riders. The user can override either input.
- Parameters:
record – The agent session record (uses
activeduration, notwall).node_name – Fleet node name from the compute ledger (e.g.
'salt').electricity_rate_per_kwh – Override the rate; defaults to the Georgia Power seasonal marginal rate for Atlanta 30309.
- Returns:
A
local_energySessionCost with the electricity cost in micro-USD, orunavailableif the node is unknown.
- gate.cost.local_energy(record: SessionRecord, node_name: str) EnergyRecord#
Compute local-inference energy from node wattage x active duration.
This is the time-based model: it uses the node’s typical sustained load wattage (from
NODE_LOAD_WATTS) multiplied by the session’s active duration. It does not reflect batch size, utilization, or model load – it is a coarse proxy. When RAPL/NVML power sampling is available, ameasuredbasis should replace this.- Parameters:
record – The agent session record (uses
activeduration).node_name – Fleet node name (e.g.
'salt').
- Returns:
An
EnergyRecordwith energy in microjoules, orunavailable.
- gate.cost.energy_from_tokens(models: Sequence[str], *, prefill_tokens: int, decode_tokens: int, cache_read_tokens: int) EnergyRecord#
Estimate energy from raw phase-split token counts (no SessionRecord needed).
Exposed for callers – like the quota tracker – that hold a windowed token tally rather than a whole session. The first matching model gives the point estimate; the band spans all matched models.
- Parameters:
models – Model label(s); the first match drives the point estimate.
prefill_tokens – Compute-bound input tokens (uncached input + cache creation).
decode_tokens – Memory-bound generated tokens (output + reasoning).
cache_read_tokens – KV-cache hit tokens.
- Returns:
An
EnergyRecordin microjoules, orunavailableif no coefficients match.
- class gate.cost.EnergyRecord(amount_microjoules: int | None = None, basis: 'measured' | 'node_wattage_time' | 'token_coefficient' | 'unavailable' = 'unavailable', method: 'time' | 'token' | None = None, source: str = '', coverage: float = 0.0, coefficient_version: str = '', amount_microjoules_low: int | None = None, amount_microjoules_high: int | None = None)#
One session’s energy consumption with provenance.
The canonical store is
amount_microjoules(integer, like micro-USD). Joules, watt-hours, and kWh are derived properties. USD and CO2e are further derived views via electricity rate and grid carbon intensity.
- class gate.cost.EnergyCoeff(prefill_mj: int = 0, decode_mj: int = 0, cache_read_mj: int = 0, source: str = '', version: str = '')#
Per-model energy coefficients in microjoules per token, by phase.
Prefill covers input-token processing (compute-bound, better GPU util). Decode covers output-token generation (memory-bound at batch 1). Cache-read covers KV-cache hits (memory read only, ~5-10% of prefill).
Aggregation#
- class gate.cost.ModelSpendSummary(model: str, total_spend: Decimal = Decimal('0'), request_count: int = 0, total_tokens: int = 0, avg_cost_per_request: Decimal = Decimal('0'), first_seen: datetime | None = None, last_seen: datetime | None = None)#
Aggregated spend by model alias.
- class gate.cost.ProjectSpendSummary(project: str, total_spend: Decimal = Decimal('0'), request_count: int = 0, total_tokens: int = 0, avg_cost_per_request: Decimal = Decimal('0'), first_seen: datetime | None = None, last_seen: datetime | None = None, model_breakdown: dict[str, ~decimal.Decimal]=<factory>)#
Aggregated spend by project/metadata.
- class gate.cost.SpendSummary(by_model: dict[str, ~gate.cost.ModelSpendSummary]=<factory>, by_project: dict[str, ~gate.cost.ProjectSpendSummary]=<factory>, total_spend: Decimal = Decimal('0'), total_requests: int = 0, window_start: datetime | None = None, window_end: datetime | None = None)#
High-level spend summary aggregating per-model and per-project views.
- by_model#
Spend aggregated by model alias.
- Type:
- by_project#
Spend aggregated by project/metadata.
- Type:
- gate.cost.aggregate_by_model(rows: list[GatewayRow]) dict[str, ModelSpendSummary]#
Group spend rows by model name into per-model summaries.
- Parameters:
rows – Gateway spend rows to aggregate.
- Returns:
Mapping from model name to ModelSpendSummary.
- gate.cost.aggregate_by_project(rows: list[GatewayRow]) dict[str, ProjectSpendSummary]#
Group spend rows by project/metadata into per-project summaries.
Since GatewayRow does not carry project metadata, all rows are grouped under ‘unknown’. Callers with enriched rows may post-process the result.
- Parameters:
rows – Gateway spend rows to aggregate.
- Returns:
Mapping from project name to ProjectSpendSummary.