05/Observability

Cost Tracking

USD cost reporting, model pricing, and node attribution.


The Costs page answers one narrow question: what did we spend? Quotas, budgets, or blocking on overage are out of scope — this is visibility only.

#Where prices come from

Price rows come from two places: rows the admin enters by hand (`source='manual'`) and a sync of the models.dev catalog (`source='models_dev'`). The sync never touches manually entered rows — a hand-entered price for a model always wins.

Resolving a price for a request narrows from provider-specific to provider-agnostic: it looks up `(provider, model)` first, then falls back to `('', model)` — a manually entered row with no provider_key. If the model name still isn't found, it also checks for a manually entered row under any provider, since the admin may have entered it under a different provider key.

#Billing mode

ModeMeaning
usagePer-token billing; computed from the input/output price per 1M tokens.
flatA subscription/flat-fee node (e.g. Antigravity, Cursor). No extra per-request charge.
localA node running on your own hardware (e.g. Ollama, vLLM). No API fee.
In `flat`/`local` mode the cost is **0**, but only when no manually entered price row exists for that model — when one does, the same rule from above applies: the hand-entered price wins and overrides the node's free mode (this is exactly why Ollama's genuinely billed `:cloud` models need a manual price). With no manual price, 0 means "free," not "unknown." When a model has no price defined at all, the cost stays empty and is NEVER estimated. A blank cell and "$0.00" say different things: one means "no data," the other means "genuinely free."

#Node attribution

A request usually carries `served_base_url`, saying which node served it. But the agent loop makes its own model calls as a SEPARATE self-call, and which upstream that inner request hit never makes it back into the outer activity row. In that case, attribution only happens if exactly one node serves that model — if several nodes serve the same model, which one actually answered is unknowable, so no attribution is made at all (leaving it blank beats guessing wrong). The judge's own model call is attributed by that same single-node rule too, but the result feeds somewhere this page never sees: not the outer activity row, but the pricing on its own `verdict` row — and that separate path has its own caching/staleness behavior. Judge spend is a separate ledger and is not part of this page's totals at all — see Platform & Admin → Judge Policies (Cost & Activity) for the details, including that attribution's caching behavior.

#What the panel shows

Breakdowns

Cost/requests/tokens broken down by model, group, node and user; a daily spend chart.

Cache savings

Cost avoided on cache hits (`cost_saved_usd`); a hit row's own `cost_usd` is written as 0, so the total never inflates.

Catalog coverage

Scans all models on active nodes — including ones that have never been requested — for how many resolve to a price.

Unpriced usage

Recently used models that spent tokens but have no price to compute a cost from — the list the admin fills in by hand.

Prices tab

Add/edit/delete price rows by hand, or trigger a models.dev sync.

When a price is entered by hand or a models.dev sync runs, older request records left blank because that model was previously unpriced get backfilled retroactively.