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

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