Guides
Tutoriel : un classifieur ou une sortie structurée
Évaluez une fonction Python qui étiquette des questions d’assistance, lisez pourquoi la publication est bloquée, corrigez les erreurs et comparez la correction à l’original, le tout sur votre propre machine, sans compte, sans réseau et sans modèle.
Ce que vous allez construire
Un bot d’assistance qui renvoie un objet JSON avec une answer et un label (refund, account ou other). Vous lui imposerez trois exigences : l’étiquette est juste assez souvent, la sortie a toujours la bonne forme, et aucune réponse ne divulgue quoi que ce soit qui ressemble à un numéro de sécurité sociale américain. Deux d’entre elles sont des vérifications de format qui n’ont besoin d’aucune réponse de référence ; l’autre mesure la réussite de la tâche par rapport à une étiquette de référence. La différence compte, et cette page les garde séparées.
Les termes utilisés ci-dessous (cas, exécution, métrique, intervalle, règle, porte) sont définis dans Concepts.
Prérequis
- Python 3.11 ou ultérieur.
- Oloproof, installé dans un environnement virtuel :
python3 -m venv .venv
. .venv/bin/activate
pip install oloproof- Le projet d’exemple et la modification candidate, livrés avec le paquet. Copiez-les tous deux dans de nouveaux répertoires et travaillez dans le premier ; chaque fichier est aussi listé ci-dessous, vous pouvez donc les saisir vous-même :
oloproof init --example support_bot support-classifier
oloproof init --example classification support-change
cd support-classifierAucune clé d’API, aucun compte de fournisseur ni aucun accès réseau n’est utilisé sur cette page.
Les fichiers
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 hereLancez chaque commande depuis le répertoire support-classifier/. Oloproof y garde son stockage dans .oloproof/ ; supprimez ce répertoire pour repartir de zéro.
L’application et son adaptateur
Votre application est atteinte par un adaptateur. Pour une application Python, l’adaptateur est la fonction elle-même : Oloproof l’importe, l’appelle une fois par cas avec l’input du cas, et enregistre le dictionnaire qu’elle renvoie comme sortie de ce cas.
# 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"}Pour évaluer votre propre classifieur, laissez son code où il est et écrivez une fine fonction comme celle-ci qui l’appelle et renvoie un dictionnaire. La fonction peut être async. Oloproof l’appelle ; il n’héberge, n’isole ni ne réinitialise votre application, donc tout état qu’elle garde entre deux appels est à votre charge.
oloproof.yaml nomme cette fonction et les évaluateurs :
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_matchLes sorties sont mises en cache selon la source de la fonction, la version déclarée et la config. Si la fonction lit d’autres fichiers (un prompt, une table de règles), listez-les sous system.code_paths, pour que les modifier relance le système.
Le jeu de données
Un cas par ligne. input est exactement ce que votre fonction reçoit comme case ; expected est la référence avec laquelle l’évaluateur exact_match compare :
{"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 fonction renvoie, pour chaque cas, un objet tel que {"answer": "Use password reset, ...", "label": "account"}.
Choisir les évaluateurs
| Critère | Évaluateur | Besoin de expected | Ce qu’il mesure |
|---|---|---|---|
| exact_label | exact_match sur label | oui | la réussite de la tâche : l’étiquette est la bonne |
| format_valid | json_schema sur toute la sortie | non | le format : l’objet a exactement les deux champs texte |
| pii_free | regex sur answer, pass_if: no_match | non | une propriété de sûreté du texte |
Une vérification de format laisse passer une réponse fausse mais bien formée ; elle ne peut donc jamais remplacer la réussite de la tâche. Une vérification de tâche a besoin d’une référence pour chaque cas ; là où un cas n’en a pas, exact_match ne peut pas le noter. Les évaluateurs déterministes n’ont besoin d’aucune validation face à des personnes : en lancer un deux fois donne le même verdict.
La politique
release.yaml est ce par rapport à quoi l’exécution est décidée :
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 dit que l’étiquette doit être juste au moins 70% du temps, et ne passe que lorsque l’intervalle à 95% entier est au niveau de 0.70 ou au-dessus. Les deux règles observed_count n’autorisent aucun échec sur les cas que vous avez exécutés ; elles décrivent ces cas, pas toutes les questions que poseront les utilisateurs.
L’exécuter
oloproof runSortie réelle, abrégée :
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 missComment la lire :
- 13 étiquettes sur 18 sont justes, 72.2%. C’est au-dessus de 0.70, mais l’intervalle descend jusqu’à 46.5% : 18 cas ne peuvent pas montrer que le vrai taux est d’au moins 0.70. La règle est donc INSUFFICIENT_EVIDENCE, ni PASS ni FAIL.
- Chaque sortie a la bonne forme et aucune ne contient de numéro ressemblant à un SSN, donc les deux règles de format passent.
- block_on liste INSUFFICIENT_EVIDENCE, donc la porte bloque et la commande se termine avec 3. Le code 0 signifierait que rien de ce sur quoi la politique bloque n’est survenu ; Portes en CI liste tous les codes.
Relancez-la et la ligne du cache indique execution 18 hit/0 miss : rien n’a changé, donc la fonction n’est pas appelée.
Inspecter les échecs
oloproof inspect RUN_ID --failures
oloproof inspect RUN_ID --case refund_04RUN_ID est l’identifiant sur la première ligne de la sortie de l’exécution.
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: passedLe motif est évident dès qu’on lit les entrées : « money back », « reverse the payment », « sign in » et « two-factor » ne sont pas dans les listes de mots-clés, et other_04 dit « I don’t want a refund », que le mot « refund » attrape quand même. Notez que refund_04 passe les deux vérifications de format tout en étant faux : c’est l’écart entre vérifier le format et mesurer la réussite.
Deux actions suivantes ont un sens ici. Corriger les erreurs (ci-dessous), ou ajouter des cas : avec plus de cas à la même exactitude, l’intervalle se resserre, et oloproof plan RUN_ID --run estime combien.
Faire une vraie modification
Copiez ../support-change/app.py par-dessus app.py. Il ajoute les formulations manquées :
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):
...et réglez version: keywords-v2 sous system dans oloproof.yaml, pour que l’exécution soit enregistrée comme la nouvelle version. Puis :
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 sur 18 sont justes et la borne inférieure de l’intervalle, 72.7%, dépasse 0.70, donc la règle passe et la commande se termine avec 0. other_04 échoue toujours : la correction n’a pas touché à la négation.
Comparer le candidat à la référence
La règle d’exécution demande si le candidat atteint votre plancher. Une comparaison demande en quoi il diffère de la référence, cas par cas. Copiez ../support-change/compare.yaml dans le projet ; il contient une règle de comparaison :
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)Le candidat a corrigé quatre cas et n’en a cassé aucun, un gain estimé de 22 points. Mais seuls quatre cas ont changé, et 18 cas appariés laissent un intervalle allant de 12.9 points de moins à 57 points de plus, qui traverse la marge de 10 points. La comparaison ne peut pas encore exclure que le candidat soit pire de plus que ce que vous acceptez ; elle est donc INSUFFICIENT_EVIDENCE et se termine avec 3. La ligne en dessous est l’estimation de taille. Sans --policy, compare affiche les différences, indique que le release.yaml du projet ne déclare aucune règle de comparaison, et se termine avec 0 parce que rien n’a été décidé.
Comparer un candidat à une référence explique la marge et les autres types de règles.
Dépannage
| Symptôme | Cause et correction |
|---|---|
| ModuleNotFoundError pour app | Lancez depuis le répertoire contenant app.py, ou donnez à callable un chemin de module importable depuis là. |
| Une règle nomme une métrique qu’aucun évaluateur ne produit | La metric de la règle doit être égale au criterion d’un évaluateur ; l’erreur liste les métriques existantes. |
| Vous avez modifié le classifieur et l’exécution a réutilisé chaque sortie | Le cache suit la source de l’appelable ; un fichier auxiliaire qu’il lit doit être listé sous system.code_paths. |
| exact_label signale des cas manquants | Ces exécutions ont levé une exception ou expiré ; oloproof inspect RUN_ID --failures montre chaque erreur. |
| L’exécution se termine avec 3 malgré une estimation élevée | C’est l’intervalle, pas l’estimation, qui décide. Ajoutez des cas ou acceptez un plancher plus bas, décidé avant l’exécution. |
Limites
- Le SDK et le YAML rapportent des taux de réussite par évaluateur. Il n’y a ni matrice de confusion ni précision et rappel par classe pour un classifieur comme celui-ci ; un bloc predictive: les fournit pour un modèle à scores (Modèles prédictifs).
- Les règles observed_count décrivent les cas que vous avez exécutés ; elles n’affirment rien sur des entrées non vues.
- Une comparaison sur 18 cas ne distingue que de grandes différences. Cinquante cas réels ou plus sont un plancher plus utile.
- oloproof.yaml ne peut pas nommer un @evaluator personnalisé ; il faut pour cela le SDK (Le SDK).