Guide
Tutorial: un classificatore o un output strutturato
Valuta una funzione Python che etichetta domande di assistenza, leggi perché il rilascio è bloccato, correggi gli errori e confronta la correzione con l'originale, tutto sulla tua macchina, senza account, senza rete e senza modelli.
Che cosa costruirai
Un bot di assistenza che restituisce un oggetto JSON con un answer e una label (refund, account o other). Lo terrai a tre requisiti: l'etichetta è corretta abbastanza spesso, l'output ha sempre la forma giusta, e nessuna risposta lascia trapelare qualcosa che somigli a un numero di previdenza sociale statunitense. Due di questi sono controlli di formato che non richiedono una risposta di riferimento; uno misura il successo del compito rispetto a un'etichetta di riferimento. La differenza conta, e questa pagina li tiene separati.
I termini usati qui sotto (caso, esecuzione, metrica, intervallo, regola, gate) sono definiti in Concetti.
Prerequisiti
- Python 3.11 o successivo.
- Oloproof, installato in un ambiente virtuale:
python3 -m venv .venv
. .venv/bin/activate
pip install oloproof- Il progetto di esempio e la modifica candidata, che sono inclusi nel pacchetto. Copia entrambi in nuove directory e lavora nella prima; ogni file è anche elencato qui sotto, quindi puoi digitarli tu:
oloproof init --example support_bot support-classifier
oloproof init --example classification support-change
cd support-classifierIn questa pagina non vengono usati chiavi API, account di provider né accesso alla rete.
I file
support-classifier/
app.py the application under test (a Python callable)
oloproof.yaml the suite: dataset, system, evaluators
release.yaml the release policy: rules the run is decided against
data/support.jsonl 18 cases, one JSON object per line
rubrics/helpful.md a judge rubric, unused hereEsegui ogni comando dalla directory support-classifier/. Oloproof tiene lì il proprio store, in .oloproof/; elimina quella directory per ricominciare da zero.
L'applicazione e il suo adattatore
La tua applicazione viene raggiunta tramite un adattatore. Per un'applicazione Python l'adattatore è la funzione stessa: Oloproof la importa, la chiama una volta per caso con l'input del caso, e registra il dizionario che restituisce come output di quel caso.
# app.py
from typing import Any
from oloproof import system
@system(name="support-bot", version="slice-a-example")
def answer(case: dict[str, Any]) -> dict[str, str]:
question = str(case["question"]).lower()
if "refund" in question:
return {"answer": "Refunds are available within 30 days when the order is eligible.",
"label": "refund"}
if "password" in question or "login" in question:
return {"answer": "Use password reset, then contact support if the login still fails.",
"label": "account"}
return {"answer": "A support specialist will follow up with the next step.", "label": "other"}Per valutare il tuo classificatore, lascia il suo codice dov'è e scrivi una funzione sottile come questa che lo chiama e restituisce un dizionario. La funzione può essere async. Oloproof la chiama; non ospita, non isola e non reimposta la tua applicazione, quindi qualsiasi stato che la tua applicazione mantiene tra una chiamata e l'altra spetta a te gestirlo.
oloproof.yaml nomina quella funzione e i valutatori:
version: 1
project: support-bot-example
dataset: data/support.jsonl
system:
name: support-bot
version: slice-a-example
callable: app:answer
timeout_s: 30
evaluators:
- type: exact_match
criterion: exact_label
field: label
- type: json_schema
criterion: format_valid
field: null
schema:
type: object
required: [answer, label]
properties:
answer: {type: string}
label: {type: string}
additionalProperties: false
- type: regex
criterion: pii_free
field: answer
pattern: '\b\d{3}-\d{2}-\d{4}\b'
pass_if: no_matchGli output vengono messi in cache in base al sorgente della funzione, alla version dichiarata e a config. Se la funzione legge altri file (un prompt, una tabella di regole), elencali sotto system.code_paths, così modificarli esegue di nuovo il sistema.
Il dataset
Un caso per riga. input è esattamente ciò che la tua funzione riceve come case; expected è il riferimento con cui il valutatore exact_match confronta:
{"id":"refund_00","input":{"question":"Can I get a refund for yesterday's order?"},"expected":{"label":"refund"}}
{"id":"account_04","input":{"question":"I can't sign in on my new phone."},"expected":{"label":"account"}}
{"id":"other_04","input":{"question":"I don't want a refund, I just need a copy of my receipt."},"expected":{"label":"other"}}La funzione restituisce, per ogni caso, un oggetto come {"answer": "Use password reset, ...", "label": "account"}.
Scegliere i valutatori
| Criterio | Valutatore | Richiede expected | Che cosa misura |
|---|---|---|---|
| exact_label | exact_match su label | sì | successo del compito: l'etichetta è quella giusta |
| format_valid | json_schema sull'intero output | no | formato: l'oggetto ha esattamente i due campi stringa |
| pii_free | regex su answer, pass_if: no_match | no | una proprietà di sicurezza del testo |
Un controllo di formato fa passare una risposta sbagliata ma ben formata, quindi non può mai sostituire il successo del compito. Un controllo del compito richiede un riferimento per ogni caso; dove un caso non ne ha, exact_match non può valutarlo. I valutatori deterministici non richiedono validazione rispetto a persone: eseguirne uno due volte dà lo stesso verdetto.
La policy
release.yaml è ciò rispetto a cui l'esecuzione viene decisa:
version: 1
confidence_level: 0.95
block_on: [FAIL, INSUFFICIENT_EVIDENCE, MANUAL_REVIEW]
warn_on: []
rules:
- id: exact-label-floor
metric: exact_label
min: 0.70
- id: valid-format
metric: format_valid
kind: observed_count
max_failures: 0
- id: pii-free
metric: pii_free
kind: observed_count
max_failures: 0exact-label-floor dice che l'etichetta deve essere corretta almeno il 70% delle volte, e passa solo quando l'intero intervallo al 95% è pari o superiore a 0.70. Le due regole observed_count non ammettono alcun fallimento sui casi che hai eseguito; descrivono questi casi, non ogni domanda che gli utenti porranno.
Eseguilo
oloproof runOutput reale, ridotto:
Run run_01M4... [DECIDED/COMPLETE]
Gate: BLOCK (exit 3)
│ exact-label-floor │ exact_label │ INSUFFICIENT_EVIDENCE │ interval_overlaps_threshold │
│ valid-format │ format_valid │ PASS │ observed_failures_within_limit │
│ pii-free │ pii_free │ PASS │ observed_failures_within_limit │
│ exact_label │ 72.2% │ [46.5%, 90.4%] │ 13 / 18 observed · 0 missing · 0 excluded │
│ format_valid │ 100.0% │ [81.4%, 100.0%] │ 18 / 18 observed · 0 missing · 0 excluded │
│ pii_free │ 100.0% │ [81.4%, 100.0%] │ 18 / 18 observed · 0 missing · 0 excluded │
Cache: execution 0 hit/18 miss; judgment 0 hit/54 missCome leggerlo:
- 13 etichette su 18 sono corrette, il 72,2%. È sopra 0.70, ma l'intervallo scende fino al 46,5%: 18 casi non possono mostrare che il tasso vero sia almeno 0.70. Quindi la regola è INSUFFICIENT_EVIDENCE, né PASS né FAIL.
- Ogni output ha la forma giusta e nessuno contiene un numero simile a un SSN, quindi entrambe le regole di formato passano.
- block_on elenca INSUFFICIENT_EVIDENCE, quindi il gate blocca e il comando esce con 3. L'uscita 0 significherebbe che non c'è nulla su cui la policy blocca; Gating in CI elenca tutti i codici.
Eseguilo di nuovo e la riga della cache riporta execution 18 hit/0 miss: non è cambiato nulla, quindi la funzione non viene chiamata.
Esamina i fallimenti
oloproof inspect RUN_ID --failures
oloproof inspect RUN_ID --case refund_04RUN_ID è l'id sulla prima riga dell'output dell'esecuzione.
5 of 18 cases failed, errored or did not finish
refund_04
output: {"answer": "A support specialist will follow up with the next step.", "label": "other"}
exact_label: failed
...
other_04
output: {"answer": "Refunds are available within 30 days when the order is eligible.", "label": "refund"}
exact_label: failedcase refund_04
input: {
"question": "I was charged twice this month and want my money back."
}
expected: {
"label": "refund"
}
execution: OK, 1 ms
output: {
"answer": "A support specialist will follow up with the next step.",
"label": "other"
}
judgments:
exact_label: failed
format_valid: passed
pii_free: passedLo schema è evidente una volta letti gli input: "money back", "reverse the payment", "sign in" e "two-factor" non sono nelle liste di parole chiave, e other_04 dice "I don't want a refund", che la parola "refund" intercetta comunque. Nota che refund_04 supera entrambi i controlli di formato pur essendo sbagliato: è il divario tra controllare il formato e misurare il successo.
Qui hanno senso due azioni successive. Correggere gli errori (sotto), oppure aggiungere casi: con più casi alla stessa accuratezza l'intervallo si restringe, e oloproof plan RUN_ID --run stima quanti ne servono.
Fai una modifica reale
Copia ../support-change/app.py sopra app.py. Aggiunge le formulazioni mancate:
REFUND_WORDS = ("refund", "money back", "reverse the payment")
ACCOUNT_WORDS = ("password", "login", "sign in", "two-factor")
@system(name="support-bot", version="keywords-v2")
def answer(case: dict[str, Any]) -> dict[str, str]:
question = str(case["question"]).lower()
if any(word in question for word in REFUND_WORDS):
...e imposta version: keywords-v2 sotto system in oloproof.yaml, così l'esecuzione viene registrata come la nuova versione. Poi:
oloproof runGate: ALLOW (exit 0)
│ exact-label-floor │ exact_label │ PASS │ lower_bound_meets_minimum │
│ exact_label │ 94.4% │ [72.7%, 99.9%] │ 17 / 18 observed · 0 missing · 0 excluded │17 su 18 sono corretti e il limite inferiore dell'intervallo, 72,7%, supera 0.70, quindi la regola passa e il comando esce con 0. other_04 fallisce ancora: la correzione non ha toccato la negazione.
Confronta il candidato con la baseline
La regola di esecuzione chiede se il candidato raggiunge la tua soglia. Un confronto chiede in che cosa differisce dalla baseline, caso per caso. Copia ../support-change/compare.yaml nel progetto; contiene una regola di confronto:
version: 1
confidence_level: 0.95
block_on: [FAIL, INSUFFICIENT_EVIDENCE, MANUAL_REVIEW]
rules:
- id: label-no-regression
kind: non_inferiority
metric: exact_label
margin: 0.10oloproof compare CANDIDATE_RUN_ID BASELINE_RUN_ID --policy compare.yamlComparison sha256:de76... of run_01M4...TJAD against run_01M4...ECVEF · 18 paired cases
exact_label: +22.2 points [-12.9, +57.0] · 18 paired · 0 missing · 0 excluded
format_valid: +0.0 points [-25.8, +25.8] · 18 paired · 0 missing · 0 excluded
pii_free: +0.0 points [-25.8, +25.8] · 18 paired · 0 missing · 0 excluded
Decisions
label-no-regression exact_label non-inferiority, margin 10.0 points INSUFFICIENT_EVIDENCE interval_overlaps_margin
about 3 more paired cases would decide it, if the difference holds (21 in total at 22% discordance)
Gate: BLOCK (exit 3)Il candidato ha corretto quattro casi e non ne ha rotto nessuno, un guadagno stimato di 22 punti. Ma sono cambiati solo quattro casi, e 18 casi appaiati lasciano un intervallo da 12,9 punti peggio a 57 punti meglio, che attraversa il margine di 10 punti. Il confronto non può ancora escludere che il candidato sia peggiore di più di quanto accetti, quindi è INSUFFICIENT_EVIDENCE ed esce con 3. La riga sotto è la stima di dimensionamento. Senza --policy, compare stampa le differenze, dice che il release.yaml del progetto non dichiara alcuna regola di confronto, ed esce con 0 perché non è stato deciso nulla.
Confrontare un candidato con una baseline spiega il margine e gli altri tipi di regola.
Risoluzione dei problemi
| Sintomo | Causa e rimedio |
|---|---|
| ModuleNotFoundError per app | Esegui dalla directory che contiene app.py, o dai a callable un percorso di modulo importabile da lì. |
| Una regola nomina una metrica che nessun valutatore produce | La metric della regola deve essere uguale al criterion di un valutatore; l'errore elenca le metriche esistenti. |
| Hai modificato il classificatore e l'esecuzione ha riutilizzato ogni output | La cache segue il sorgente del callable; un file di supporto che legge deve essere elencato sotto system.code_paths. |
| exact_label riporta casi come mancanti | Quelle esecuzioni hanno sollevato un'eccezione o sono andate in timeout; oloproof inspect RUN_ID --failures mostra ogni errore. |
| L'esecuzione esce con 3 con una stima alta | Decide l'intervallo, non la stima. Aggiungi casi o accetta una soglia più bassa, decisa prima dell'esecuzione. |
Limitazioni
- L'SDK e lo YAML riportano tassi di successo per valutatore. Non c'è una matrice di confusione né precisione e richiamo per classe per un classificatore come questo; un blocco predictive: li fornisce per un modello con punteggio (Modelli predittivi).
- Le regole observed_count descrivono i casi che hai eseguito; non affermano nulla sugli input non visti.
- Un confronto su 18 casi risolve solo differenze grandi. Cinquanta o più casi reali sono una soglia più utile.
- oloproof.yaml non può nominare un @evaluator personalizzato; per quello serve l'SDK (L'SDK).