Class: Assistant::CostCalculator
- Inherits:
-
Object
- Object
- Assistant::CostCalculator
- Defined in:
- app/services/assistant/cost_calculator.rb
Overview
Calculates the cost of AI model usage based on token counts and provider pricing.
Pricing is per million tokens (USD), sourced from provider pricing pages.
Cache pricing:
Anthropic: cache_read = 0.1x input, cache_write = 1.25x input
OpenAI: cache_read = 0.25x input
Gemini: provider-published per-model rates (not a single multiplier) —
e.g. 3.7 Flash promotional cache_read $0.075/M
Usage:
Assistant::CostCalculator.cost_for('claude-sonnet', input_tokens: 50_000, output_tokens: 2_000)
=> 0.18
Assistant::CostCalculator.pricing_for('claude-sonnet')
=> { input: 3.00, output: 15.00, cache_read: 0.30, cache_write: 3.75 }
Constant Summary collapse
- MODEL_PRICING =
Per-million-token pricing (USD). Updated Aug 2026 from provider pricing
pages. Gemini 3.7 Flash promotional rates through 2026-12-31 are
$0.75/$3.75 with $0.075 cache reads; rates double on 2027-01-01. Both
gemini keys resolve to gemini-3.7-flash via
AiModelConstants (gemini_pro is the high-thinking-effort tier of the same
model), so they share flash rates. Keys match
Assistant::ChatService::MODELS keys. { 'claude-haiku' => { input: 1.00, output: 5.00, cache_read: 0.10, cache_write: 1.25 }, 'claude-sonnet' => { input: 3.00, output: 15.00, cache_read: 0.30, cache_write: 3.75 }, 'claude-opus' => { input: 5.00, output: 25.00, cache_read: 0.50, cache_write: 6.25 }, 'gpt-5' => { input: 1.25, output: 10.00, cache_read: 0.3125, cache_write: 0.0 }, 'gpt-5.5' => { input: 5.00, output: 30.00, cache_read: 0.50, cache_write: 0.0 }, 'gpt-5-mini' => { input: 0.25, output: 2.00, cache_read: 0.0625, cache_write: 0.0 }, 'gemini-flash' => { input: 0.75, output: 3.75, cache_read: 0.075, cache_write: 0.0 }, 'gemini-pro' => { input: 0.75, output: 3.75, cache_read: 0.075, cache_write: 0.0 }, # Grok 4.6 (2026-08-13). Same rates as the 4.5 it replaces; xAI # publishes no separate cache-write charge. Doubles to $4/$12 above a # 200k prompt, which this flat table does not model — under-reporting # a badge on a giant prompt beats inventing a tier the picker cannot # see. 'grok-4.6' => { input: 2.00, output: 6.00, cache_read: 0.50, cache_write: 0.0 } }.freeze
Class Method Summary collapse
-
.cost_for(model_key, input_tokens:, output_tokens:, cached_tokens: 0, cache_creation_tokens: 0) ⇒ Float
Calculate the cost (in USD) for a single response given token counts and model key.
-
.pricing_for(model_key) ⇒ Hash?
Look up pricing by ChatService::MODELS key OR by model id.
Class Method Details
.cost_for(model_key, input_tokens:, output_tokens:, cached_tokens: 0, cache_creation_tokens: 0) ⇒ Float
Calculate the cost (in USD) for a single response given token counts and model key.
RubyLLM 1.15 normalized token accounting across providers: input_tokens
now means "standard input only" — prompt cache reads and writes are
reported separately as cached_tokens and cache_creation_tokens. The
three buckets are additive (no subtraction needed).
58 59 60 61 62 63 64 65 66 |
# File 'app/services/assistant/cost_calculator.rb', line 58 def self.cost_for(model_key, input_tokens:, output_tokens:, cached_tokens: 0, cache_creation_tokens: 0) pricing = pricing_for(model_key) return 0.0 unless pricing ((input_tokens / 1_000_000.0) * pricing[:input]) + ((output_tokens / 1_000_000.0) * pricing[:output]) + ((cached_tokens / 1_000_000.0) * pricing[:cache_read]) + ((cache_creation_tokens / 1_000_000.0) * pricing[:cache_write]) end |
.pricing_for(model_key) ⇒ Hash?
Look up pricing by ChatService::MODELS key OR by model id.
Callers have both. The per-turn badge passes what the response reports —
an id like gemini-3.7-flash or z-ai/glm-5.2 — while this table is
keyed by the short MODELS key (gemini-flash). The mismatch returned nil,
cost_for returned 0.0, and the badge hid the cost entirely: that is why
the grey per-turn line showed a price on some turns and not others.
78 79 80 81 82 83 |
# File 'app/services/assistant/cost_calculator.rb', line 78 def self.pricing_for(model_key) MODEL_PRICING[model_key] || MODEL_PRICING[models_key_for_id(model_key)] || gateway_pricing_for(model_key) || registry_pricing_for(model_key) end |