101 lines
8.2 KiB
Python
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()
|