test/openagents/chat/token_usage_test.exs

main at 58e6347eeb72 · 2 KB

defmodule OpenAgents.Chat.TokenUsageTest do
  @moduledoc """
  The chat console reports token counts twice — once per turn, once for the
  conversation — and the two surfaces disagreed in production: every turn's line
  omitted reasoning while the running list read `Reasoning 0`. Both now ask this
  module, and a turn's line is the report over a list of one, so the cases below
  each assert the whole-conversation answer and the per-turn answer together.
  """

  use ExUnit.Case, async: true

  alias OpenAgents.Chat.TokenUsage

  defp reasoning(usages), do: Enum.find(TokenUsage.counts(usages), &(elem(&1, 0) == :reasoning))

  test "a reported positive count reaches both the turn and the conversation" do
    usage = %{input: 24, output: 8, reasoning: 3, cached: 4, total: 32}

    assert reasoning([usage]) == {:reasoning, "Reasoning", 3}
    assert reasoning([usage, usage]) == {:reasoning, "Reasoning", 6}
  end

  test "a count every turn reported as zero stays a reported zero" do
    usage = %{input: 24, output: 8, reasoning: 0, cached: 4, total: 32}

    assert reasoning([usage]) == {:reasoning, "Reasoning", 0}
    assert reasoning([usage, usage]) == {:reasoning, "Reasoning", 0}
  end

  test "a count no turn reported stays off both reports" do
    usage = %{input: 24, output: 8, reasoning: nil, cached: 4, total: 32}

    assert reasoning([usage]) == nil
    assert reasoning([usage, usage]) == nil
  end

  test "a zero beside an unreported count reports nothing rather than a count of none" do
    measured = %{input: 24, output: 8, reasoning: 0, cached: 4, total: 32}
    unreported = %{input: 30, output: 10, reasoning: nil, cached: 2, total: 40}

    assert reasoning([measured]) == {:reasoning, "Reasoning", 0}
    assert reasoning([unreported]) == nil
    assert reasoning([measured, unreported]) == nil
  end

  test "a positive count survives a turn that reported none of that kind" do
    measured = %{input: 24, output: 8, reasoning: 3, cached: 4, total: 32}
    unreported = %{input: 30, output: 10, reasoning: nil, cached: 2, total: 40}

    assert reasoning([measured, unreported]) == {:reasoning, "Reasoning", 3}
  end

  test "counts keep their display order and drop only the kinds nothing reported" do
    usage = %{input: 24, output: 8, reasoning: nil, cached: 4, total: 32}

    assert TokenUsage.counts([usage]) == [
             {:input, "Input", 24},
             {:output, "Output", 8},
             {:cached, "Cached", 4}
           ]
  end

  test "a total says whether the provider reported it" do
    reported = %{input: 24, output: 8, reasoning: nil, cached: nil, total: 32}
    derived = %{input: 24, output: 8, reasoning: nil, cached: nil, total: nil}

    assert TokenUsage.total([]) == nil
    assert TokenUsage.total([reported]) == {32, :reported}
    assert TokenUsage.total([derived]) == {32, :derived}
    assert TokenUsage.total([reported, derived]) == {64, :derived}
  end
end