feat: BYOK users consume a single credit only

This commit is contained in:
Shivam Mishra
2026-01-13 20:03:17 +05:30
parent 011bbca28f
commit b67ba61bda
3 changed files with 30 additions and 1 deletions
@@ -28,7 +28,7 @@ module Enterprise::Captain::BaseTaskService
end
def increment_usage
credits = Llm::Models.credit_multiplier_for(configured_model)
credits = using_hook_key? ? 1 : Llm::Models.credit_multiplier_for(configured_model)
Rails.logger.info("[CAPTAIN][#{self.class.name}] Incrementing response usage for account #{account.id} by #{credits} credits")
account.increment_response_usage(credits: credits)
end
+13
View File
@@ -72,6 +72,11 @@ class Captain::BaseTaskService
build_ruby_llm_response(response, messages)
end
rescue StandardError => e
# TODO: RubyLLM throws the RubyLLM::Error when any API based error occurs
# Just like inboxes have a reauthroizable error handling mechanism,
# we should handle the errors if the account is using their own key.
# If more than 5 errors occur, we disable the hook and notify the admins
# see: https://rubyllm.com/error-handling/#rubyllm-error-hierarchy
ChatwootExceptionTracker.new(e, account: account).capture_exception
{ error: e.message, request_messages: messages }
end
@@ -128,6 +133,8 @@ class Captain::BaseTaskService
end
def api_key
# TODO: Once we support multiple LLM providers, ensure provider key is correctly looked up
# Currently works because the only model/provider is OpenAI
@api_key ||= openai_hook&.settings&.dig('api_key') || system_api_key
end
@@ -135,6 +142,12 @@ class Captain::BaseTaskService
@openai_hook ||= account.hooks.find_by(app_id: 'openai', status: 'enabled')
end
def using_hook_key?
# TODO: Once we support multiple LLM providers, ensure provider key is correctly looked up
# Currently works because the only model/provider is OpenAI
openai_hook&.settings&.dig('api_key').present?
end
def system_api_key
@system_api_key ||= InstallationConfig.find_by(name: 'CAPTAIN_OPEN_AI_API_KEY')&.value
end
@@ -69,6 +69,22 @@ RSpec.describe Captain::BaseTaskService, type: :model do
service.perform
end
context 'when account uses their own OpenAI key via hook' do
let!(:openai_hook) { create(:integrations_hook, :openai, account: account, settings: { 'api_key' => 'user-own-key' }) }
it 'uses 1 credit regardless of model cost' do
allow(account).to receive(:captain_editor_model).and_return('gpt-5.1')
expect(account).to receive(:increment_response_usage).with(credits: 1)
service.perform
end
it 'uses 1 credit even for expensive models' do
allow(account).to receive(:captain_editor_model).and_return('gpt-4.1')
expect(account).to receive(:increment_response_usage).with(credits: 1)
service.perform
end
end
context 'when result has an error' do
let(:perform_result) { { error: 'API Error' } }