Module: Silas::Inbox::Cost
- Defined in:
- lib/silas/inbox/cost.rb
Overview
Cost is derived at read time from step tokens. Prices come from
config.model_prices (the OVERRIDE map — custom deployments, fine-tunes,
models newer than the installed registry) and fall back to RubyLLM's
model registry, priced per (model, provider) — 85/1081 registry ids
exist under multiple providers at different prices, which is why steps
stamp the provider. Unknown stays unpriced, never a lying $0.00.
Class Method Summary collapse
- .aggregate(rows) ⇒ Object
- .for_agent(agent_name) ⇒ Object
- .for_session(session) ⇒ Object
- .for_turn(turn) ⇒ Object
-
.format(cents) ⇒ Object
microcents -> "$0.0123" (or nil when unpriced with no priced tokens).
-
.rate_for(model, provider) ⇒ Object
out: in cost-units per 1k tokens (1e6 units = $1), or nil when the model can't be priced.
Class Method Details
.aggregate(rows) ⇒ Object
35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
# File 'lib/silas/inbox/cost.rb', line 35 def aggregate(rows) input = output = microcents = 0 unpriced = false rows.each do |model, provider, in_tok, out_tok| in_tok = in_tok.to_i out_tok = out_tok.to_i input += in_tok output += out_tok if (rate = rate_for(model, provider)) microcents += (in_tok * rate[:in] + out_tok * rate[:out]) / 1000 else unpriced = true end end { input_tokens: input, output_tokens: output, microcents: microcents, unpriced: unpriced } end |
.for_agent(agent_name) ⇒ Object
27 28 29 30 31 32 33 |
# File 'lib/silas/inbox/cost.rb', line 27 def for_agent(agent_name) rows = Silas::Step.joins(turn: :session) .where(silas_sessions: { agent_name: agent_name }) .group(:model, :provider) .pluck(:model, :provider, Arel.sql("SUM(silas_steps.input_tokens)"), Arel.sql("SUM(silas_steps.output_tokens)")) aggregate(rows) end |
.for_session(session) ⇒ Object
12 13 14 15 16 17 18 |
# File 'lib/silas/inbox/cost.rb', line 12 def for_session(session) rows = Silas::Step.joins(:turn) .where(silas_turns: { session_id: session.id }) .group(:model, :provider) .pluck(:model, :provider, Arel.sql("SUM(silas_steps.input_tokens)"), Arel.sql("SUM(silas_steps.output_tokens)")) aggregate(rows) end |
.for_turn(turn) ⇒ Object
20 21 22 23 24 25 |
# File 'lib/silas/inbox/cost.rb', line 20 def for_turn(turn) rows = Silas::Step.where(turn_id: turn.id) .group(:model, :provider) .pluck(:model, :provider, Arel.sql("SUM(silas_steps.input_tokens)"), Arel.sql("SUM(silas_steps.output_tokens)")) aggregate(rows) end |
.format(cents) ⇒ Object
microcents -> "$0.0123" (or nil when unpriced with no priced tokens)
75 76 77 78 79 |
# File 'lib/silas/inbox/cost.rb', line 75 def format(cents) return nil if cents.nil? "$%.4f" % (cents / 1_000_000.0) end |
.rate_for(model, provider) ⇒ Object
out: in cost-units per 1k tokens (1e6 units = $1), or nil when the model can't be priced. Override map first; then the registry — two-arg find when the step stamped a provider (the bare form tie-breaks by a hardcoded preference list and can price the wrong provider), registry $/MTok converted at x1000. A model with no price data returns nil: unknown is never zero.
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
# File 'lib/silas/inbox/cost.rb', line 58 def rate_for(model, provider) if (price = Silas.config.model_prices[model]) return price end return nil if model.nil? info = provider.present? ? ::RubyLLM.models.find(model, provider) : ::RubyLLM.models.find(model) in_pm = info.input_price_per_million out_pm = info.output_price_per_million return nil unless in_pm && out_pm { in: (in_pm * 1000).round, out: (out_pm * 1000).round } rescue StandardError nil end |