"""Tests for the per-model prompt-token calibration."""
from __future__ import annotations
import pytest
from navi.core.token_calibration import (
DEFAULT_CHARS_PER_TOKEN,
TokenCalibration,
calibration_for,
observe_prompt,
reset_calibration,
)
@pytest.fixture(autouse=True)
def _clean_registry():
reset_calibration()
yield
reset_calibration()
class TestDefault:
def test_without_observations_uses_the_conservative_default(self):
cal = TokenCalibration()
# chars // 3 would say 1000; the default is deliberately higher because
# chars // 3 was measured ~1.8x below the real prompt size on prod.
assert cal.tokens_for(1800) == int(1800 / DEFAULT_CHARS_PER_TOKEN)
assert cal.tokens_for(1800) > 1800 // 3
def test_one_observation_is_not_enough_for_a_line(self):
cal = TokenCalibration()
cal.observe(100_000, 60_000)
assert cal._line() is None
assert cal.tokens_for(50_000) == int(50_000 / DEFAULT_CHARS_PER_TOKEN)
class TestLine:
def test_two_observations_pin_overhead_and_slope(self):
cal = TokenCalibration()
# overhead 10k tokens, 2 chars per token
cal.observe(100_000, 60_000)
cal.observe(40_000, 30_000)
intercept, slope = cal._line()
assert intercept == pytest.approx(10_000, rel=0.01)
assert slope == pytest.approx(0.5, rel=0.01)
assert cal.tokens_for(200_000) == pytest.approx(110_000, rel=0.01)
def test_predicts_a_smaller_kept_region_in_real_units(self):
"""The point of the line: a hypothetical post-compression context has to
be priced in real tokens, overhead included. A pure ratio would call
10k chars '5.6k tokens' where the real prompt is 15k."""
cal = TokenCalibration()
cal.observe(100_000, 60_000)
cal.observe(40_000, 30_000)
assert cal.tokens_for(10_000) == pytest.approx(15_000, rel=0.01)
def test_delta_pricing_drops_the_fixed_overhead(self):
"""Appended content is priced at the slope: the 10k overhead is already
inside the real baseline count the delta is added to."""
cal = TokenCalibration()
cal.observe(100_000, 60_000)
cal.observe(40_000, 30_000)
assert cal.delta_for(10_000) == pytest.approx(5_000, rel=0.01)
assert cal.delta_for(10_000) < cal.tokens_for(10_000)
def test_delta_without_a_line_uses_the_default_ratio(self):
cal = TokenCalibration()
assert cal.delta_for(1800) == int(1800 / DEFAULT_CHARS_PER_TOKEN)
def test_observations_with_equal_size_do_not_fit(self):
cal = TokenCalibration()
cal.observe(50_000, 30_000)
cal.observe(51_000, 30_500) # 2% apart — slope would be noise
assert cal._line() is None
def test_an_implausible_slope_is_rejected(self):
cal = TokenCalibration()
# 10 tokens per char can only mean one prompt was truncated server-side.
cal.observe(10_000, 100_000)
cal.observe(20_000, 200_000)
assert cal._line() is None
assert cal.tokens_for(1800) == int(1800 / DEFAULT_CHARS_PER_TOKEN)
def test_a_negative_intercept_is_clamped_to_zero(self):
cal = TokenCalibration()
cal.observe(100_000, 40_000)
cal.observe(50_000, 20_500)
intercept, _ = cal._line()
assert intercept >= 0
def test_only_the_last_observations_are_kept(self):
cal = TokenCalibration()
for i in range(20):
cal.observe(10_000 + i * 10_000, 5_000 + i * 5_000)
assert cal.observations == 8
class TestRegistry:
def test_calibration_is_per_model(self):
observe_prompt("glm", 100_000, 60_000)
observe_prompt("glm", 40_000, 30_000)
assert calibration_for("glm").observations == 2
assert calibration_for("gemma").observations == 0
def test_model_may_be_the_profile_priority_list(self):
observe_prompt(["glm-5.3-flash:cloud", "gemma4:31b-cloud"], 1000, 500)
assert calibration_for("GLM-5.3-FLASH:cloud").observations == 1
def test_reset_drops_every_model(self):
observe_prompt("glm", 1000, 500)
reset_calibration()
assert calibration_for("glm").observations == 0