Skip to content
prassoaiPublic

About

Shared Go module: LLM inference cost/pricing from vendored LiteLLM data (MIT). Consumed by murmur, llm-gateway, and back.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

llmcost

Shared Go module: LLM inference cost from vendored LiteLLM pricing data (MIT). Data plus a lookup and an exact multiply — nothing clever.

Costs are in nls: 1 nls = 1/100,000 USD (the unit also known as a centimill). Model ids are LiteLLM pricing keys, looked up directly.

Usage — provider-explicit, raw counts in

The providers report token counts differently. Cost takes a sealed Usage interface implemented only by ClaudeUsage, OpenAIUsage, and FireworksUsage; each takes its provider's RAW usage fields and normalizes to disjoint billable components inside, and carries exactly the pricing modes that provider actually has — a mode a provider doesn't offer is inexpressible, not merely unpriceable. Never pre-subtract, never mix shapes.

Anthropic (disjoint counts — input_tokens EXCLUDES cache activity):

cost, ok := llmcost.Cost("claude-opus-4-8", llmcost.ClaudeUsage{
    InputTokens:                1200,  // usage.input_tokens — uncached input only
    CacheReadInputTokens:       45000, // usage.cache_read_input_tokens
    CacheCreationInputTokens:   3000,  // usage.cache_creation_input_tokens — TOTAL writes, both TTLs
    CacheCreation1hInputTokens: 1000,  // usage.cache_creation.ephemeral_1h_input_tokens — 1h subset
    OutputTokens:               800,   // usage.output_tokens
    Speed:                      "",    // the REQUEST's speed param — "fast" bills the model's fast premium (2× today)
    InferenceGeo:               "",    // usage.inference_geo — a pinned region bills its premium (us: 1.1×)
})

OpenAI / codex (overlapping counts — input_tokens INCLUDES cached and cache-write):

cost, ok := llmcost.Cost("gpt-5.6-sol", llmcost.OpenAIUsage{
    InputTokens:       46800, // usage.input_tokens — total (45000 cached + 600 write + 1200 uncached)
    CachedInputTokens: 45000, // input_tokens_details.cached_tokens — cache-read subset
    CacheWriteTokens:  600,   // input_tokens_details.cache_write_tokens — GPT-5.6+, 1.25× input
    OutputTokens:      800,   // reasoning tokens are a subset, already included
    DataResidency:     "",    // "eu"/"us" when served by eu./us.api.openai.com — bills the 1.1× uplift
    ServiceTier:       "",    // "" = standard; TierFlex/TierPriority bill that tier's rates
})

Fireworks AI (OpenAI-compatible, so also overlapping — but no cache writes, no tiers, no regional pricing, so the type has three fields):

cost, ok := llmcost.Cost("fireworks_ai/accounts/fireworks/models/deepseek-v4-flash", llmcost.FireworksUsage{
    InputTokens:  1020000, // usage.prompt_tokens — total (1000000 cached + 20000 uncached)
    CachedTokens: 1000000, // prompt_tokens_details.cached_tokens — cache-read subset
    OutputTokens: 5000,    // usage.completion_tokens
})

ok is false whenever the response can't be priced — unknown model, or usage on a component the model has no rate for. Nothing silently bills zero.

Service tiers — flex and priority

