0
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2026-08-12 05:37:26 +08:00
discourse/plugins/discourse-ai/app/models/llm_quota.rb
Sam 6fc5673598
FEATURE: add cost limits to LLM quotas (#41277)
Track estimated request costs in AI audit logs and usage rollups so
quota
checks and usage reports can use the stored cost when available.

Add max cost quota fields to the admin UI, serializers, validations,
and staff logs.
2026-06-30 15:59:24 +10:00

113 lines
2.9 KiB
Ruby
Vendored

# frozen_string_literal: true
class LlmQuota < ActiveRecord::Base
self.table_name = "llm_quotas"
belongs_to :group
belongs_to :llm_model
has_many :llm_quota_usages
validates :group_id, presence: true
# we can not validate on create cause it breaks build
validates :llm_model_id, presence: true, on: :update
validates :duration_seconds, presence: true, numericality: { greater_than: 0 }
validates :max_tokens, numericality: { only_integer: true, greater_than: 0, allow_nil: true }
validates :max_usages, numericality: { greater_than: 0, allow_nil: true }
validates :max_cost, numericality: { greater_than: 0, allow_nil: true }
validate :at_least_one_limit
def self.check_quotas!(llm, user)
return true if user.blank?
quotas = joins(:group).where(llm_model: llm).where(group: user.groups)
return true if quotas.empty?
errors =
quotas.map do |quota|
usage = LlmQuotaUsage.find_or_create_for(user: user, llm_quota: quota)
begin
usage.check_quota!
nil
rescue LlmQuotaUsage::QuotaExceededError => e
e
end
end
return if errors.include?(nil)
raise errors.first
end
def self.log_usage(
llm,
user,
request_tokens:,
response_tokens:,
cache_read_tokens: 0,
cache_write_tokens: 0,
estimated_cost: nil
)
return if user.blank?
estimated_cost ||=
llm.estimated_cost_for_tokens(
request_tokens: request_tokens,
response_tokens: response_tokens,
cache_read_tokens: cache_read_tokens,
cache_write_tokens: cache_write_tokens,
)
quotas = joins(:group).where(llm_model: llm).where(group: user.groups)
quotas.each do |quota|
usage = LlmQuotaUsage.find_or_create_for(user: user, llm_quota: quota)
usage.increment_usage!(
input_tokens: request_tokens,
output_tokens: response_tokens,
cache_read_tokens: cache_read_tokens,
cache_write_tokens: cache_write_tokens,
cost: estimated_cost,
)
end
end
def available_tokens
max_tokens
end
def available_usages
max_usages
end
def available_cost
max_cost
end
private
def at_least_one_limit
if max_tokens.nil? && max_usages.nil? && max_cost.nil?
errors.add(:base, I18n.t("discourse_ai.errors.quota_required"))
end
end
end
# == Schema Information
#
# Table name: llm_quotas
#
# id :bigint not null, primary key
# duration_seconds :integer not null
# max_cost :decimal(20, 10)
# max_tokens :integer
# max_usages :integer
# created_at :datetime not null
# updated_at :datetime not null
# group_id :bigint not null
# llm_model_id :bigint not null
#
# Indexes
#
# index_llm_quotas_on_group_id_and_llm_model_id (group_id,llm_model_id) UNIQUE
# index_llm_quotas_on_llm_model_id (llm_model_id)
#