Pygmales commited on
Commit
95b5fa3
·
verified ·
1 Parent(s): e5d702c

Sync from GitHub 73909d4496990ebe682e14cac6037513d7188032

Browse files
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
- @pytest.mark.parametrize("case", CASES, ids=[c["id"] for c in CASES])
238
- def test_fact_eval(case, make_chain):
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
- assert facts["reference_date"] == "2026-06-30"
618
- assert facts["programmes"]["iemba"]["tuition_chf"]["first_deadline"]["deadline_status"] == "passed"
619
- assert facts["programmes"]["iemba"]["tuition_chf"]["final_deadline"]["deadline_status"] == "due_today"
620
- assert facts["programmes"]["iemba"]["tuition_chf"]["applicable_today"]["fee"] == 85000
621
- assert facts["programmes"]["emba_x"]["tuition_chf"]["applicable_today"]["fee"] == 99000
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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):