OpenAI bills the same request differently by processing tier: flex (cheaper, slower) and priority (pricier, faster) publish their own rates (LiteLLM's *_flex / *_priority variants). The tier rides on OpenAIUsage.ServiceTier (zero value = standard) — Claude and Fireworks usage have no tier knob, because neither provider publishes tiered rates.

cost, ok := llmcost.Cost("gpt-5.5", llmcost.OpenAIUsage{
    InputTokens:       46200,
    CachedInputTokens: 45000,
    OutputTokens:      800,
    ServiceTier:       llmcost.TierFlex,
})

The no-fallback rule extends across tiers: a model without rates at the requested tier (gpt-5.3-codex has no flex), a tier missing a component the usage reports, or an unrecognized non-empty ServiceTier value all return ok=false — flex/priority usage is never silently billed at standard rates (~2× off either way). Priority has its own context-window tiers (*_above_Xk_tokens_priority), resolved with the same semantics. LiteLLM's *_batches variants (Batch API) are not modeled.

Semantics

  • Real cache rates, no fallback. Cache reads bill at the model's cache_read_input_token_cost (0.1× input for Anthropic and OpenAI). Cache writes bill by TTL for Anthropic: the 1-hour subset at cache_creation_input_token_cost_above_1hr (2× input) and the 5-minute remainder at cache_creation_input_token_cost (1.25× input) — the 1h rate participates in context-window tiers too. OpenAI has a single cache-write TTL: GPT-5.6+ bill CacheWriteTokens at cache_creation_input_token_cost (1.25× input); pre-5.6 models bill reads only. Fireworks bills reads only always — its prompt cache is automatic and unbilled, so FireworksUsage has no cache-write field. A model missing a rate for a reported component fails to price — never approximated at another rate.
  • Context-window tiers. Models with LiteLLM *_above_Xk_tokens pricing (Anthropic's 1M-context beta >200k, GPT-5.4/5.5 >272k, Gemini >200k) are tiered on the request's total prompt size (uncached + cache reads + cache writes; strict >), and once over the threshold the entire request bills at the tier's rates — not just the excess. Verified against LiteLLM's own cost calculator and the providers' billing. Untiered components inherit the priced service tier's base rates — never another service tier's.
  • Price multipliers, no silent standard rates. Anthropic's fast mode (speed: "fast", ×2 on opus-4-8 and opus-5 — LiteLLM carried ×6 for opus-4-6/4-7 and retired it) and pinned-region inference (usage.inference_geo, ×1.1 for us) multiply uncached input and output — cache traffic is never scaled — and compose multiplicatively. A mode the model has no factor for fails to price: fast premiums have reached 6×, so billing standard rates on a data lag is a silent multiple-× underbill. OpenAI's data residency (eu./us.api.openai.com) uplifts every component including cache reads (×1.1 on gpt-5.4/5.5); models OpenAI doesn't regionally price bill standard, as in LiteLLM. Fireworks has neither mode — it publishes no multipliers and no regional uplifts, so FireworksUsage has no mode fields. Multipliers scale the rates of whichever service tier is being priced.
  • Exact math, ceiling at the total. Rates parse from decimal literals into math/big.Rat — never through float64. The response is priced exactly in USD and converted to nls only at the final total, ceiling-rounded. Sub-nls token costs accumulate correctly; any non-zero usage costs at least 1 nls.
  • Provider in the key, grammar in ModelSelector, no fuzzy aliasing. The same vendor model bills differently per serving provider — gpt-5.4 vs azure/gpt-5.4 vs azure/us/gpt-5.4 (Azure data zones carry a ~10% premium in the rates), Claude direct vs Bedrock AWS ids vs vertex_ai/… — each its own key. ModelSelector{Provider, Model, Region}.Key() resolves the provider's native id verbatim or the vendor's canonical name through each cloud's bespoke renaming scheme (Bedrock's anthropic. prefix, -v1:0 artifact versions, us. geo profiles; Vertex's @date/@default; Azure's gpt-35 spelling): {Bedrock, "claude-sonnet-4-5", "us"} → us.anthropic.claude-sonnet-4-5-20250929-v1:0. Resolution is deterministic and verified — an ambiguous undated name fails, a missing region key fails (never the cheaper global key), a cross-provider key fails. Canonical names exist only for providers that rename what they serve; Fireworks doesn't, so its keys are fireworks_ai/ plus your own model path verbatim ({Fireworks, "accounts/fireworks/models/deepseek-v4-flash"}) and resolve by native id alone. TestSelectorCanonicalCoverage gates every data sync on the whole scheme: each cloud-served Anthropic/OpenAI key must stay selectable by its vendor name, and every Fireworks key by its model path. Which selectors you bill is your policy: test that each resolves — that test is your guarantee that a data sync can't silently drop a model you depend on.

RatesFor(model, tier) exposes the raw per-token rates — base and tiers — for callers that want them. See doc.go for the full contract.

Vendored data

model_prices_and_context_window.json is embedded byte-identical from BerriAI/litellm (MIT — see THIRD_PARTY_LICENSES) at the commit pinned in VENDORED_FROM.

A weekly workflow (.github/workflows/sync.yml) re-fetches upstream, and if the data changed, bumps the pin and opens a PR. The validation gate — go test, run both in the sync job and by CI — fails the update if upstream restructures the pricing schema or drops the canary models' pricing (LiteLLM has shipped a broken cost map before). Consumers' own resolution tests catch dropped models when they bump the module version.

Opening that PR requires the repo secret SYNC_PR_TOKEN — a fine-grained PAT with Pull requests: write and Contents: write on this repo. It has to be long-lived: the secret is read by a weekly cron, so a GitHub App installation token (valid one hour) would 401 on every run after the first. Prefer an App? Mint the installation token inside the job with actions/create-github-app-token and pass its output. Actions' default GITHUB_TOKEN cannot be used: the org disallows GitHub Actions from creating pull requests, and the API rejects creation only after the sync branch is pushed, stranding the synced data on a branch with no PR. Without the secret, the sync workflow pushes sync/litellm-prices and then fails on the "open PR" step.

Every merge to main is auto-tagged with the next patch version (.github/workflows/tag.yml), so consumers always have a real module tag to pin. Bump minor/major by pushing a tag by hand.

About

Shared Go module: LLM inference cost/pricing from vendored LiteLLM data (MIT). Consumed by murmur, llm-gateway, and back.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages