Valutazione
Gli harness di valutazione per QA ed estrazione — metriche QA a quattro gate, accuratezza dell'estrazione per canale e un gate di regressione sulla baseline che si alza progressivamente e non si abbassa mai in modo silenzioso.
Il dominio eval (src/ragspine/eval/) è il ciclo chiuso di valutazione di RAGSpine: due harness
(QA ed estrazione) che assegnano un punteggio al motore rispetto a golden set sotto controllo di versione, più un
gate sulla baseline che fa fallire la CI in caso di regressione. La sua invariante guida:
Il gate sulla baseline si alza progressivamente, non si abbassa mai — una regressione deve far fallire il gate, mai abbassarlo silenziosamente. Non indebolire mai un golden o una baseline per far passare un caso.
Struttura
I dati golden e di baseline risiedono in data/golden/ (forzati sotto controllo di versione), e le
fixture di ground truth per l'estrazione in data/fixtures/.
Valutazione QA — i quattro gate
qa_eval.py è l'harness a ciclo chiuso per il Q&A. Il punto di ingresso:
def run_qa_eval(
golden_path: str | Path,
mode: str = "tool",
kb_dir: str | Path | None = None,
) -> QAEvalReport: ...Carica e valida il golden set, costruisce una knowledge base sintetica deterministica
(build_eval_kb, a partire da _EVAL_FACT_ROWS + _EVAL_NARRATIVE_DOCS del modulo), esegue ogni
caso in una delle due modalità (EVAL_MODES = ("tool", "agent")) e restituisce un report a quattro gate.
modalità tool
Zero LLM, accesso diretto ai tool: parse_intent → clarify_scope → execute_query_metric / retrieve narrativo. Deterministica.
modalità agent
Orchestratore completo: answer_question con un MockProvider(reference_date=...) deterministico.
Ogni caso (GoldenCase: id, question, case_type, expected, tags,
reference_date) viene eseguito producendo un CaseOutcome normalizzato (route, modalità di chiarimento, valore/unità/fonte
trovati, flag di rifiuto, fonti, stati dei tool), quindi valutato rispetto ai quattro
gate — mantenuti come quattro pass rate separati, mai fusi in un unico numero:
Proprietà
Tipo
GATE_METRICS è la tupla di tutti e quattro. Una quinta metrica separata traccia le risposte inventate nei
casi di rifiuto:
FABRICATION = "fabrication" viene calcolata solo sui casi di rifiuto tramite
detect_fabricated_numbers(answer): rimuove i token di periodo (inseriti in whitelist a partire dalla dimensione
temporale del profilo attivo) e tratta qualsiasi cifra rimanente come risposta inventata. L'obiettivo è 0.
È deliberatamente non fusa nei quattro gate.
I tipi del report:
GateMetric—name,total,passed,pass_rate(1.0setotal == 0, altrimentipassed / total),failures,by_tag.QAEvalReport—mode,n_cases,metrics: dict[str, GateMetric]e unfabrication: GateMetricseparato (con una proprietàfabrication_count).evaluate(cases, outcomes, mode)applica le regole di tutti e quattro i gate per ciascun caso.
Gate di fedeltà e ancoraggio alle fonti (0.7.0+)
I quattro gate qui sopra valutano corrispondenza delle citazioni, rifiuto e chiarimento — ma non hanno mai verificato
che una risposta narrativa sia effettivamente implicata dagli snippet recuperati, che è esattamente
il punto in cui avviene l'allucinazione. eval/groundedness.py chiude questa lacuna con due nuovi gate
soggetti a ratchet:
Proprietà
Tipo
GROUNDEDNESS_METRICS = (faithfulness, answer_accuracy) sono nuove chiavi nello stesso
report.metrics, quindi confluiscono nello stesso ratchet della baseline dei quattro gate (sotto) —
ri-baselinati in 1.0 e verificati sia in --mode tool sia in --mode agent. I quattro
GATE_METRICS mantengono la loro semantica esatta; nulla viene fuso.
Il metodo predefinito è l'entailment offline deterministico basato su sovrapposizione lessicale
(LexicalOverlapJudge: un'affermazione è implicata se e solo se la sua copertura dei token di contenuto da parte del contesto supera
una soglia) — nessun modello, nessuna rete, quindi make ci lo verifica completamente offline. Il contesto è osservato
solo lato valutazione; il ciclo answer_question predefinito è invariato al byte.
Il giudice predefinito è un proxy lessicale, non un vero NLI — cieco a parafrasi, negazione e
inversione numerica; intercetta la forma più comune di risposta inventata (nuovi token assenti dal
contesto), dimostrato con un test inverso in cui un'affermazione non implicata fallisce il gate mentre un
eco fedele passa. Il vero giudice ONNX-NLI ([eval]) e un giudice LLM ([llm]) dietro la
cucitura EntailmentJudge (make_entailment_judge), più context-precision / recall /
answer-relevance, sono seguiti onesti.
Questo rende la prevenzione delle risposte inventate un blocco di regressione misurato sul canale narrativo, non solo asserito — l'invariante ora viene verificata dove fallisce davvero.
Gate di regressione sulla baseline QA
qa_eval.py possiede anche il confronto con la baseline:
make_baseline_entry(report) -> dict→{"metrics": {name: pass_rate}, "fabrication_count": int, "n_cases": int}.compare_to_baseline(report, baseline) -> BaselineComparison— fa fallire il gate quando ilpass_ratedi un qualsiasi gate è sotto la sua soglia di baseline, oppurefabrication_countsale sopra la baseline. Vengono verificate solo le metriche elencate nella baseline; l'uguaglianza esatta passa. Ogni regressione registrametric,baseline,current,delta.
Non esistono soglie hardcoded — le soglie sono i pass rate per modalità
memorizzati in precedenza in data/golden/qa_baseline.json. La baseline si alza progressivamente attraverso le esecuzioni
osservate.
Harness QA — la CLI
scripts/run_qa_eval.py pilota l'harness dalla radice del progetto:
.venv/bin/python scripts/run_qa_eval.py --mode tool.venv/bin/python scripts/run_qa_eval.py --mode agent --report out/qa_report.json.venv/bin/python scripts/run_qa_eval.py --mode tool --update-baselineFlag: --mode {tool,agent} (predefinito tool), --golden (predefinito
data/golden/qa_golden_set.jsonl), --report, --baseline (predefinito
data/golden/qa_baseline.json), --update-baseline. Il gate è indicizzato per modalità: una modalità
senza baseline ne genera automaticamente una alla prima esecuzione e passa (exit 0); con una baseline, qualsiasi
regressione di un gate o aumento delle risposte inventate esce con 1.
Formato dei dati golden
data/golden/qa_golden_set.jsonl contiene un oggetto JSON per riga. Ogni caso reca id,
question, case_type (uno tra numeric / clarification / refusal / narrative /
composite), expected (sempre clarification ∈ {none, ask_first, answer_with_assumptions}
e refuse: bool; numeric/composite aggiungono value + unit +
source; narrative/composite aggiungono narrative_doc), tags e un reference_date.
{
"id": "num-001",
"question": "香港FY2025的REVENUE是多少",
"case_type": "numeric",
"expected": {
"clarification": "none",
"refuse": false,
"value": 1702.0,
"unit": "USD_M",
"source": { "doc": "ACME_FY2025_Results.pptx", "locator": "slide=5,table=1,row=2,col=3" }
},
"tags": { "topic": "FIN", "scope": "ACME_HK", "qtype": "must_not_clarify" },
"reference_date": "2026-06-12"
}Il golden set utilizza l'azienda fittizia ACME e cifre sintetiche. data/golden/ è
tracciato forzatamente in modo che la baseline non possa scivolare fuori dal controllo di versione.
Valutazione dell'estrazione
extraction_eval.py assegna un punteggio all'estrazione da documenti d'ufficio per canale:
def run_eval(
facts: list[dict[str, object]],
ground_truth: dict[str, object] | list[dict[str, object]],
) -> EvalReport: ...Allinea i facts estratti rispetto alla ground truth tramite sheet!cell_ref, valutando solo le celle
presenti su entrambi i lati, e calcola l'accuratezza su tre canali:
| Canale | Costante | Campo del fatto confrontato |
|---|---|---|
| Valore della cella | CELL_VALUE = "cell_value" | value |
| Mappatura dei colori | COLOR_MAPPING = "color_mapping" | tags |
| Attribuzione dell'intestazione | HEADER_ATTRIBUTION = "header_attribution" | merge_span |
Tipi: ChannelMetric (name, total, correct, accuracy, mismatches), EvalReport
(channels: dict[str, ChannelMetric], n_facts). Il gate di regressione è
compare_to_baseline(metrics, baseline) -> BaselineComparison: qualsiasi canale il cui accuracy
scende sotto la propria soglia fallisce (passed=False); vengono verificati solo i canali elencati nella baseline;
l'uguaglianza esatta passa. Le fixture di ground truth risiedono in data/fixtures/ (es.
fixtures_ground_truth.json, pdf/pdf_ground_truth.json, pptx/pptx_ground_truth.json).
Vedi anche
Prevenzione delle risposte inventate
Il comportamento che le metriche di rifiuto e di risposte inventate proteggono.
Provenienza
La discendenza che il gate di validità delle citazioni verifica.
Agente
L'orchestratore esercitato nella valutazione in modalità agent.
CLI e script
run_qa_eval.py e gli altri script alla radice del repository.
Agent
Il livello di orchestrazione — parsing dell'intento a quattro slot, il gateway di chiarimento, un gate di sicurezza deterministico, l'instradamento a tre percorsi, il ciclo tool-use, la giunzione del provider LLM e la protezione di prevenzione delle risposte inventate per singolo percorso.
Flussi di lavoro
Scaffolding in linguaggio naturale, il catalogo di 1.000 template, normalizzazione JSON/YAML/TOML, compatibilità con Dify e n8n, preview v1 e confini di esecuzione.