diff --git a/lib/req_llm/billing.ex b/lib/req_llm/billing.ex index f2b7de76..53b7bf0f 100644 --- a/lib/req_llm/billing.ex +++ b/lib/req_llm/billing.ex @@ -79,7 +79,8 @@ defmodule ReqLLM.Billing do total: total_cost, line_items: line_items, input_cost: token_costs.input_cost, - output_cost: token_costs.output_cost + output_cost: token_costs.output_cost, + reasoning_cost: token_costs.reasoning_cost } end @@ -216,12 +217,17 @@ defmodule ReqLLM.Billing do |> Enum.filter(&token_input_item?/1) |> Enum.reduce(0.0, fn item, acc -> Float.round(acc + item.cost, 6) end) + reasoning_cost = + line_items + |> Enum.filter(&token_reasoning_item?/1) + |> Enum.reduce(0.0, fn item, acc -> Float.round(acc + item.cost, 6) end) + output_cost = line_items |> Enum.filter(&token_output_item?/1) |> Enum.reduce(0.0, fn item, acc -> Float.round(acc + item.cost, 6) end) - %{input_cost: input_cost, output_cost: output_cost} + %{input_cost: input_cost, output_cost: output_cost, reasoning_cost: reasoning_cost} end defp token_input_item?(%{id: id}) when is_binary(id) do @@ -230,6 +236,12 @@ defmodule ReqLLM.Billing do defp token_input_item?(_), do: false + defp token_reasoning_item?(%{id: id}) when is_binary(id) do + String.starts_with?(id, "token.reasoning") + end + + defp token_reasoning_item?(_), do: false + defp token_output_item?(%{id: id}) when is_binary(id) do String.starts_with?(id, "token.output") or String.starts_with?(id, "token.reasoning") end diff --git a/lib/req_llm/step/usage.ex b/lib/req_llm/step/usage.ex index 8a428f1e..f128850a 100644 --- a/lib/req_llm/step/usage.ex +++ b/lib/req_llm/step/usage.ex @@ -64,6 +64,7 @@ defmodule ReqLLM.Step.Usage do Map.merge(meta, %{ input_cost: cost_breakdown.input_cost, output_cost: cost_breakdown.output_cost, + reasoning_cost: cost_breakdown.reasoning_cost, total_cost: cost_breakdown.total_cost }) else diff --git a/lib/req_llm/usage/cost.ex b/lib/req_llm/usage/cost.ex index 1e6acf0a..59d95e16 100644 --- a/lib/req_llm/usage/cost.ex +++ b/lib/req_llm/usage/cost.ex @@ -35,6 +35,7 @@ defmodule ReqLLM.Usage.Cost do %{ input_cost: cost.input_cost, output_cost: cost.output_cost, + reasoning_cost: cost.reasoning_cost, total_cost: cost.total, cost: cost }} @@ -57,6 +58,7 @@ defmodule ReqLLM.Usage.Cost do |> Map.put(:cost, cost_breakdown.cost) |> Map.put(:input_cost, cost_breakdown.input_cost) |> Map.put(:output_cost, cost_breakdown.output_cost) + |> Map.put(:reasoning_cost, cost_breakdown.reasoning_cost) if Keyword.get(opts, :preserve_total_cost, false) do Map.put_new(usage, :total_cost, cost_breakdown.total_cost) diff --git a/test/req_llm/billing_test.exs b/test/req_llm/billing_test.exs index 4501d106..6bc7ac58 100644 --- a/test/req_llm/billing_test.exs +++ b/test/req_llm/billing_test.exs @@ -141,4 +141,54 @@ defmodule ReqLLM.BillingTest do assert cost.tokens == 0.00074 assert cost.total == 0.00074 end + + test "calculates reasoning_cost separately when token.reasoning component exists" do + model = %LLMDB.Model{ + provider: :test, + id: "m1", + pricing: %{ + components: [ + %{id: "token.input", kind: "token", per: 1_000_000, rate: 1.0}, + %{id: "token.output", kind: "token", per: 1_000_000, rate: 2.0}, + %{id: "token.reasoning", kind: "token", per: 1_000_000, rate: 3.0} + ] + } + } + + usage = %{ + input_tokens: 1_000_000, + output_tokens: 500_000, + reasoning_tokens: 200_000 + } + + assert {:ok, cost} = Billing.calculate(usage, model) + assert cost.input_cost == 1.0 + assert cost.output_cost == 1.6 + assert cost.reasoning_cost == 0.6 + assert cost.total == 2.6 + end + + test "reasoning_cost is zero when no reasoning tokens" do + model = %LLMDB.Model{ + provider: :test, + id: "m1", + pricing: %{ + components: [ + %{id: "token.input", kind: "token", per: 1_000_000, rate: 1.0}, + %{id: "token.output", kind: "token", per: 1_000_000, rate: 2.0} + ] + } + } + + usage = %{ + input_tokens: 1_000_000, + output_tokens: 500_000 + } + + assert {:ok, cost} = Billing.calculate(usage, model) + assert cost.input_cost == 1.0 + assert cost.output_cost == 1.0 + assert cost.reasoning_cost == 0.0 + assert cost.total == 2.0 + end end