Spaces:
Running
Running
Sync from GitHub 73909d4496990ebe682e14cac6037513d7188032
Browse files- tests/test_llm_fact_eval.py +20 -5
- tests/test_uat_llm_judge.py +47 -5
tests/test_llm_fact_eval.py
CHANGED
|
@@ -223,8 +223,8 @@ CASES = build_cases() if os.getenv("RUN_LLM_EVAL") else []
|
|
| 223 |
def make_chain():
|
| 224 |
from src.rag.agent_chain import ExecutiveAgentChain
|
| 225 |
|
| 226 |
-
def _factory(lang: str):
|
| 227 |
-
return ExecutiveAgentChain(language=lang, session_id=f"eval_{lang}")
|
| 228 |
|
| 229 |
return _factory
|
| 230 |
|
|
@@ -233,12 +233,16 @@ def make_chain():
|
|
| 233 |
# catches gross regressions such as switching back to a reasoning model).
|
| 234 |
MAX_TURN_SECONDS = 25.0
|
| 235 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 236 |
|
| 237 |
-
|
| 238 |
-
def
|
| 239 |
from time import perf_counter
|
| 240 |
|
| 241 |
-
chain = make_chain(case["lang"])
|
| 242 |
turn_start = perf_counter()
|
| 243 |
result = chain.query(case["query"])
|
| 244 |
elapsed = perf_counter() - turn_start
|
|
@@ -266,3 +270,14 @@ def test_fact_eval(case, make_chain):
|
|
| 266 |
f"\nFORBIDDEN token found (cross-programme contamination): {forbidden}"
|
| 267 |
f"\nAnswer: {answer[:600]}"
|
| 268 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 223 |
def make_chain():
|
| 224 |
from src.rag.agent_chain import ExecutiveAgentChain
|
| 225 |
|
| 226 |
+
def _factory(lang: str, attempt: int = 0):
|
| 227 |
+
return ExecutiveAgentChain(language=lang, session_id=f"eval_{lang}_{attempt}")
|
| 228 |
|
| 229 |
return _factory
|
| 230 |
|
|
|
|
| 233 |
# catches gross regressions such as switching back to a reasoning model).
|
| 234 |
MAX_TURN_SECONDS = 25.0
|
| 235 |
|
| 236 |
+
# LLM answers are sampled, so a single case can flake even though the facts are
|
| 237 |
+
# in the prompt (observed on main: a different case failed on each smoke run).
|
| 238 |
+
# One retry with a fresh chain/session filters sampling noise; a real
|
| 239 |
+
# regression (wrong facts, cross-contamination, latency) still fails twice.
|
| 240 |
+
FLAKE_RETRIES = 1
|
| 241 |
|
| 242 |
+
|
| 243 |
+
def _assert_case(case, chain):
|
| 244 |
from time import perf_counter
|
| 245 |
|
|
|
|
| 246 |
turn_start = perf_counter()
|
| 247 |
result = chain.query(case["query"])
|
| 248 |
elapsed = perf_counter() - turn_start
|
|
|
|
| 270 |
f"\nFORBIDDEN token found (cross-programme contamination): {forbidden}"
|
| 271 |
f"\nAnswer: {answer[:600]}"
|
| 272 |
)
|
| 273 |
+
|
| 274 |
+
|
| 275 |
+
@pytest.mark.parametrize("case", CASES, ids=[c["id"] for c in CASES])
|
| 276 |
+
def test_fact_eval(case, make_chain):
|
| 277 |
+
for attempt in range(FLAKE_RETRIES + 1):
|
| 278 |
+
try:
|
| 279 |
+
_assert_case(case, make_chain(case["lang"], attempt))
|
| 280 |
+
return
|
| 281 |
+
except AssertionError:
|
| 282 |
+
if attempt == FLAKE_RETRIES:
|
| 283 |
+
raise
|
tests/test_uat_llm_judge.py
CHANGED
|
@@ -611,14 +611,56 @@ def test_uat_mixed_language_case_expects_english_clarification():
|
|
| 611 |
assert cases["TC-EDGE-05"].mixed_language_clarification_expected is True
|
| 612 |
|
| 613 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 614 |
def test_hard_facts_include_deadline_status_and_applicable_fee():
|
| 615 |
facts = _hard_facts()
|
| 616 |
|
| 617 |
-
|
| 618 |
-
|
| 619 |
-
assert facts["
|
| 620 |
-
|
| 621 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 622 |
|
| 623 |
|
| 624 |
def test_uat_transcript_starts_with_bot_greeting(monkeypatch):
|
|
|
|
| 611 |
assert cases["TC-EDGE-05"].mixed_language_clarification_expected is True
|
| 612 |
|
| 613 |
|
| 614 |
+
def test_tuition_deadline_status_enrichment_is_deterministic():
|
| 615 |
+
tuition = {
|
| 616 |
+
"first_deadline": {"deadline": "2026-03-31", "fee": 80000},
|
| 617 |
+
"final_deadline": {"deadline": "2026-06-30", "fee": 85000},
|
| 618 |
+
}
|
| 619 |
+
|
| 620 |
+
enriched = _tuition_with_deadline_status(tuition, date(2026, 6, 30))
|
| 621 |
+
|
| 622 |
+
assert enriched["first_deadline"]["deadline_status"] == "passed"
|
| 623 |
+
assert enriched["final_deadline"]["deadline_status"] == "due_today"
|
| 624 |
+
assert enriched["applicable_today"] == {
|
| 625 |
+
"deadline_type": "final_deadline",
|
| 626 |
+
"deadline": "2026-06-30",
|
| 627 |
+
"deadline_status": "due_today",
|
| 628 |
+
"fee": 85000,
|
| 629 |
+
}
|
| 630 |
+
|
| 631 |
+
all_passed = _tuition_with_deadline_status(tuition, date(2026, 7, 1))
|
| 632 |
+
assert all_passed["applicable_today"]["deadline_type"] == "final_deadline"
|
| 633 |
+
|
| 634 |
+
all_future = _tuition_with_deadline_status(tuition, date(2026, 1, 1))
|
| 635 |
+
assert all_future["applicable_today"]["deadline_type"] == "first_deadline"
|
| 636 |
+
assert all_future["applicable_today"]["fee"] == 80000
|
| 637 |
+
|
| 638 |
+
|
| 639 |
def test_hard_facts_include_deadline_status_and_applicable_fee():
|
| 640 |
facts = _hard_facts()
|
| 641 |
|
| 642 |
+
# generated_at moves with every daily facts refresh, so assert consistency
|
| 643 |
+
# against the file instead of hardcoding a snapshot date.
|
| 644 |
+
assert facts["reference_date"] == (facts["generated_at"] or "")[:10]
|
| 645 |
+
reference_date = date.fromisoformat(facts["reference_date"])
|
| 646 |
+
|
| 647 |
+
for programme in facts["programmes"].values():
|
| 648 |
+
tuition = programme["tuition_chf"]
|
| 649 |
+
for key in ("first_deadline", "final_deadline"):
|
| 650 |
+
entry = tuition[key]
|
| 651 |
+
assert entry["deadline_status"] == _deadline_status(
|
| 652 |
+
entry.get("deadline"), reference_date
|
| 653 |
+
)
|
| 654 |
+
|
| 655 |
+
applicable = tuition["applicable_today"]
|
| 656 |
+
chosen = tuition[applicable["deadline_type"]]
|
| 657 |
+
assert applicable["fee"] == chosen["fee"]
|
| 658 |
+
assert applicable["deadline"] == chosen["deadline"]
|
| 659 |
+
assert applicable["deadline_status"] == chosen["deadline_status"]
|
| 660 |
+
if tuition["first_deadline"]["deadline_status"] in {"future", "due_today"}:
|
| 661 |
+
assert applicable["deadline_type"] == "first_deadline"
|
| 662 |
+
else:
|
| 663 |
+
assert applicable["deadline_type"] == "final_deadline"
|
| 664 |
|
| 665 |
|
| 666 |
def test_uat_transcript_starts_with_bot_greeting(monkeypatch):
|