[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"blog-it-llm-golden-set-regression-protocol":3},{"id":4,"title":5,"author":6,"body":7,"cover":1768,"description":1769,"draft":1770,"extension":1771,"locale":1772,"meta":1773,"navigation":1774,"path":1775,"publishedAt":1776,"seo":1777,"seoTitle":1778,"slug":1779,"stem":1780,"tags":1781,"__hash__":1785},"blog\u002Fit\u002Fblog\u002Fllm-golden-set-regression-protocol.md","Protocollo di regressione LLM: golden set, matcher deterministici e policy di aggiornamento","Manuel Tardivo",{"type":8,"value":9,"toc":1731},"minimark",[10,15,24,28,33,45,49,64,68,92,107,111,122,128,133,156,166,170,181,185,195,199,483,491,523,731,769,773,907,911,918,1066,1078,1082,1093,1097,1131,1135,1180,1184,1229,1247,1251,1279,1283,1307,1311,1318,1321,1336,1340,1384,1388,1423,1427,1495,1506,1510,1517,1521,1551,1555,1605,1608,1658,1662,1681,1685,1703,1707,1713,1727],[11,12,14],"h2",{"id":13},"per-chi-è-questo-pezzo","Per chi è questo pezzo",[16,17,18,19,23],"p",{},"Per chi ha già un sistema LLM in produzione (RAG, agente, classificatore) e ha vissuto almeno una volta: ",[20,21,22],"em",{},"«ho cambiato il prompt o aggiornato il modello e non so se ho rotto qualcosa»",". Non è un tutorial per chi parte da zero: assume che tu sappia cosa sia un prompt, un endpoint di inferenza e una pipeline di deploy.",[11,25,27],{"id":26},"il-problema-che-questo-risolve","Il problema che questo risolve",[29,30,32],"h3",{"id":31},"cambio-prompt","Cambio prompt",[16,34,35,36,40,41,44],{},"Modifichi wording, aggiungi contesto, stringi il formato di output. Il modello è lo stesso, ma la superficie di comportamento cambia spesso e a piccoli incrementi. Ti serve un modo ",[37,38,39],"strong",{},"deterministico"," per sapere se, sui ",[37,42,43],{},"casi noti",", il sistema risponde ancora secondo le asserzioni che consideri corrette (intent, campi JSON, assenza di frasi vietate).",[29,46,48],{"id":47},"cambio-modello","Cambio modello",[16,50,51,52,55,56,59,60,63],{},"Passi da una versione a un’altra (es. famiglia o snapshot diversi). Il nuovo modello può essere “migliore” in benchmark generici e peggiorare su ",[37,53,54],{},"i tuoi"," input reali. La regressione qui è ",[37,57,58],{},"rara"," ma ad ",[37,61,62],{},"alto impatto sistemico",": un solo deploy può alterare migliaia di risposte.",[29,65,67],{"id":66},"perché-separare-i-due-scenari","Perché separare i due scenari",[69,70,71,82],"ul",{},[72,73,74,77,78,81],"li",{},[37,75,76],{},"Cambio prompt:"," alta frequenza, impatto localizzato; vuoi feedback ",[37,79,80],{},"veloce"," in CI (pre-merge).",[72,83,84,87,88,91],{},[37,85,86],{},"Cambio modello:"," bassa frequenza, impatto globale; vuoi gate ",[37,89,90],{},"pre-deploy"," e spesso una finestra di review più larga.",[16,93,94,95,98,99,102,103,106],{},"Il punto operativo (non ideologico) sull’approccio “un LLM valuta l’output”: non è ",[37,96,97],{},"ripetibile"," al bit, non ",[37,100,101],{},"scala"," bene in CI senza varianza, e quando fallisce non ti dice ",[37,104,105],{},"quale asserzione"," ha rotto il contratto. Qui lo sostituiamo con golden set + matcher espliciti e policy di aggiornamento.",[11,108,110],{"id":109},"cosè-un-golden-set-e-cosa-non-è","Cos’è un golden set (e cosa non è)",[16,112,113,116,117,121],{},[37,114,115],{},"È:"," una collezione di casi ",[118,119,120],"code",{},"(input, asserzioni attese)"," che fissano il comportamento accettabile su esempi rappresentativi. Ogni caso è una piccola specifica eseguibile.",[16,123,124,127],{},[37,125,126],{},"Non è:"," dataset di fine-tuning, test suite esaustiva sulla lunga coda, né un archivio di “risposte perfette” da citare in tribunale. Non sostituisce il monitoraggio in produzione.",[16,129,130],{},[37,131,132],{},"Principi di selezione:",[69,134,135,142,149],{},[72,136,137,138,141],{},"priorità ai ",[37,139,140],{},"casi limite"," già visti fallire in produzione o in staging;",[72,143,144,145,148],{},"coprire le ",[37,146,147],{},"varianti principali"," dell’input (non tutte le permutazioni);",[72,150,151,152,155],{},"se il sistema ha ",[37,153,154],{},"più tipi di output"," (es. tool JSON vs risposta libera), includere almeno un caso per tipo.",[16,157,158,161,162,165],{},[37,159,160],{},"Dimensione:"," per un RAG o classificatore medio, ",[37,163,164],{},"20–50"," casi è una fascia ragionevole. Sotto ~20 il segnale è fragile; sopra ~200 la manutenzione diventa costosa senza tooling dedicato (split per dominio, ownership per team).",[11,167,169],{"id":168},"formato-file","Formato file",[16,171,172,173,176,177,180],{},"YAML è un buon default: leggibile in review, ",[37,174,175],{},"diff-friendly"," su Git, adatto a ",[118,178,179],{},"yamllint"," e a merge controllati.",[29,182,184],{"id":183},"struttura-consigliata-del-repository","Struttura consigliata del repository",[186,187,192],"pre",{"className":188,"code":190,"language":191},[189],"language-text","eval\u002F\n  golden\u002F\n    cases\u002F\n      refund-intent-001.yaml\n      shipping-delay-002.yaml\n    README.md\n","text",[118,193,190],{"__ignoreMap":194},"",[29,196,198],{"id":197},"singolo-caso-solo-matcher-fino-al-livello-2","Singolo caso (solo matcher fino al livello 2)",[186,200,204],{"className":201,"code":202,"language":203,"meta":194,"style":194},"language-yaml shiki shiki-themes github-light github-dark","id: refund-intent-001\ndescription: \"Utente chiede rimborso ordine con ID esplicito\"\ncreated_at: \"2026-05-01\"\nupdated_at: \"2026-05-01\"\nupdate_reason: \"Creazione caso da ticket #4412\"\ndeprecated: false\ninput:\n  user_message: \"Voglio il rimborso dell'ordine #1234\"\n  context: \"Cliente verificato; ultimo ordine spedito 10 giorni fa.\"\nmatchers:\n  - type: json_field\n    field: intent\n    op: eq\n    value: refund\n  - type: json_field\n    field: confidence\n    op: gte\n    value: 0.85\n  - type: contains\n    value: \"1234\"\n  - type: not_contains\n    value: \"non posso aiutarti\"\nlevel: 2\ntags:\n  - refund\n  - entity-extraction\n","yaml",[118,205,206,223,234,245,255,266,278,287,298,309,317,331,342,353,364,375,385,395,405,417,427,439,449,460,468,475],{"__ignoreMap":194},[207,208,211,215,219],"span",{"class":209,"line":210},"line",1,[207,212,214],{"class":213},"s9eBZ","id",[207,216,218],{"class":217},"sVt8B",": ",[207,220,222],{"class":221},"sZZnC","refund-intent-001\n",[207,224,226,229,231],{"class":209,"line":225},2,[207,227,228],{"class":213},"description",[207,230,218],{"class":217},[207,232,233],{"class":221},"\"Utente chiede rimborso ordine con ID esplicito\"\n",[207,235,237,240,242],{"class":209,"line":236},3,[207,238,239],{"class":213},"created_at",[207,241,218],{"class":217},[207,243,244],{"class":221},"\"2026-05-01\"\n",[207,246,248,251,253],{"class":209,"line":247},4,[207,249,250],{"class":213},"updated_at",[207,252,218],{"class":217},[207,254,244],{"class":221},[207,256,258,261,263],{"class":209,"line":257},5,[207,259,260],{"class":213},"update_reason",[207,262,218],{"class":217},[207,264,265],{"class":221},"\"Creazione caso da ticket #4412\"\n",[207,267,269,272,274],{"class":209,"line":268},6,[207,270,271],{"class":213},"deprecated",[207,273,218],{"class":217},[207,275,277],{"class":276},"sj4cs","false\n",[207,279,281,284],{"class":209,"line":280},7,[207,282,283],{"class":213},"input",[207,285,286],{"class":217},":\n",[207,288,290,293,295],{"class":209,"line":289},8,[207,291,292],{"class":213},"  user_message",[207,294,218],{"class":217},[207,296,297],{"class":221},"\"Voglio il rimborso dell'ordine #1234\"\n",[207,299,301,304,306],{"class":209,"line":300},9,[207,302,303],{"class":213},"  context",[207,305,218],{"class":217},[207,307,308],{"class":221},"\"Cliente verificato; ultimo ordine spedito 10 giorni fa.\"\n",[207,310,312,315],{"class":209,"line":311},10,[207,313,314],{"class":213},"matchers",[207,316,286],{"class":217},[207,318,320,323,326,328],{"class":209,"line":319},11,[207,321,322],{"class":217},"  - ",[207,324,325],{"class":213},"type",[207,327,218],{"class":217},[207,329,330],{"class":221},"json_field\n",[207,332,334,337,339],{"class":209,"line":333},12,[207,335,336],{"class":213},"    field",[207,338,218],{"class":217},[207,340,341],{"class":221},"intent\n",[207,343,345,348,350],{"class":209,"line":344},13,[207,346,347],{"class":213},"    op",[207,349,218],{"class":217},[207,351,352],{"class":221},"eq\n",[207,354,356,359,361],{"class":209,"line":355},14,[207,357,358],{"class":213},"    value",[207,360,218],{"class":217},[207,362,363],{"class":221},"refund\n",[207,365,367,369,371,373],{"class":209,"line":366},15,[207,368,322],{"class":217},[207,370,325],{"class":213},[207,372,218],{"class":217},[207,374,330],{"class":221},[207,376,378,380,382],{"class":209,"line":377},16,[207,379,336],{"class":213},[207,381,218],{"class":217},[207,383,384],{"class":221},"confidence\n",[207,386,388,390,392],{"class":209,"line":387},17,[207,389,347],{"class":213},[207,391,218],{"class":217},[207,393,394],{"class":221},"gte\n",[207,396,398,400,402],{"class":209,"line":397},18,[207,399,358],{"class":213},[207,401,218],{"class":217},[207,403,404],{"class":276},"0.85\n",[207,406,408,410,412,414],{"class":209,"line":407},19,[207,409,322],{"class":217},[207,411,325],{"class":213},[207,413,218],{"class":217},[207,415,416],{"class":221},"contains\n",[207,418,420,422,424],{"class":209,"line":419},20,[207,421,358],{"class":213},[207,423,218],{"class":217},[207,425,426],{"class":221},"\"1234\"\n",[207,428,430,432,434,436],{"class":209,"line":429},21,[207,431,322],{"class":217},[207,433,325],{"class":213},[207,435,218],{"class":217},[207,437,438],{"class":221},"not_contains\n",[207,440,442,444,446],{"class":209,"line":441},22,[207,443,358],{"class":213},[207,445,218],{"class":217},[207,447,448],{"class":221},"\"non posso aiutarti\"\n",[207,450,452,455,457],{"class":209,"line":451},23,[207,453,454],{"class":213},"level",[207,456,218],{"class":217},[207,458,459],{"class":276},"2\n",[207,461,463,466],{"class":209,"line":462},24,[207,464,465],{"class":213},"tags",[207,467,286],{"class":217},[207,469,471,473],{"class":209,"line":470},25,[207,472,322],{"class":217},[207,474,363],{"class":221},[207,476,478,480],{"class":209,"line":477},26,[207,479,322],{"class":217},[207,481,482],{"class":221},"entity-extraction\n",[29,484,486,487,490],{"id":485},"esempio-con-livello-3-json_subset-e-campi-extra-per-versione-modello","Esempio con livello 3 (",[118,488,489],{},"json_subset"," e campi extra per versione modello)",[16,492,493,494,497,498,501,502,505,506,509,510,513,514,518,519,522],{},"L’API (o il tool) restituisce JSON con ",[37,495,496],{},"chiavi aggiuntive"," che cambiano tra versioni di modello o tra snapshot (",[118,499,500],{},"model_reasoning",", ",[118,503,504],{},"debug_trace",", campi sperimentali). A te interessa solo che restino vere ",[37,507,508],{},"alcune"," coppie chiave\u002Fvalore: intent, stato operativo, ecc. In quel caso ",[37,511,512],{},"livello 2 non basta"," senza elencare ogni chiave extra con asserzioni negative fragili; usi ",[37,515,516],{},[118,517,489],{},": l’output parsato deve contenere ",[37,520,521],{},"almeno"," l’oggetto atteso, ignorando il resto.",[186,524,526],{"className":201,"code":525,"language":203,"meta":194,"style":194},"id: tool-output-contract-015\ndescription: \"Tool tracking: il modello aggiunge chiavi extra, il contratto è solo intent + status + confidence\"\ncreated_at: \"2026-05-12\"\nupdated_at: \"2026-05-12\"\nupdate_reason: \"Dopo upgrade modello compaiono chiavi opzionali non documentate nel client\"\ndeprecated: false\ninput:\n  user_message: \"Dov'è il mio pacco?\"\nmatchers:\n  - type: json_subset\n    expected:\n      intent: tracking\n      status: ok\n  - type: json_field\n    field: confidence\n    op: gte\n    value: 0.72\n  - type: normalized_text_equal\n    field: summary\n    expected: \"Il pacco è in transito verso il centro operativo.\"\nlevel: 3\ntags:\n  - tool-json\n  - tracking\n",[118,527,528,537,546,555,563,572,580,586,595,601,612,619,629,639,649,657,665,674,685,694,703,712,718,725],{"__ignoreMap":194},[207,529,530,532,534],{"class":209,"line":210},[207,531,214],{"class":213},[207,533,218],{"class":217},[207,535,536],{"class":221},"tool-output-contract-015\n",[207,538,539,541,543],{"class":209,"line":225},[207,540,228],{"class":213},[207,542,218],{"class":217},[207,544,545],{"class":221},"\"Tool tracking: il modello aggiunge chiavi extra, il contratto è solo intent + status + confidence\"\n",[207,547,548,550,552],{"class":209,"line":236},[207,549,239],{"class":213},[207,551,218],{"class":217},[207,553,554],{"class":221},"\"2026-05-12\"\n",[207,556,557,559,561],{"class":209,"line":247},[207,558,250],{"class":213},[207,560,218],{"class":217},[207,562,554],{"class":221},[207,564,565,567,569],{"class":209,"line":257},[207,566,260],{"class":213},[207,568,218],{"class":217},[207,570,571],{"class":221},"\"Dopo upgrade modello compaiono chiavi opzionali non documentate nel client\"\n",[207,573,574,576,578],{"class":209,"line":268},[207,575,271],{"class":213},[207,577,218],{"class":217},[207,579,277],{"class":276},[207,581,582,584],{"class":209,"line":280},[207,583,283],{"class":213},[207,585,286],{"class":217},[207,587,588,590,592],{"class":209,"line":289},[207,589,292],{"class":213},[207,591,218],{"class":217},[207,593,594],{"class":221},"\"Dov'è il mio pacco?\"\n",[207,596,597,599],{"class":209,"line":300},[207,598,314],{"class":213},[207,600,286],{"class":217},[207,602,603,605,607,609],{"class":209,"line":311},[207,604,322],{"class":217},[207,606,325],{"class":213},[207,608,218],{"class":217},[207,610,611],{"class":221},"json_subset\n",[207,613,614,617],{"class":209,"line":319},[207,615,616],{"class":213},"    expected",[207,618,286],{"class":217},[207,620,621,624,626],{"class":209,"line":333},[207,622,623],{"class":213},"      intent",[207,625,218],{"class":217},[207,627,628],{"class":221},"tracking\n",[207,630,631,634,636],{"class":209,"line":344},[207,632,633],{"class":213},"      status",[207,635,218],{"class":217},[207,637,638],{"class":221},"ok\n",[207,640,641,643,645,647],{"class":209,"line":355},[207,642,322],{"class":217},[207,644,325],{"class":213},[207,646,218],{"class":217},[207,648,330],{"class":221},[207,650,651,653,655],{"class":209,"line":366},[207,652,336],{"class":213},[207,654,218],{"class":217},[207,656,384],{"class":221},[207,658,659,661,663],{"class":209,"line":377},[207,660,347],{"class":213},[207,662,218],{"class":217},[207,664,394],{"class":221},[207,666,667,669,671],{"class":209,"line":387},[207,668,358],{"class":213},[207,670,218],{"class":217},[207,672,673],{"class":276},"0.72\n",[207,675,676,678,680,682],{"class":209,"line":397},[207,677,322],{"class":217},[207,679,325],{"class":213},[207,681,218],{"class":217},[207,683,684],{"class":221},"normalized_text_equal\n",[207,686,687,689,691],{"class":209,"line":407},[207,688,336],{"class":213},[207,690,218],{"class":217},[207,692,693],{"class":221},"summary\n",[207,695,696,698,700],{"class":209,"line":419},[207,697,616],{"class":213},[207,699,218],{"class":217},[207,701,702],{"class":221},"\"Il pacco è in transito verso il centro operativo.\"\n",[207,704,705,707,709],{"class":209,"line":429},[207,706,454],{"class":213},[207,708,218],{"class":217},[207,710,711],{"class":276},"3\n",[207,713,714,716],{"class":209,"line":441},[207,715,465],{"class":213},[207,717,286],{"class":217},[207,719,720,722],{"class":209,"line":451},[207,721,322],{"class":217},[207,723,724],{"class":221},"tool-json\n",[207,726,727,729],{"class":209,"line":462},[207,728,322],{"class":217},[207,730,628],{"class":221},[16,732,733,735,736,739,740,743,744,747,748,753,754,757,758,761,762,764,765,768],{},[118,734,489],{}," confronta la ",[37,737,738],{},"struttura minima","; ",[118,741,742],{},"json_field"," su ",[118,745,746],{},"confidence"," resta un controllo locale di soglia. Il matcher ",[37,749,750],{},[118,751,752],{},"normalized_text_equal"," (sempre livello 3 qui) applica nel runner una normalizzazione ",[37,755,756],{},"fissata e documentata"," (es. NFC, minuscolo, collasso spazi) prima del confronto sul campo ",[118,759,760],{},"summary",": utile quando il modello varia leggermente punteggiatura o spazi ma il contenuto deve restare equivalente. Il ",[118,763,454],{}," del caso è ",[37,766,767],{},"3"," perché il matcher più “debole” strutturalmente (non byte-per-byte su tutto il JSON) è quello di insieme\u002Fnormalizzazione.",[29,770,772],{"id":771},"significato-dei-campi","Significato dei campi",[774,775,776,789],"table",{},[777,778,779],"thead",{},[780,781,782,786],"tr",{},[783,784,785],"th",{},"Campo",[783,787,788],{},"Ruolo",[790,791,792,805,814,832,841,853,862,875,898],"tbody",{},[780,793,794,799],{},[795,796,797],"td",{},[118,798,214],{},[795,800,801,802,804],{},"Chiave stabile usata dal runner CI e nei report. Non rinominare: meglio ",[118,803,271],{}," + nuovo file.",[780,806,807,811],{},[795,808,809],{},[118,810,228],{},[795,812,813],{},"Contesto umano per review: perché questo caso esiste.",[780,815,816,823],{},[795,817,818,820,821],{},[118,819,239],{}," \u002F ",[118,822,250],{},[795,824,825,826,828,829,831],{},"Audit trail; ogni modifica che cambia il contratto del caso aggiorna ",[118,827,250],{}," e ",[118,830,260],{},".",[780,833,834,838],{},[795,835,836],{},[118,837,260],{},[795,839,840],{},"Obbligatorio quando cambi asserzioni o input: spiega la decisione (bug golden, cambio spec, adeguamento modello).",[780,842,843,847],{},[795,844,845],{},[118,846,271],{},[795,848,849,852],{},[118,850,851],{},"true"," se il caso non va più eseguito come gate, ma resta in repo per storia.",[780,854,855,859],{},[795,856,857],{},[118,858,283],{},[795,860,861],{},"Payload che il runner passa alla funzione\u002Fendpoint sotto test (messaggio utente, chunk RAG, metadati sessione).",[780,863,864,868],{},[795,865,866],{},[118,867,314],{},[795,869,870,871,874],{},"Lista ordinata di controlli ",[37,872,873],{},"deterministici"," (vedi gerarchia livelli).",[780,876,877,881],{},[795,878,879],{},[118,880,454],{},[795,882,883,886,887,890,891,894,895,897],{},[37,884,885],{},"Massimo"," livello di matcher usato in questo caso (",[118,888,889],{},"1"," … ",[118,892,893],{},"5","). Serve ad aggregare nei report (es. “quanti casi toccano livello 4?”). Deve essere coerente con i ",[118,896,325],{}," presenti.",[780,899,900,904],{},[795,901,902],{},[118,903,465],{},[795,905,906],{},"Filtri in dashboard o ownership per prodotto.",[29,908,910],{"id":909},"esempio-con-livello-4-embedding","Esempio con livello 4 (embedding)",[16,912,913,914,917],{},"Usa questo pattern solo quando la risposta è ",[37,915,916],{},"libera"," ma vuoi comunque un segnale automatico prima della review:",[186,919,921],{"className":201,"code":920,"language":203,"meta":194,"style":194},"id: tone-paraphrase-010\ndescription: \"Risposta empatica equivalente, non letterale\"\ncreated_at: \"2026-05-10\"\nupdated_at: \"2026-05-10\"\nupdate_reason: \"Calibrazione soglia su 30 esempi umani\"\ndeprecated: false\ninput:\n  user_message: \"Sono molto deluso dal ritardo.\"\nmatchers:\n  - type: embedding_similarity\n    golden_text: \"Capisco la frustrazione per il ritardo; ti aggiorno sullo stato della spedizione.\"\n    threshold: 0.88\n    model: text-embedding-3-small\nlevel: 4\ntags:\n  - tone\n  - shipping\n",[118,922,923,932,941,950,958,967,975,981,990,996,1007,1017,1027,1037,1046,1052,1059],{"__ignoreMap":194},[207,924,925,927,929],{"class":209,"line":210},[207,926,214],{"class":213},[207,928,218],{"class":217},[207,930,931],{"class":221},"tone-paraphrase-010\n",[207,933,934,936,938],{"class":209,"line":225},[207,935,228],{"class":213},[207,937,218],{"class":217},[207,939,940],{"class":221},"\"Risposta empatica equivalente, non letterale\"\n",[207,942,943,945,947],{"class":209,"line":236},[207,944,239],{"class":213},[207,946,218],{"class":217},[207,948,949],{"class":221},"\"2026-05-10\"\n",[207,951,952,954,956],{"class":209,"line":247},[207,953,250],{"class":213},[207,955,218],{"class":217},[207,957,949],{"class":221},[207,959,960,962,964],{"class":209,"line":257},[207,961,260],{"class":213},[207,963,218],{"class":217},[207,965,966],{"class":221},"\"Calibrazione soglia su 30 esempi umani\"\n",[207,968,969,971,973],{"class":209,"line":268},[207,970,271],{"class":213},[207,972,218],{"class":217},[207,974,277],{"class":276},[207,976,977,979],{"class":209,"line":280},[207,978,283],{"class":213},[207,980,286],{"class":217},[207,982,983,985,987],{"class":209,"line":289},[207,984,292],{"class":213},[207,986,218],{"class":217},[207,988,989],{"class":221},"\"Sono molto deluso dal ritardo.\"\n",[207,991,992,994],{"class":209,"line":300},[207,993,314],{"class":213},[207,995,286],{"class":217},[207,997,998,1000,1002,1004],{"class":209,"line":311},[207,999,322],{"class":217},[207,1001,325],{"class":213},[207,1003,218],{"class":217},[207,1005,1006],{"class":221},"embedding_similarity\n",[207,1008,1009,1012,1014],{"class":209,"line":319},[207,1010,1011],{"class":213},"    golden_text",[207,1013,218],{"class":217},[207,1015,1016],{"class":221},"\"Capisco la frustrazione per il ritardo; ti aggiorno sullo stato della spedizione.\"\n",[207,1018,1019,1022,1024],{"class":209,"line":333},[207,1020,1021],{"class":213},"    threshold",[207,1023,218],{"class":217},[207,1025,1026],{"class":276},"0.88\n",[207,1028,1029,1032,1034],{"class":209,"line":344},[207,1030,1031],{"class":213},"    model",[207,1033,218],{"class":217},[207,1035,1036],{"class":221},"text-embedding-3-small\n",[207,1038,1039,1041,1043],{"class":209,"line":355},[207,1040,454],{"class":213},[207,1042,218],{"class":217},[207,1044,1045],{"class":276},"4\n",[207,1047,1048,1050],{"class":209,"line":366},[207,1049,465],{"class":213},[207,1051,286],{"class":217},[207,1053,1054,1056],{"class":209,"line":377},[207,1055,322],{"class":217},[207,1057,1058],{"class":221},"tone\n",[207,1060,1061,1063],{"class":209,"line":387},[207,1062,322],{"class":217},[207,1064,1065],{"class":221},"shipping\n",[16,1067,1068,1069,1071,1072,1075,1076,831],{},"Qui ",[118,1070,454],{}," è ",[37,1073,1074],{},"4"," perché il matcher di punta è semantico; non mischiare nel report con casi solo livello 2 senza aggiornare ",[118,1077,454],{},[11,1079,1081],{"id":1080},"la-gerarchia-dei-matcher-livello-1-5","La gerarchia dei matcher (livello 1 → 5)",[16,1083,1084,1085,1088,1089,1092],{},"Regola pratica da tenere appesa sopra la scrivania: ",[37,1086,1087],{},"usa il livello più basso possibile",". Se la maggior parte dei casi finisce a livello 4 o 5, il problema è spesso il ",[37,1090,1091],{},"design del contratto di output"," (troppo aperto), non la qualità dei test.",[29,1094,1096],{"id":1095},"livello-1-uguaglianza-esatta","Livello 1 — Uguaglianza esatta",[69,1098,1099,1109,1119,1125],{},[72,1100,1101,1104,1105,1108],{},[37,1102,1103],{},"Cosa:"," confronto stringa byte-per-byte (o normalizzazione solo se ",[37,1106,1107],{},"esplicitata"," nello stesso livello, altrimenti resta 1 “strict”).",[72,1110,1111,1114,1115,1118],{},[37,1112,1113],{},"Tipo esempio:"," ",[118,1116,1117],{},"exact_text"," sul serializzato canonico dell’output (es. JSON con chiavi ordinate se il tuo runner serializza sempre uguale).",[72,1120,1121,1124],{},[37,1122,1123],{},"Quando:"," output rigidamente formattato, temperatura 0, nessuna variazione lecita.",[72,1126,1127,1130],{},[37,1128,1129],{},"Costo computazionale:"," trascurabile.",[29,1132,1134],{"id":1133},"livello-2-predicati-su-struttura-o-sottostringhe","Livello 2 — Predicati su struttura o sottostringhe",[69,1136,1137,1169,1174],{},[72,1138,1139,1114,1141,1143,1144,501,1147,501,1150,501,1153,501,1156,1159,1160,501,1163,501,1166,831],{},[37,1140,1103],{},[118,1142,742],{}," (",[118,1145,1146],{},"eq",[118,1148,1149],{},"ne",[118,1151,1152],{},"gte",[118,1154,1155],{},"lte",[118,1157,1158],{},"in","), ",[118,1161,1162],{},"contains",[118,1164,1165],{},"not_contains",[118,1167,1168],{},"regex",[72,1170,1171,1173],{},[37,1172,1123],{}," structured output, intent + confidence, divieti lessicali (“non dire X”).",[72,1175,1176,1179],{},[37,1177,1178],{},"Costo:"," lineare nella lunghezza dell’output, tutto locale.",[29,1181,1183],{"id":1182},"livello-3-contratto-strutturale-o-normalizzazione-controllata","Livello 3 — Contratto strutturale o normalizzazione controllata",[69,1185,1186,1207,1224],{},[72,1187,1188,1114,1190,1192,1193,1196,1197,501,1200,1202,1203,1206],{},[37,1189,1103],{},[118,1191,489],{}," (l’output parsato contiene almeno le coppie annidate in ",[118,1194,1195],{},"expected","), validazione ",[118,1198,1199],{},"json_schema",[118,1201,752],{}," (regole di normalizzazione ",[37,1204,1205],{},"fissate nel runner",", es. NFC + minuscolo + collasso spazi, poi uguaglianza sul campo indicato).",[72,1208,1209,1211,1212,1215,1216,1219,1220,1223],{},[37,1210,1123],{}," chiavi ",[37,1213,1214],{},"extra"," accettate o variabili tra versioni modello (vedi esempio ",[118,1217,1218],{},"tool-output-contract-015"," sopra); testo libero in un campo dove solo la ",[37,1221,1222],{},"forma normalizzata"," deve combaciare.",[72,1225,1226,1228],{},[37,1227,1178],{}," parsing JSON + eventuale validazione schema; tutto locale.",[16,1230,1231,1234,1235,1237,1238,1242,1243,1246],{},[37,1232,1233],{},"Livello 2 vs 3:"," al livello 2 usi ",[118,1236,742],{}," su ogni campo che ti interessa e rischi di dover aggiornare il golden ogni volta che compare una chiave nuova irrilevante. Al livello 3 ",[37,1239,1240],{},[118,1241,489],{}," fissa il ",[37,1244,1245],{},"sotto-contratto"," che importa e ignora il rumore strutturale esterno.",[29,1248,1250],{"id":1249},"livello-4-similarità-embedding","Livello 4 — Similarità embedding",[69,1252,1253,1269,1274],{},[72,1254,1255,1114,1257,1260,1261,1264,1265,1268],{},[37,1256,1103],{},[118,1258,1259],{},"embedding_similarity"," tra output e ",[118,1262,1263],{},"golden_text"," (o tra embedding cache del golden e dell’actual), con ",[37,1266,1267],{},"soglia"," fissata per calibrazione su un set di validazione umano.",[72,1270,1271,1273],{},[37,1272,1123],{}," parafrasi accettabili, risposte libere con tolleranza semantica misurabile.",[72,1275,1276,1278],{},[37,1277,1178],{}," una o due chiamate embedding per caso (cache sul golden in CI se possibile).",[29,1280,1282],{"id":1281},"livello-5-solo-review-umana","Livello 5 — Solo review umana",[69,1284,1285,1294,1299],{},[72,1286,1287,1289,1290,1293],{},[37,1288,1103],{}," il caso documenta input e criteri qualitativi (brand, compliance, rischio legale) che ",[37,1291,1292],{},"non"," codifichi in matcher binari affidabili.",[72,1295,1296,1298],{},[37,1297,1123],{}," nessun automatismo generico è sufficiente; il gate è checklist umana o processo dedicato.",[72,1300,1301,1303,1304,1306],{},[37,1302,1178],{}," tempo umano; ",[37,1305,1292],{}," va usato come “pass automatico” in CI.",[11,1308,1310],{"id":1309},"ci-contratto-di-integrazione","CI: contratto di integrazione",[16,1312,1313,1314,1317],{},"Non serve imporre Vitest, GitHub Actions o altro: serve un ",[37,1315,1316],{},"contratto"," che qualsiasi runner può implementare.",[29,1319,1320],{"id":283},"Input",[69,1322,1323,1326,1333],{},[72,1324,1325],{},"Directory o manifest dei file YAML del golden set.",[72,1327,1328,1329,1332],{},"Funzione o endpoint sotto test (versione prompt e modello ",[37,1330,1331],{},"pin"," o identificati da variabili d’ambiente della pipeline).",[72,1334,1335],{},"Configurazione runner: timeout, retry solo dove è accettabile (di solito no su test di regressione), path dei segreti per embedding se usi livello 4.",[29,1337,1339],{"id":1338},"output-report-macchina-leggibile-umano","Output (report macchina-leggibile + umano)",[69,1341,1342,1355,1366,1381],{},[72,1343,1344,1345,1348,1349,1143,1352,1354],{},"Conteggio ",[37,1346,1347],{},"pass \u002F fail"," totale e ",[37,1350,1351],{},"per livello massimo",[118,1353,454],{}," del caso).",[72,1356,1357,1358,1361,1362,1365],{},"Per ogni fallimento: quale ",[118,1359,1360],{},"matcher"," ha rotto, valore ",[37,1363,1364],{},"atteso vs attuale"," (stringa troncata, path JSON, score di similarità).",[72,1367,1368,1369,1374,1375,1378,1379,831],{},"Flag ",[37,1370,1371],{},[118,1372,1373],{},"review_required: true"," su ogni fallimento ",[37,1376,1377],{},"livello 4"," (evidenza per la review legata all’eventuale override) e su tutti i casi ",[37,1380,893],{},[72,1382,1383],{},"Opzionale: diff testuale o JSON pretty-print tra output canonico e snapshot atteso (solo se non viola PII — mascherare ID ordine nei log).",[29,1385,1387],{"id":1386},"policy-di-esecuzione","Policy di esecuzione",[69,1389,1390,1400,1409],{},[72,1391,1392,1395,1396,1399],{},[37,1393,1394],{},"Ogni cambio prompt"," che tocca il sistema sotto test: run ",[37,1397,1398],{},"obbligatorio"," pre-merge (o pre-push se il team è piccolo).",[72,1401,1402,1405,1406,1408],{},[37,1403,1404],{},"Ogni cambio modello"," (versione API, snapshot, temperatura di default prod-relevant): run ",[37,1407,1398],{}," pre-deploy.",[72,1410,1411,1414,1415,1418,1419,1422],{},[37,1412,1413],{},"Run periodico"," (es. settimanale) anche senza cambi al codice: i provider aggiornano pesi e routing; il ",[37,1416,1417],{},"model drift"," esiste anche se il tuo ",[118,1420,1421],{},"package.json"," non è cambiato.",[29,1424,1426],{"id":1425},"soglie-di-fallimento-policy-consigliata","Soglie di fallimento (policy consigliata)",[774,1428,1429,1439],{},[777,1430,1431],{},[780,1432,1433,1436],{},[783,1434,1435],{},"Livello matcher coinvolto nel fallimento",[783,1437,1438],{},"Esito pipeline",[790,1440,1441,1452,1485],{},[780,1442,1443,1446],{},[795,1444,1445],{},"1–3",[795,1447,1448,1451],{},[37,1449,1450],{},"Fail"," = blocco merge\u002Fdeploy (rosso).",[780,1453,1454,1456],{},[795,1455,1074],{},[795,1457,1458,1461,1462,1465,1466,1469,1470,1473,1474,1477,1478,1480,1481,1484],{},[37,1459,1460],{},"Default di questo protocollo:"," come 1–3 — ",[37,1463,1464],{},"fail = blocco"," merge\u002Fdeploy. ",[37,1467,1468],{},"Override consentito"," solo con ",[37,1471,1472],{},"approvazione esplicita tracciata"," (ticket collegato, etichetta su PR, o campo obbligatorio nel report CI firmato da un owner) dopo lettura del report (score di similarità, snippet actual). Senza traccia non si “sblocca” il rosso: altrimenti il livello 4, per natura ",[37,1475,1476],{},"non binario"," come un ",[118,1479,1146],{},", smorzerebbe la CI fino a renderla decorativa. I team più grandi possono adottare una policy più permissiva ",[37,1482,1483],{},"solo"," sostituendo per iscritto questo default interno.",[780,1486,1487,1489],{},[795,1488,893],{},[795,1490,1491,1494],{},[37,1492,1493],{},"Mai"," esito automatico verde\u002Frosso definitivo: solo coda review umana.",[16,1496,1497,1498,1501,1502,1505],{},"Il default di blocco anche sul livello 4 evita che un merge passi mentre l’unico segnale era “embedding sotto soglia”: la soglia ha ",[37,1499,1500],{},"varianza"," (modello embedding, testo, token); la review deve essere un ",[37,1503,1504],{},"passo consapevole",", non un buco nella pipeline.",[11,1507,1509],{"id":1508},"policy-di-aggiornamento-del-golden-set","Policy di aggiornamento del golden set",[16,1511,1512,1513,1516],{},"Un golden set obsoleto è peggio dell’assenza di test: dà ",[37,1514,1515],{},"falsa sicurezza",". Questa sezione è il contratto sociale tra ingegneria e prodotto.",[29,1518,1520],{"id":1519},"quando-aggiornare","Quando aggiornare",[69,1522,1523,1534,1545],{},[72,1524,1525,1526,1529,1530,820,1532,831],{},"Compare in produzione un ",[37,1527,1528],{},"nuovo tipo di input"," non coperto dal set → aggiungi un caso con ",[118,1531,239],{},[118,1533,260],{},[72,1535,1536,1537,1540,1541,1544],{},"Un caso fallisce ma l’output nuovo è ",[37,1538,1539],{},"corretto per spec aggiornata"," → non “sistemi” il modello a forza: aggiorni il golden e documenti ",[37,1542,1543],{},"perché"," il vecchio atteso era sbagliato.",[72,1546,1547,1550],{},[37,1548,1549],{},"Cambio intenzionale di comportamento"," (nuova policy rimborso, tono diverso) → aggiornamento golden è parte della stessa change request del codice o del prompt.",[29,1552,1554],{"id":1553},"come-aggiornare-regole-operative","Come aggiornare (regole operative)",[69,1556,1557,1573,1583],{},[72,1558,1559,1562,1563,1565,1566,1568,1569,1572],{},[37,1560,1561],{},"Nessun aggiornamento silenzioso automatizzato"," da pipeline (tipo “sovrascrivi expected con actual”). Ogni modifica a ",[118,1564,314],{}," o ",[118,1567,283],{}," è una ",[37,1570,1571],{},"decisione umana"," tracciata.",[72,1574,1575,1576,828,1578,1582],{},"Ogni modifica tocca ",[118,1577,250],{},[37,1579,1580],{},[118,1581,260],{}," (una riga frase nel YAML va bene).",[72,1584,1585,1586,1589,1590,1593,1594,828,1597,1600,1601,1604],{},"I casi non si ",[37,1587,1588],{},"cancellano"," dal giorno alla notte: ",[118,1591,1592],{},"deprecated: true",", opzionalmente ",[118,1595,1596],{},"deprecated_at",[118,1598,1599],{},"deprecated_reason",", e restano almeno ",[37,1602,1603],{},"un ciclo di release"," per permettere grep e audit.",[16,1606,1607],{},"Esempio di deprecazione:",[186,1609,1611],{"className":201,"code":1610,"language":203,"meta":194,"style":194},"id: refund-intent-legacy-000\ndescription: \"Vecchia formulazione pre-spec 2026-04\"\ndeprecated: true\ndeprecated_at: \"2026-05-15\"\ndeprecated_reason: \"Sostituito da refund-intent-001 dopo modifica policy ID ordine\"\n",[118,1612,1613,1622,1631,1640,1649],{"__ignoreMap":194},[207,1614,1615,1617,1619],{"class":209,"line":210},[207,1616,214],{"class":213},[207,1618,218],{"class":217},[207,1620,1621],{"class":221},"refund-intent-legacy-000\n",[207,1623,1624,1626,1628],{"class":209,"line":225},[207,1625,228],{"class":213},[207,1627,218],{"class":217},[207,1629,1630],{"class":221},"\"Vecchia formulazione pre-spec 2026-04\"\n",[207,1632,1633,1635,1637],{"class":209,"line":236},[207,1634,271],{"class":213},[207,1636,218],{"class":217},[207,1638,1639],{"class":276},"true\n",[207,1641,1642,1644,1646],{"class":209,"line":247},[207,1643,1596],{"class":213},[207,1645,218],{"class":217},[207,1647,1648],{"class":221},"\"2026-05-15\"\n",[207,1650,1651,1653,1655],{"class":209,"line":257},[207,1652,1599],{"class":213},[207,1654,218],{"class":217},[207,1656,1657],{"class":221},"\"Sostituito da refund-intent-001 dopo modifica policy ID ordine\"\n",[29,1659,1661],{"id":1660},"segnale-di-degrado-del-set","Segnale di degrado del set",[16,1663,1664,1665,1668,1669,1672,1673,1676,1677,1680],{},"Se dopo ",[37,1666,1667],{},"un singolo"," cambio modello più del ",[37,1670,1671],{},"~30%"," dei casi richiede riscrittura delle asserzioni, il set è troppo accoppiato alla ",[37,1674,1675],{},"forma superficiale"," dell’output (verbatim, punteggiatura) e non al ",[37,1678,1679],{},"contratto semantico",". Rimedi: spostare matcher verso livello 2–3 (campi strutturati), stringere l’output con schema, ridurre testo libero dove non serve.",[11,1682,1684],{"id":1683},"cosa-resta-fuori","Cosa resta fuori",[69,1686,1687,1693],{},[72,1688,1689,1692],{},[37,1690,1691],{},"Evaluator LLM-as-judge"," come gate primario: pattern diverso, non deterministico nel senso usato qui.",[72,1694,1695,1698,1699,1702],{},[37,1696,1697],{},"A\u002FB test"," su traffico reale, ",[37,1700,1701],{},"eval di fine-tuning",", framework commerciali (LangSmith, RAGAS, …): utili, ma non necessari per implementare lo standard sopra con uno script interno.",[11,1704,1706],{"id":1705},"chiusura","Chiusura",[16,1708,1709,1710],{},"La domanda da portarsi a casa: ",[20,1711,1712],{},"«Se domani mattina cambio modello, in quanto tempo so se ho rotto qualcosa sui miei casi reali?»",[16,1714,1715,1716,1565,1719,1722,1723,1726],{},"Se la risposta è ",[20,1717,1718],{},"non lo so",[20,1720,1721],{},"ci vuole un giorno",", ti serve un golden set operativo come questo. Se la risposta diventa ",[20,1724,1725],{},"dieci minuti dopo il merge della pipeline",", l’articolo ha fatto il suo lavoro.",[1728,1729,1730],"style",{},"html pre.shiki code .s9eBZ, html code.shiki .s9eBZ{--shiki-default:#22863A;--shiki-dark:#85E89D}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":194,"searchDepth":225,"depth":225,"links":1732},[1733,1734,1739,1740,1748,1755,1761,1766,1767],{"id":13,"depth":225,"text":14},{"id":26,"depth":225,"text":27,"children":1735},[1736,1737,1738],{"id":31,"depth":236,"text":32},{"id":47,"depth":236,"text":48},{"id":66,"depth":236,"text":67},{"id":109,"depth":225,"text":110},{"id":168,"depth":225,"text":169,"children":1741},[1742,1743,1744,1746,1747],{"id":183,"depth":236,"text":184},{"id":197,"depth":236,"text":198},{"id":485,"depth":236,"text":1745},"Esempio con livello 3 (json_subset e campi extra per versione modello)",{"id":771,"depth":236,"text":772},{"id":909,"depth":236,"text":910},{"id":1080,"depth":225,"text":1081,"children":1749},[1750,1751,1752,1753,1754],{"id":1095,"depth":236,"text":1096},{"id":1133,"depth":236,"text":1134},{"id":1182,"depth":236,"text":1183},{"id":1249,"depth":236,"text":1250},{"id":1281,"depth":236,"text":1282},{"id":1309,"depth":225,"text":1310,"children":1756},[1757,1758,1759,1760],{"id":283,"depth":236,"text":1320},{"id":1338,"depth":236,"text":1339},{"id":1386,"depth":236,"text":1387},{"id":1425,"depth":236,"text":1426},{"id":1508,"depth":225,"text":1509,"children":1762},[1763,1764,1765],{"id":1519,"depth":236,"text":1520},{"id":1553,"depth":236,"text":1554},{"id":1660,"depth":236,"text":1661},{"id":1683,"depth":225,"text":1684},{"id":1705,"depth":225,"text":1706},"\u002Fblog\u002F22_llm_golden_set_regression_protocol_1x.webp","Golden set per regressioni LLM su prompt e modello, senza LLM-as-judge: formato file, gerarchia dei matcher, policy CI e regole di aggiornamento.",false,"md","it",{},true,"\u002Fit\u002Fblog\u002Fllm-golden-set-regression-protocol","2026-06-16",{"title":5,"description":1769},"Golden set LLM: regressione senza LLM-as-judge","llm-golden-set-regression-protocol","it\u002Fblog\u002Fllm-golden-set-regression-protocol",[1782,1783,1784],"mvp","llm","automation","_3Wd4n3ko31Ay0TB8aB9mVS5UZ0O4p966dOJ41rOYmw"]