mirror of
https://gh.wpcy.net/https://github.com/discourse/discourse.git
synced 2026-06-19 03:05:45 +08:00
## 🔍 Overview This update some changes when showcasing feature cost for LLMs that has credit allocations ## 📸 Screenshots _Included in plan instead of cost when a feature comes solely from a credit allocation model_ <img width="1107" height="463" alt="Screenshot 2025-12-08 at 16 47 53" src="https://github.com/user-attachments/assets/24391a07-2af2-4d56-b8b9-b770ebdb132d" /> _A subtable in a tooltip with line items for mixed costs_ <img width="1110" height="460" alt="Screenshot 2025-12-08 at 16 48 19" src="https://github.com/user-attachments/assets/ceb94f00-9a1a-49f8-9e46-7ff544113d55" />
128 lines
4.3 KiB
Ruby
Vendored
128 lines
4.3 KiB
Ruby
Vendored
# frozen_string_literal: true
|
|
|
|
RSpec.describe AiUsageSerializer do
|
|
fab!(:user)
|
|
fab!(:claude_model) do
|
|
Fabricate(
|
|
:llm_model,
|
|
name: "claude-3-opus",
|
|
provider: "anthropic",
|
|
input_cost: 15.0,
|
|
output_cost: 75.0,
|
|
)
|
|
end
|
|
|
|
fab!(:gpt_model) do
|
|
Fabricate(:llm_model, name: "gpt-4", provider: "open_ai", input_cost: 10.0, output_cost: 30.0)
|
|
end
|
|
|
|
before { enable_current_plugin }
|
|
|
|
describe "#feature_models" do
|
|
it "returns a hash mapping feature names to model breakdowns" do
|
|
AiApiRequestStat.create!(
|
|
provider_id: AiApiAuditLog::Provider::Anthropic,
|
|
user_id: user.id,
|
|
llm_id: claude_model.id,
|
|
language_model: "claude-3-opus",
|
|
feature_name: "ai_bot",
|
|
request_tokens: 1000,
|
|
response_tokens: 500,
|
|
created_at: 1.day.ago,
|
|
)
|
|
|
|
AiApiRequestStat.create!(
|
|
provider_id: AiApiAuditLog::Provider::OpenAI,
|
|
user_id: user.id,
|
|
llm_id: gpt_model.id,
|
|
language_model: "gpt-4",
|
|
feature_name: "ai_bot",
|
|
request_tokens: 2000,
|
|
response_tokens: 1000,
|
|
created_at: 1.day.ago,
|
|
)
|
|
|
|
report = DiscourseAi::Completions::Report.new(start_date: 2.days.ago, end_date: Time.current)
|
|
serialized = described_class.new(report, root: false)
|
|
json = JSON.parse(serialized.to_json)
|
|
|
|
expect(json).to have_key("feature_models")
|
|
expect(json["feature_models"]).to be_a(Hash)
|
|
expect(json["feature_models"]).to have_key("ai_bot")
|
|
expect(json["feature_models"]["ai_bot"].size).to eq(2)
|
|
end
|
|
|
|
it "includes credit_allocation for models with LlmCreditAllocation" do
|
|
seeded_model = Fabricate(:seeded_model)
|
|
Fabricate(:llm_credit_allocation, llm_model: seeded_model)
|
|
|
|
AiApiRequestStat.create!(
|
|
provider_id: AiApiAuditLog::Provider::Anthropic,
|
|
user_id: user.id,
|
|
llm_id: seeded_model.id,
|
|
language_model: seeded_model.name,
|
|
feature_name: "ai_helper",
|
|
request_tokens: 500,
|
|
response_tokens: 250,
|
|
created_at: 1.day.ago,
|
|
)
|
|
|
|
report = DiscourseAi::Completions::Report.new(start_date: 2.days.ago, end_date: Time.current)
|
|
serialized = described_class.new(report, root: false)
|
|
json = JSON.parse(serialized.to_json)
|
|
|
|
ai_helper_models = json["feature_models"]["ai_helper"]
|
|
seeded_model_data = ai_helper_models.find { |m| m["llm_id"].to_i == seeded_model.id }
|
|
|
|
expect(seeded_model_data).to have_key("credit_allocation")
|
|
expect(seeded_model_data["credit_allocation"]).to be_present
|
|
end
|
|
|
|
it "does not include credit_allocation for models without LlmCreditAllocation" do
|
|
AiApiRequestStat.create!(
|
|
provider_id: AiApiAuditLog::Provider::Anthropic,
|
|
user_id: user.id,
|
|
llm_id: claude_model.id,
|
|
language_model: "claude-3-opus",
|
|
feature_name: "ai_bot",
|
|
request_tokens: 1000,
|
|
response_tokens: 500,
|
|
created_at: 1.day.ago,
|
|
)
|
|
|
|
report = DiscourseAi::Completions::Report.new(start_date: 2.days.ago, end_date: Time.current)
|
|
serialized = described_class.new(report, root: false)
|
|
json = JSON.parse(serialized.to_json)
|
|
|
|
ai_bot_models = json["feature_models"]["ai_bot"]
|
|
claude_model_data = ai_bot_models.find { |m| m["llm_id"].to_i == claude_model.id }
|
|
|
|
expect(claude_model_data).not_to have_key("credit_allocation")
|
|
end
|
|
|
|
it "includes spending data for each model in the breakdown" do
|
|
AiApiRequestStat.create!(
|
|
provider_id: AiApiAuditLog::Provider::Anthropic,
|
|
user_id: user.id,
|
|
llm_id: claude_model.id,
|
|
language_model: "claude-3-opus",
|
|
feature_name: "ai_bot",
|
|
request_tokens: 1000,
|
|
response_tokens: 500,
|
|
created_at: 1.day.ago,
|
|
)
|
|
|
|
report = DiscourseAi::Completions::Report.new(start_date: 2.days.ago, end_date: Time.current)
|
|
serialized = described_class.new(report, root: false)
|
|
json = JSON.parse(serialized.to_json)
|
|
|
|
ai_bot_models = json["feature_models"]["ai_bot"]
|
|
model_data = ai_bot_models.first
|
|
|
|
expect(model_data).to have_key("input_spending")
|
|
expect(model_data).to have_key("output_spending")
|
|
expect(model_data).to have_key("total_tokens")
|
|
expect(model_data).to have_key("usage_count")
|
|
end
|
|
end
|
|
end
|