""" Mandatory test case (Fase 9) — the academic analogue of the legal notebook's "uang lembur" test. Makes REAL LLM calls using the key configured in .env. python -m eval.test_case Verifies, for an English and an Indonesian version of the same question: 1. reasoning is present (the block was produced and parsed out), 2. the final answer cites >= 2 distinct references [n], 3. every cited [n] exists in the returned reference list (no hallucinated citation indices), 4. the Indonesian answer is actually in Indonesian even though the source papers are English (the multilingual claim, tested explicitly). """ from __future__ import annotations import json import re from pathlib import Path from eval.run_eval import _reset_eval_collections, ingest_corpus, EVAL_USER # The question spans TWO corpus papers (LoRA + QLoRA) so the >=2-citations # check is genuinely satisfiable — a question answerable from one paper would # punish the model for honest single-source citing. TEST_CASES = [ { "lang": "en", "question": ("Which parameter-efficient fine-tuning methods in my library " "allow adapting very large language models on limited GPU " "memory, and how do they differ?"), }, { "lang": "id", "question": ("Metode fine-tuning hemat parameter apa saja di library saya " "yang memungkinkan adaptasi model bahasa sangat besar dengan " "memori GPU terbatas, dan apa bedanya?"), }, ] _ID_MARKERS = re.compile( r"\b(yang|adalah|dengan|tidak|dapat|keterbatasan|jawaban|dokumen|utama|memungkinkan|perbedaan)\b", re.IGNORECASE, ) _EN_MARKERS = re.compile( r"\b(the|which|allows?|memory|difference|fine-tuning|weights|while|both)\b", re.IGNORECASE, ) def check_answer(response, lang: str) -> list[tuple[str, bool, str]]: """Run the mandatory checks; returns (name, passed, detail) rows.""" cited = sorted({int(n) for n in re.findall(r"\[(\d+)\]", response.answer)}) n_refs = len(response.references) checks = [ ("reasoning present", bool(response.reasoning.strip()), f"{len(response.reasoning)} chars"), (">= 2 distinct citations", len(cited) >= 2, f"cited: {cited}"), ("no hallucinated citation index", all(1 <= n <= n_refs for n in cited) and bool(cited), f"refs available: {n_refs}"), ("answer from local KB", response.source == "kb", f"source: {response.source}"), ] if lang == "id": checks.append(( "answer in Indonesian", len(_ID_MARKERS.findall(response.answer)) >= 3, response.answer[:80].replace("\n", " "), )) if lang == "en": checks.append(( "answer in English", len(_EN_MARKERS.findall(response.answer)) > len(_ID_MARKERS.findall(response.answer)), response.answer[:80].replace("\n", " "), )) return checks def main() -> None: from app.config import get_settings from app.rag import ask cfg = get_settings() key = cfg.groq_api_key or cfg.gemini_api_key model = cfg.default_model if not key: raise SystemExit("Requires GROQ_API_KEY or GEMINI_API_KEY in .env") corpus = json.loads( (Path(__file__).parent / "corpus.json").read_text(encoding="utf-8") ) print("[test-case] ingesting eval corpus ...") _reset_eval_collections() ingest_corpus(corpus) all_ok = True for case in TEST_CASES: print(f"\n=== [{case['lang']}] {case['question']}") response = ask(case["question"], api_key=key, model=model, user_id=EVAL_USER) print(f"--- reasoning ({len(response.reasoning)} chars):") print(" " + response.reasoning[:300].replace("\n", "\n ")) print("--- answer:") print(" " + response.answer[:600].replace("\n", "\n ")) for name, passed, detail in check_answer(response, case["lang"]): mark = "PASS" if passed else "FAIL" all_ok &= passed print(f" [{mark}] {name} ({detail})") _reset_eval_collections() print("\n" + ("ALL CHECKS PASSED" if all_ok else "SOME CHECKS FAILED")) raise SystemExit(0 if all_ok else 1) if __name__ == "__main__": main()