InsuranceBot / tests /test_credits_election.py
rohitsar567's picture
fix(voice+llm+admin): KI-200/201/202/203/204/205/206/207/208/210/211 bundle
e9cfc9f
Raw
History Blame Contribute Delete
17.1 kB
"""KI-085 (2026-05-15) β€” proactive credit tracking + election gate.
Tests:
1. update_credits_from_groq parser variants (valid / threshold / missing / malformed)
2. NIM local rate-meter increments + 60s reset
3. _is_election_eligible gates by credits_remaining vs credits_low_water
4. elect_primary_and_backup integration: credit-exhausted candidates skipped
Run:
.venv/bin/python -m unittest tests.test_credits_election -v
"""
from __future__ import annotations
import time
import unittest
from unittest import mock
from backend import llm_health
from backend.llm_health import (
GROQ_TOKENS_LOW_WATER,
ModelHealth,
NIM_REQ_PER_MIN_LOW_WATER,
_has_credits,
_is_election_eligible,
record_nim_call,
update_credits_from_groq,
)
def _fresh_state():
"""Wipe in-memory state + mark loaded so tests don't trigger disk reads."""
llm_health._STATE.clear()
llm_health._STATE_LOADED = True
llm_health._NIM_CALL_TIMES.clear()
def _healthy_now(model: str) -> ModelHealth:
"""Build a ModelHealth that passes every check EXCEPT credits β€” so
flipping credits is the only failure surface in these tests."""
h = ModelHealth(model=model)
h.status = "healthy"
h.latency_ms = 200
h.tested_at = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
return h
class TestGroqHeaderParser(unittest.TestCase):
def setUp(self) -> None:
_fresh_state()
def test_546_tokens_remaining_gates_out(self) -> None:
"""546 < 5000 low water β†’ credit-gate fails."""
model = "groq:llama-3.3-70b-versatile"
update_credits_from_groq("brain", model, {
"x-ratelimit-remaining-tokens-day": "546",
"x-ratelimit-reset-tokens-day": "1h2m",
})
h = llm_health._STATE[model]
self.assertEqual(h.credits_remaining, 546.0)
self.assertEqual(h.credits_unit, "tokens_day")
self.assertEqual(h.credits_low_water, GROQ_TOKENS_LOW_WATER)
self.assertFalse(_has_credits(h, time.monotonic()))
def test_50000_tokens_remaining_is_eligible(self) -> None:
"""50K >> 5K low water β†’ credit-gate passes."""
model = "groq:llama-3.3-70b-versatile"
update_credits_from_groq("brain", model, {
"x-ratelimit-remaining-tokens-day": "50000",
"x-ratelimit-reset-tokens-day": "5h",
})
h = llm_health._STATE[model]
self.assertEqual(h.credits_remaining, 50000.0)
self.assertTrue(_has_credits(h, time.monotonic()))
def test_missing_header_is_noop(self) -> None:
"""No daily-tokens header β†’ state untouched (credits_remaining stays None)."""
model = "groq:llama-3.3-70b-versatile"
update_credits_from_groq("brain", model, {})
self.assertNotIn(model, llm_health._STATE)
def test_malformed_value_is_noop(self) -> None:
"""Garbage in the header β†’ no-op + warning logged (not raised)."""
model = "groq:llama-3.3-70b-versatile"
update_credits_from_groq("brain", model, {
"x-ratelimit-remaining-tokens-day": "not-a-number",
})
# State remains absent because update bailed before stamping.
self.assertNotIn(model, llm_health._STATE)
def test_reset_string_parses_duration(self) -> None:
"""`1h2m` β†’ ~3720s offset; `45s` β†’ ~45s offset; `30m` β†’ ~1800s."""
now_mono = time.monotonic()
from backend.llm_health import _parse_reset_seconds
v = _parse_reset_seconds("1h2m", now_mono)
self.assertIsNotNone(v)
self.assertAlmostEqual(v - now_mono, 3720, delta=2)
v = _parse_reset_seconds("45s", now_mono)
self.assertAlmostEqual(v - now_mono, 45, delta=2)
v = _parse_reset_seconds("30m", now_mono)
self.assertAlmostEqual(v - now_mono, 1800, delta=2)
# Bare seconds-from-now
v = _parse_reset_seconds("120", now_mono)
self.assertAlmostEqual(v - now_mono, 120, delta=2)
# Empty / None
self.assertIsNone(_parse_reset_seconds("", now_mono))
self.assertIsNone(_parse_reset_seconds(None, now_mono))
class TestNimRateMeter(unittest.TestCase):
def setUp(self) -> None:
_fresh_state()
def test_30_calls_in_60s_eligible(self) -> None:
"""30 successful calls β†’ 40-30=10 remaining, > 5 low water β†’ eligible."""
model = "qwen/qwen3-next-80b-a3b-instruct"
for _ in range(30):
record_nim_call("brain", model)
h = llm_health._STATE[model]
self.assertEqual(h.credits_remaining, 10.0)
self.assertEqual(h.credits_unit, "requests_min")
self.assertTrue(_has_credits(h, time.monotonic()))
def test_36_calls_in_60s_gated_out(self) -> None:
"""36 successful calls β†’ 40-36=4 remaining, < 5 low water β†’ gated."""
model = "qwen/qwen3-next-80b-a3b-instruct"
for _ in range(36):
record_nim_call("brain", model)
h = llm_health._STATE[model]
self.assertEqual(h.credits_remaining, 4.0)
self.assertFalse(_has_credits(h, time.monotonic()))
def test_60s_window_resets(self) -> None:
"""After 60s elapses, _has_credits flips back to True via stale-reset."""
model = "qwen/qwen3-next-80b-a3b-instruct"
for _ in range(36):
record_nim_call("brain", model)
h = llm_health._STATE[model]
self.assertFalse(_has_credits(h, time.monotonic()))
# Simulate 65 seconds passing: credits_reset_at is now in the past.
future_now = (h.credits_reset_at or time.monotonic()) + 5.0
self.assertTrue(_has_credits(h, future_now))
class TestElectionCreditGate(unittest.TestCase):
"""Integration: elect_primary skips quota-exhausted candidates."""
def setUp(self) -> None:
_fresh_state()
def test_groq_below_water_skipped_in_election(self) -> None:
"""Groq has fast probe latency but is below daily-tokens water β†’
elector falls through to next candidate."""
groq_model = "groq:llama-3.3-70b-versatile"
nim_model = "qwen/qwen3-next-80b-a3b-instruct"
# Both healthy + fresh probe + low latency. Groq is faster (100ms).
gh = _healthy_now(groq_model)
gh.latency_ms = 100
gh.credits_remaining = 100.0 # < 5000 low_water
gh.credits_unit = "tokens_day"
gh.credits_low_water = GROQ_TOKENS_LOW_WATER
gh.credits_observed_at = time.monotonic()
llm_health._STATE[groq_model] = gh
nh = _healthy_now(nim_model)
nh.latency_ms = 300
llm_health._STATE[nim_model] = nh
with mock.patch.object(
llm_health, "_chain_for", return_value=[groq_model, nim_model]
):
primary = llm_health.get_primary("brain")
self.assertEqual(primary, nim_model,
"Quota-exhausted Groq should be skipped despite faster latency.")
def test_nim_preferred_over_faster_groq_when_eligible(self) -> None:
"""KI-201 chain-order: chain head wins regardless of latency.
Under KI-201 the chain-head model is always primary when eligible.
Pre-KI-201 (KI-087) the elector applied a NIM-first preference on
top of latency-score; that preference is now redundant because the
canonical chains in nvidia_nim_llm.py list NIM models first by
construction. This test exercises the new contract: with NIM as
chain-head and Groq slower in the chain, NIM wins on POSITION
not on latency-vs-NIM-preference."""
groq_model = "groq:llama-3.3-70b-versatile"
nim_model = "qwen/qwen3-next-80b-a3b-instruct"
gh = _healthy_now(groq_model)
gh.latency_ms = 100 # faster (irrelevant under KI-201)
gh.credits_remaining = 10000.0 # well above water
gh.credits_unit = "tokens_day"
gh.credits_low_water = GROQ_TOKENS_LOW_WATER
gh.credits_observed_at = time.monotonic()
llm_health._STATE[groq_model] = gh
nh = _healthy_now(nim_model)
nh.latency_ms = 300 # slower but chain-head
llm_health._STATE[nim_model] = nh
# NIM is chain-head; under chain-order election it wins regardless
# of Groq being faster.
with mock.patch.object(
llm_health, "_chain_for", return_value=[nim_model, groq_model]
):
primary = llm_health.get_primary("brain")
self.assertEqual(
primary, nim_model,
"KI-201: chain-head wins regardless of latency. NIM is listed "
"first in BRAIN_CHAIN by construction (KI-175); chain-order "
"election picks it without needing a separate NIM-first rule.",
)
def test_groq_picked_when_nim_pool_empty(self) -> None:
"""KI-201 chain-order fallthrough: when NO eligible NIM candidate
exists (all NIM models down / out of credits / not in chain),
election walks the chain in order and picks the FIRST eligible
non-NIM candidate. Locks in the safety net so a full NIM regional
outage still produces a working brain call.
Pre-KI-201 (KI-087) this test asserted Groq won via latency-score
even when listed second in the chain. Under chain-order election
the chain is the truth, so we list Groq first to exercise the
same fallthrough behaviour."""
groq_model = "groq:llama-3.3-70b-versatile"
or_model = "openrouter:openai/gpt-oss-120b"
gh = _healthy_now(groq_model)
gh.latency_ms = 100
gh.credits_remaining = 10000.0
gh.credits_unit = "tokens_day"
gh.credits_low_water = GROQ_TOKENS_LOW_WATER
gh.credits_observed_at = time.monotonic()
llm_health._STATE[groq_model] = gh
oh = _healthy_now(or_model)
oh.latency_ms = 800 # slower (irrelevant under KI-201 chain-order)
llm_health._STATE[or_model] = oh
# Chain has NO NIM candidates; Groq is chain-head.
with mock.patch.object(
llm_health, "_chain_for", return_value=[groq_model, or_model]
):
primary = llm_health.get_primary("brain")
self.assertEqual(
primary, groq_model,
"KI-201 chain-order: no NIM eligible β†’ election picks the "
"FIRST eligible candidate in chain order (Groq is chain-head).",
)
def test_none_credits_is_permissive(self) -> None:
"""Cold-start: a candidate with credits_remaining=None must be electable."""
model = "qwen/qwen3-next-80b-a3b-instruct"
h = _healthy_now(model)
# credits_remaining is None by default β€” leave it alone.
llm_health._STATE[model] = h
self.assertTrue(_is_election_eligible(h, time.monotonic()),
"Cold-start (None credits) must NOT gate out a healthy candidate.")
class TestStrictChainCreditExhausted(unittest.TestCase):
"""KI-122 (2026-05-15) β€” strict banner rule.
Banner fires ONLY when zero chain members are available_for_calls AND at
least one has a credit-exhaustion signal that's strictly in-window
(credits_remaining<=low_water AND credits_reset_at is in the future).
Pre-KI-122 holes the new rule must close:
H1) one healthy NIM primary (credits=None) + one credit-low Groq backup
β†’ banner USED to fire because `any_signal=True` on Groq + skip on
NIM's None signal. Must NOT fire.
H2) every chain member has reset_at=None (e.g. OpenRouter wallet) and
credits below water β†’ banner USED to fire. Must NOT fire by itself
(probe success is the authoritative truth; wallet-zero with
successful probes means free-tier models).
H3) every member is genuinely gated (low + future reset) AND not
available β†’ banner SHOULD fire.
"""
def setUp(self) -> None:
_fresh_state()
# Import locally so test discovery doesn't load admin until needed.
from backend.admin import _chain_summary
self._chain_summary = _chain_summary
def _build(self, chain_models, model_states):
"""Inject states + run _chain_summary against a mocked chain."""
for m, h in model_states.items():
llm_health._STATE[m] = h
now_mono = time.monotonic()
with mock.patch.object(
llm_health, "_chain_for", return_value=list(chain_models),
):
return self._chain_summary(
"brain", {"brain": list(chain_models)}, llm_health._STATE, now_mono,
)
def test_healthy_nim_plus_exhausted_groq_does_not_fire_banner(self) -> None:
"""H1 β€” NIM primary HEALTHY/None-credits + Groq backup credit-low.
Banner must STAY DOWN because NIM is available_for_calls."""
nim = "qwen/qwen3-next-80b-a3b-instruct"
groq = "groq:llama-3.3-70b-versatile"
nh = _healthy_now(nim)
nh.latency_ms = 300
# NIM has NO credit signal (None) β€” cold start permissive.
gh = _healthy_now(groq)
gh.latency_ms = 100
gh.credits_remaining = 100.0
gh.credits_unit = "tokens_day"
gh.credits_low_water = GROQ_TOKENS_LOW_WATER
gh.credits_reset_at = time.monotonic() + 3600.0 # future reset
gh.credits_observed_at = time.monotonic()
block = self._build([nim, groq], {nim: nh, groq: gh})
self.assertFalse(
block["chain_credit_exhausted"],
"Banner must NOT fire when a healthy cold-start NIM primary "
"shares a chain with one credit-exhausted Groq backup.",
)
# Sanity: NIM should be in_use, Groq should be gated.
names = {mi["model"]: mi for mi in block["chain_members"]}
self.assertTrue(names[nim]["available_for_calls"])
self.assertTrue(names[nim]["is_current_primary"])
self.assertFalse(names[groq]["available_for_calls"])
self.assertTrue(names[groq]["credit_exhausted"])
def test_credits_reset_at_none_does_not_fire_banner(self) -> None:
"""H2 β€” every member has reset_at=None + credits below water (typical
OpenRouter usd_balance free-tier shape). Banner must NOT fire on the
reset_at=None signal alone; the strict rule requires an in-window reset."""
m1 = "openrouter:openai/gpt-oss-120b"
m2 = "openrouter:meta-llama/llama-3.3-70b-instruct:free"
h1 = _healthy_now(m1)
h1.latency_ms = 800
h1.credits_remaining = 0.0
h1.credits_unit = "usd_balance"
h1.credits_low_water = 0.05
h1.credits_reset_at = None # OpenRouter wallet: no scheduled reset
h1.credits_observed_at = time.monotonic()
h2 = _healthy_now(m2)
h2.latency_ms = 900
h2.credits_remaining = 0.0
h2.credits_unit = "usd_balance"
h2.credits_low_water = 0.05
h2.credits_reset_at = None
h2.credits_observed_at = time.monotonic()
block = self._build([m1, m2], {m1: h1, m2: h2})
self.assertFalse(
block["chain_credit_exhausted"],
"Banner must NOT fire when credits_reset_at is None on every "
"member β€” usd_balance=$0 with healthy probes is a free-tier "
"signal, not a real outage.",
)
def test_all_in_window_exhausted_fires_banner(self) -> None:
"""H3 β€” every chain member has credits<=low_water AND a future reset
AND is consequently NOT available_for_calls. Banner SHOULD fire."""
m1 = "groq:llama-3.3-70b-versatile"
m2 = "groq:meta-llama/llama-4-scout-17b-16e-instruct"
future = time.monotonic() + 3600.0
h1 = _healthy_now(m1)
h1.latency_ms = 100
h1.credits_remaining = 100.0
h1.credits_unit = "tokens_day"
h1.credits_low_water = GROQ_TOKENS_LOW_WATER
h1.credits_reset_at = future
h1.credits_observed_at = time.monotonic()
h2 = _healthy_now(m2)
h2.latency_ms = 110
h2.credits_remaining = 50.0
h2.credits_unit = "tokens_day"
h2.credits_low_water = GROQ_TOKENS_LOW_WATER
h2.credits_reset_at = future
h2.credits_observed_at = time.monotonic()
block = self._build([m1, m2], {m1: h1, m2: h2})
self.assertTrue(
block["chain_credit_exhausted"],
"Banner SHOULD fire when every chain member is strictly "
"credit-exhausted with an in-window future reset.",
)
def test_chain_block_carries_current_primary_and_last_probe(self) -> None:
"""KI-122 β€” verify the new wire fields are populated."""
nim = "qwen/qwen3-next-80b-a3b-instruct"
nh = _healthy_now(nim)
nh.latency_ms = 250
block = self._build([nim], {nim: nh})
self.assertEqual(block["current_primary"], nim)
self.assertTrue(block["current_primary_available"])
self.assertIsNotNone(block["last_probe_at"])
self.assertIsNotNone(block["last_probe_age_seconds"])
self.assertGreaterEqual(block["last_probe_age_seconds"], 0.0)
if __name__ == "__main__":
unittest.main(verbosity=2)