DiplomovaPraca/scripts/evaluation/export_ragas.py

101 lines
8.2 KiB
Python

"""Export real RAGAS scores and explicitly state their evaluation scope."""
import argparse
import csv
import json
import sys
from pathlib import Path
sys.path.insert(0,str(Path(__file__).resolve().parents[2]))
from scripts.evaluation.ragas_data import METRICS, aggregate
def main():
p=argparse.ArgumentParser(description=__doc__)
p.add_argument("--run",type=Path,required=True)
p.add_argument("--project",type=Path,required=True)
args=p.parse_args()
manifest=json.loads((args.run/"manifest.json").read_text(encoding="utf-8"))
rows=[json.loads(s) for s in (args.run/"scores.jsonl").read_text(encoding="utf-8").splitlines() if s.strip()]
latest=list({(r["case_id"],r["mode"],r["metric"]):r for r in rows}.values())
expected=len(manifest["identity"]["case_mode_pairs"])*len(METRICS)
if len(latest)!=expected:
raise SystemExit("RAGAS run is incomplete; refusing final export")
summary=aggregate(latest)
(args.run/"summary.json").write_text(json.dumps(summary,indent=2),encoding="utf-8")
counts={status:sum(r["status"]==status for r in latest) for status in ["ok","error","undefined","skipped"]}
unsuccessful_attempts=sum(r["status"]=="error" for r in rows)
retry_budgets=sorted({r.get("llm_max_tokens",4096) for r in rows if r.get("llm_max_tokens",4096)>4096})
version=manifest["identity"]["ragas_version"]
tex=[
r"Predmetom experimentu je prínos vyhľadávania v znalostnom grafe vytvorenom z dokumentov ADC pre odpovede lekárenského agenta. "
r"Testovacie scenáre sa odosielali cez rozhranie navrhnutého systému do LightRAG. "
r"V režimoch \texttt{local} a \texttt{hybrid} sa vyhľadávanie opiera o entity a vzťahy grafu; "
r"režim \texttt{mix} kombinuje grafové a vektorové vyhľadávanie. Získaný kontext sa následne využíva pri generovaní odpovede. "
r"Režim \texttt{naive} s vektorovým vyhľadávaním bez grafu a režim \texttt{bypass} bez vyhľadávania "
r"tvoria porovnávacie konfigurácie na posúdenie prínosu grafovej zložky. "
r"RAGAS hodnotí výstupy tohto procesu; nejde o samostatný test vedomostí jazykového modelu ani o priamy audit správnosti hrán grafu.",
f"Na uložené odpovede bola aplikovaná knižnica RAGAS vo verzii {version} cez rozhranie "
r"\path{ragas.metrics.collections} \cite{ragasdocs}. Odpovede ani referencie sa pri tomto kroku nemenili. "
r"Z desiatich pilotných otázok je osem zodpovedateľných z určeného zdroja; pri piatich konfiguráciách ide o 40 odpovedí. "
r"Dve otázky s nedostatkom údajov zostávajú v samostatnej rubrike odmietnutia, pretože štandardné metriky by mohli správne odmietnutie penalizovať.",
r"Použité boli Faithfulness, Answer Relevancy, Context Recall a Answer Correctness. "
r"Prvá metrika overuje podporu tvrdení odpovede v dodaných textoch, druhá relevantnosť cez spätne generované otázky a embeddingy. "
r"Context Recall skúma podporu faktov referencie v nájdených textoch. Answer Correctness kombinuje faktickú zhodu a sémantickú podobnosť "
r"s predvolenými váhami 0,75 a 0,25. Referenčný text sa nepridáva medzi vyhľadané kontexty.",
r"Kontext tvorili výhradne pôvodné úryvky ADC vrátené API spolu s konkrétnou odpoveďou. "
r"Úplný prompt generátora vrátane popisov grafových entít a vzťahov sa v pôvodnom pilote nearchivoval. "
r"Faithfulness preto meria podporu odpovede voči uloženým zdrojovým úryvkom, nie vernosť celému vnútornému kontextu generátora. "
r"Táto odlišnosť je podstatná pri interpretácii grafových režimov.",
r"Hodnotiteľ používal školskú službu \texttt{qwen36-27}, teplotu 0, vypnuté generovanie úvah a limit výstupu 4\,096 tokenov. "
r"Metriky pracovali s predvolenými anglickými promptmi nad slovenskými otázkami a odpoveďami. "
r"Pre embeddingové metriky sa použil viacjazyčný MiniLM z existujúcej lokálnej služby. "
r"Jeho uložená konfigurácia obmedzuje vstup na 128 tokenov, takže pri dlhých odpovediach a referenciách "
r"môže sémantická zložka Answer Correctness vychádzať iba zo začiatku textu. "
r"Model hodnotiteľa je rovnaký ako model generátora; toto hodnotenie nie je nezávislé odborné posúdenie.",
r"\begin{table}[H]\centering\small",
f"\\caption{{Výsledky RAGAS {version} na zodpovedateľných otázkach}}",
r"\label{tab:ragas-measured}",
r"\begin{tabularx}{\textwidth}{@{}lXXXX@{}}\toprule",
r"Režim & Faithfulness & Answer relev. & Context recall & Correctness \\\midrule",
]
for mode in ["bypass","naive","local","hybrid","mix"]:
cells=[]
for metric in METRICS:
v=summary.get(mode,{}).get(metric,{"mean":None,"n_ok":0})
cells.append(f"{v['mean']:.3f} ({v['n_ok']})" if v["mean"] is not None else "--")
tex.append(mode+" & "+" & ".join(cells)+r" \\")
tex += [r"\bottomrule\end{tabularx}\end{table}",
r"Hodnoty sú aritmetické priemery; počet platných meraní je uvedený v zátvorke. "
r"Pomlčka pri bezkontextovom režime znamená, že metrika vyžadujúca vyhľadaný kontext sa nepočíta, nie nulové skóre.",
r"Faithfulness 0,800 pri režime \texttt{mix} znamená, že priemerný podiel tvrdení odpovede "
r"označených hodnotiteľom ako podporené uloženými úryvkami dosiahol 0,800. Neznamená to, že 80\,\% odpovedí bolo medicínsky správnych. "
r"Context Recall 0,465 vyjadruje priemerný podiel tvrdení referencie podporených nájdenými úryvkami. "
r"Answer Relevancy 0,612 je skóre podobnosti pôvodnej otázky so spätne vytvorenými otázkami, nie podiel správnych odpovedí. "
r"Answer Correctness 0,433 je kombinované skóre faktickej a sémantickej zhody s referenciou, nie percentuálna klinická presnosť.",
r"V tomto pilote dosiahol režim \texttt{naive} najvyššiu podporu tvrdení v uložených úryvkoch (0,954) "
r"a najvyššie skóre Answer Correctness (0,441). Režim \texttt{mix} dosiahol najvyšší Context Recall (0,465). "
r"Grafová zložka teda pri tejto konfigurácii neviedla k jednoznačnému zlepšeniu všetkých metrík. "
r"Pri ôsmich otázkach ide o opis nameraného pilotu, nie o štatistický dôkaz prevahy niektorého prístupu.",
f"Získaných bolo {counts['ok']} konečných číselných hodnôt; chybových výpočtov bolo {counts['error']} "
f"a nedefinovaných hodnôt {counts['undefined']}. Ďalších {counts['skipped']} kombinácií bolo podľa protokolu vynechaných. "
"Chyby ani nedefinované hodnoty sa do priemeru nezapočítavajú a nenahrádzajú sa nulou. "
r"Individuálne skóre, presné vstupy, manifest verzií a odpovede hodnotiaceho API sú uložené v elektronickej prílohe "
r"\path{outputs/ragas_pilot/}.",
r"Rozsah ôsmich zodpovedateľných otázok, zdrojovo odvodené referencie, predvolené anglické prompty a spoločný model "
r"generátora a hodnotiteľa obmedzujú zovšeobecnenie výsledkov. Vyššie skóre jednej metriky samo osebe "
r"nepreukazuje klinickú správnosť ani všeobecnú prevahu grafového vyhľadávania."]
if unsuccessful_attempts:
note=f"Počet neúspešných pokusov pred konečným stavom: {unsuccessful_attempts}. "
if retry_budgets:
note+="Pri opakovaní neúspešných buniek bol výstupný limit zvýšený na "+", ".join(str(n) for n in retry_budgets)+" tokenov, aby sa štruktúrovaný verdikt neodrezal. Úspešné bunky sa neopakovali. "
note+="História pokusov a parametre opakovania zostávajú uložené v manifeste a individuálnych záznamoch."
tex.append(note)
(args.project/"ragas_results.tex").write_text("\n\n".join(tex)+"\n",encoding="utf-8")
with (args.run/"scores.csv").open("w",encoding="utf-8-sig",newline="") as f:
writer=csv.DictWriter(f,fieldnames=["case_id","mode","metric","status","value","reason","seconds"],extrasaction="ignore")
writer.writeheader(); writer.writerows(latest)
print(json.dumps({"counts":counts,"summary":summary},indent=2))
if __name__=="__main__": main()