fix(ai): attribute Bedrock model IDs to anthropic + clean nil enum

- LlmUsage.infer_provider now returns "anthropic" for Bedrock /
  Vertex shaped IDs (anthropic.* and anthropic/*), so cost-ledger
  filtering by provider stays correct even when no per-MTok rate is
  stored. Previously these IDs fell through to the "openai" default.
- AutoCategorizer drops the redundant nil sentinel from the
  category_name enum — the union type [string, null] already permits
  null, and some JSON Schema validators reject nil literals inside
  enum arrays.
This commit is contained in:
Guillem Arias
2026-05-25 20:30:41 +02:00
parent 45c61bcbc1
commit 29030d648e
3 changed files with 24 additions and 1 deletions

View File

@@ -92,6 +92,13 @@ class LlmUsage < ApplicationRecord
def self.infer_provider(model)
return "openai" if model.blank?
# Bedrock + Vertex prefix model IDs with "anthropic." regardless of
# whether the Claude family is in the local PRICING map. Attribute them
# to the Anthropic provider so cost-ledger filtering by provider is
# correct even when we can't compute a per-token rate (custom endpoints
# bill via their own provider, not Anthropic directly).
return "anthropic" if model.start_with?("anthropic.", "anthropic/")
# Check each provider to see if they have pricing for this model
PRICING.each do |provider_name, provider_pricing|
# Try exact match first

View File

@@ -77,7 +77,7 @@ class Provider::Anthropic::AutoCategorizer
category_name: {
type: [ "string", "null" ],
description: "Matched category name from the user's categories, or null when uncertain.",
enum: [ *user_categories.map { |c| c[:name] }, nil ]
enum: user_categories.map { |c| c[:name] }
}
},
required: [ "transaction_id", "category_name" ],

View File

@@ -12,6 +12,22 @@ class LlmUsageTest < ActiveSupport::TestCase
assert_equal "openai", LlmUsage.infer_provider("gpt-5")
end
test "infer_provider attributes Bedrock and Vertex prefixed IDs to anthropic" do
assert_equal "anthropic", LlmUsage.infer_provider("anthropic.claude-sonnet-4-5-20250929-v1:0")
assert_equal "anthropic", LlmUsage.infer_provider("anthropic.claude-opus-4-20250514-v1:0")
assert_equal "anthropic", LlmUsage.infer_provider("anthropic/claude-3-5-sonnet@20240620")
end
test "calculate_cost returns nil for Bedrock IDs (no per-token rate stored)" do
# Bedrock bills through AWS not Anthropic — we don't store a per-MTok rate,
# but the row must still attribute to anthropic for provider filtering.
assert_nil LlmUsage.calculate_cost(
model: "anthropic.claude-sonnet-4-5-20250929-v1:0",
prompt_tokens: 1000,
completion_tokens: 500
)
end
test "calculate_cost returns Anthropic pricing for Claude models" do
cost = LlmUsage.calculate_cost(model: "claude-sonnet-4-6", prompt_tokens: 1_000_000, completion_tokens: 100_000)