Merge pull request #984 from basecamp/refactor-rubby-llm-costs
Unify AI usage cost calculations
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
class Ai::UsageCost
|
||||
attr_reader :model_id, :input_tokens, :output_tokens
|
||||
|
||||
class << self
|
||||
def from_llm_response(response)
|
||||
new \
|
||||
model_id: response.model_id,
|
||||
input_tokens: response.input_tokens,
|
||||
output_tokens: response.output_tokens
|
||||
end
|
||||
end
|
||||
|
||||
def initialize(model_id:, input_tokens:, output_tokens:)
|
||||
@model_id = model_id
|
||||
@input_tokens = input_tokens
|
||||
@output_tokens = output_tokens
|
||||
end
|
||||
|
||||
def in_microcents
|
||||
input_cost_in_microcents + output_cost_in_microcents
|
||||
end
|
||||
|
||||
def input_cost_in_microcents
|
||||
calculate_token_cost(input_tokens, model_info.input_price_per_million)
|
||||
end
|
||||
|
||||
def output_cost_in_microcents
|
||||
calculate_token_cost(output_tokens, model_info.output_price_per_million)
|
||||
end
|
||||
|
||||
private
|
||||
def calculate_token_cost(token_count, price_per_million)
|
||||
return 0 unless price_per_million
|
||||
|
||||
single_token_price = price_per_million.to_d / 1_000_000
|
||||
token_cost_dollars = token_count * single_token_price
|
||||
Ai::Quota::Money.wrap(token_cost_dollars).in_microcents
|
||||
end
|
||||
|
||||
def model_info
|
||||
@model_info ||= RubyLLM.models.find(model_id)
|
||||
end
|
||||
end
|
||||
@@ -16,9 +16,9 @@ module Conversation::Message::Respondable
|
||||
model_id: response.model_id,
|
||||
input_tokens: response.input_tokens,
|
||||
output_tokens: response.output_tokens,
|
||||
input_cost_in_microcents: response.input_cost_in_microcents,
|
||||
output_cost_in_microcents: response.output_cost_in_microcents,
|
||||
cost_in_microcents: response.cost_in_microcents
|
||||
input_cost_in_microcents: response.cost.input_cost_in_microcents,
|
||||
output_cost_in_microcents: response.cost.output_cost_in_microcents,
|
||||
cost_in_microcents: response.cost.in_microcents
|
||||
}
|
||||
|
||||
conversation.respond(response.answer, **message_attributes)
|
||||
|
||||
@@ -8,41 +8,7 @@ class Conversation::Message::ResponseGenerator::Response
|
||||
@model_id = model_id
|
||||
end
|
||||
|
||||
def cost_in_microcents
|
||||
input_cost_in_microcents + output_cost_in_microcents
|
||||
def cost
|
||||
@cost ||= Ai::UsageCost.from_llm_response(self)
|
||||
end
|
||||
|
||||
def input_cost_in_microcents
|
||||
return unless token_price = input_token_price_microcents
|
||||
|
||||
(input_tokens * token_price).to_i
|
||||
end
|
||||
|
||||
def input_token_price_microcents
|
||||
return unless model_info
|
||||
|
||||
price_per_million_tokens_in_microcents(model_info.input_price_per_million)
|
||||
end
|
||||
|
||||
def output_cost_in_microcents
|
||||
return unless token_price = output_token_price_microcents
|
||||
|
||||
(output_tokens * token_price).to_i
|
||||
end
|
||||
|
||||
def output_token_price_microcents
|
||||
return unless model_info
|
||||
|
||||
price_per_million_tokens_in_microcents(model_info.output_price_per_million)
|
||||
end
|
||||
|
||||
def model_info
|
||||
@model_info ||= RubyLLM.models.find(model_id)
|
||||
end
|
||||
|
||||
private
|
||||
def price_per_million_tokens_in_microcents(price)
|
||||
single_token_price = price.to_d / 1_000_000
|
||||
Ai::Quota::Money.wrap(single_token_price).in_microcents
|
||||
end
|
||||
end
|
||||
|
||||
@@ -29,7 +29,7 @@ class Event::Summarizer
|
||||
|
||||
def summarize
|
||||
response = chat.ask join_prompts("Summarize the following content:", summarizable_content)
|
||||
[ response.content, calculate_cost_in_microcents(response) ]
|
||||
[ response.content, Ai::UsageCost.from_llm_response(response).in_microcents ]
|
||||
end
|
||||
|
||||
def summarizable_content
|
||||
@@ -43,21 +43,4 @@ class Event::Summarizer
|
||||
chat = RubyLLM.chat(model: llm_model)
|
||||
chat.with_instructions(join_prompts(prompt, domain_model_prompt, user_data_injection_prompt))
|
||||
end
|
||||
|
||||
def calculate_cost_in_microcents(response)
|
||||
model_info = RubyLLM.models.find(response.model_id)
|
||||
|
||||
input_cost = calculate_token_cost(response.input_tokens, model_info.input_price_per_million)
|
||||
output_cost = calculate_token_cost(response.output_tokens, model_info.output_price_per_million)
|
||||
|
||||
input_cost + output_cost
|
||||
end
|
||||
|
||||
def calculate_token_cost(token_count, price_per_million)
|
||||
return 0 unless price_per_million
|
||||
|
||||
single_token_price = price_per_million.to_d / 1_000_000
|
||||
token_cost_dollars = token_count * single_token_price
|
||||
Ai::Quota::Money.wrap(token_cost_dollars).in_microcents
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
require "test_helper"
|
||||
|
||||
class Ai::ResponseCostTest < ActiveSupport::TestCase
|
||||
test "price calculations" do
|
||||
response_cost = Ai::UsageCost.new(
|
||||
model_id: "gpt-4",
|
||||
input_tokens: 198,
|
||||
output_tokens: 2
|
||||
)
|
||||
|
||||
# We've got 198 input tokens, so that's
|
||||
# 198 * 3000 = 594000
|
||||
assert_equal 594000, response_cost.input_cost_in_microcents
|
||||
|
||||
# We've got 2 output tokens, so that's
|
||||
# 2 * 6000 = 12000
|
||||
assert_equal 12000, response_cost.output_cost_in_microcents
|
||||
|
||||
# So the total is 594000 + 12000 micro-cents
|
||||
assert_equal 606000, response_cost.in_microcents
|
||||
end
|
||||
end
|
||||
@@ -1,32 +0,0 @@
|
||||
require "test_helper"
|
||||
|
||||
class Conversation::Message::ResponseGenerator::ResponseTest < ActiveSupport::TestCase
|
||||
test "price calculations" do
|
||||
response = Conversation::Message::ResponseGenerator::Response.new(
|
||||
answer: "Hi!",
|
||||
input_tokens: 198,
|
||||
output_tokens: 2,
|
||||
model_id: "gpt-4"
|
||||
)
|
||||
|
||||
# The price of an input token is 30 USD per million tokens
|
||||
# and 60 USD per million output tokens
|
||||
# That's 0.00003 cents per input token and 0.00006 cents
|
||||
# per output token
|
||||
# Which is 3000 micro-cents per input token and 6000 micro-cents
|
||||
# per output token
|
||||
assert_equal "3000.0".to_d, response.input_token_price_microcents
|
||||
assert_equal "6000.0".to_d, response.output_token_price_microcents
|
||||
|
||||
# We've got 198 input tokens, so that's
|
||||
# 198 * 3000 = 594000
|
||||
assert_equal 594000, response.input_cost_in_microcents
|
||||
|
||||
# We've got 2 output tokens, so that's
|
||||
# 2 * 6000 = 12
|
||||
assert_equal 12000, response.output_cost_in_microcents
|
||||
|
||||
# So the total is 594000 + 12000 micro-cents
|
||||
assert_equal 606000, response.cost_in_microcents
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user