Class: SpreeMenuChat::TokenBudget
- Inherits:
-
Object
- Object
- SpreeMenuChat::TokenBudget
- Defined in:
- app/services/spree_menu_chat/token_budget.rb
Overview
Per-store daily cap on Gemini generation token spend (see SpreeMenuChat::Configuration#daily_token_budget) — the other half of M5's guardrails alongside SpreeMenuChat::RateLimiter. Once a store's daily total is exhausted, SpreeMenuChat::AnswerGenerator stops calling Gemini for the rest of the day and falls back to a "come back tomorrow / contact us" response instead, rather than continuing to spend.
Deliberately tracks generation tokens only, using Gemini's own reported usageMetadata.totalTokenCount (not embedding tokens, and not an estimate). Voyage's embedding call in the live chat path is just the query embedding (SpreeMenuChat::Retriever) — a few dozen tokens, dwarfed by a real generation call's typical few-hundred-token cost. Folding embedding spend in too would be more complete, but it would mean EmbeddingClient/Retriever both starting to return usage alongside vectors — real plumbing, not just a config tweak — so it's left as a follow-up rather than blocking this guardrail on it.
Class Method Summary collapse
- .exceeded?(store: Spree::Store.default) ⇒ Boolean
- .record!(tokens, store: Spree::Store.default) ⇒ Object
Instance Method Summary collapse
- #exceeded? ⇒ Boolean
-
#initialize(store:) ⇒ TokenBudget
constructor
A new instance of TokenBudget.
- #record!(tokens) ⇒ Object
Constructor Details
#initialize(store:) ⇒ TokenBudget
Returns a new instance of TokenBudget.
27 28 29 |
# File 'app/services/spree_menu_chat/token_budget.rb', line 27 def initialize(store:) @store = store end |
Class Method Details
.exceeded?(store: Spree::Store.default) ⇒ Boolean
19 20 21 |
# File 'app/services/spree_menu_chat/token_budget.rb', line 19 def self.exceeded?(store: Spree::Store.default) new(store: store).exceeded? end |
.record!(tokens, store: Spree::Store.default) ⇒ Object
23 24 25 |
# File 'app/services/spree_menu_chat/token_budget.rb', line 23 def self.record!(tokens, store: Spree::Store.default) new(store: store).record!(tokens) end |
Instance Method Details
#exceeded? ⇒ Boolean
31 32 33 34 |
# File 'app/services/spree_menu_chat/token_budget.rb', line 31 def exceeded? used = SpreeMenuChat::TokenUsage.find_by(store: @store, date: Date.current)&.tokens_used || 0 used >= SpreeMenuChat::Config.daily_token_budget end |
#record!(tokens) ⇒ Object
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
# File 'app/services/spree_menu_chat/token_budget.rb', line 36 def record!(tokens) tokens = tokens.to_i return if tokens <= 0 ActiveRecord::Base.transaction do usage = SpreeMenuChat::TokenUsage.lock.find_or_initialize_by(store: @store, date: Date.current) usage.tokens_used = usage.tokens_used.to_i + tokens usage.save! end rescue ActiveRecord::RecordNotUnique # Lost a race with a concurrent first-request-of-the-day for this # store — the row exists now, retry against it. Same pattern as # SpreeMenuChat::RateLimiter#check!. retry end |