Ga naar de inhoud

Handleidingen

Tutorial: een classifier of gestructureerde uitvoer

Evalueer een Python-functie die supportvragen labelt, lees waarom de release geblokkeerd is, los de missers op en vergelijk de oplossing met het origineel, allemaal op je eigen machine zonder account, zonder netwerk en zonder model.

Wat je gaat bouwen

Een supportbot die een JSON-object teruggeeft met een answer en een label (refund, account of other). Je houdt hem aan drie eisen: het label is vaak genoeg juist, de uitvoer heeft altijd de juiste vorm, en geen enkel antwoord lekt iets dat op een Amerikaans burgerservicenummer (SSN) lijkt. Twee daarvan zijn formaatcontroles die geen referentieantwoord nodig hebben; één meet taaksucces tegen een referentielabel. Het verschil doet ertoe, en deze pagina houdt ze gescheiden.

De termen hieronder (case, run, metriek, interval, regel, gate) worden gedefinieerd in Kernbegrippen.

Vereisten

  • Python 3.11 of nieuwer.
  • Oloproof, geïnstalleerd in een virtuele omgeving:
python3 -m venv .venv
. .venv/bin/activate
pip install oloproof
  • Het voorbeeldproject en de kandidaatwijziging, die met het pakket meekomen. Kopieer beide naar nieuwe mappen en werk in de eerste; elk bestand staat ook hieronder, zodat je ze ook kunt overtypen:
oloproof init --example support_bot support-classifier
oloproof init --example classification support-change
cd support-classifier

Op deze pagina wordt nergens een API-sleutel, provideraccount of netwerktoegang gebruikt.

De bestanden

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 here

Voer elk commando uit vanuit de map support-classifier/. Oloproof bewaart zijn store daar in .oloproof/; verwijder die map om weer bij nul te beginnen.

De applicatie en haar adapter

Je applicatie wordt bereikt via een adapter. Voor een Python-applicatie is de adapter de functie zelf: Oloproof importeert haar, roept haar één keer per case aan met de input van de case, en legt de dictionary die ze teruggeeft vast als uitvoer van die case.

# 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"}

Om je eigen classifier te evalueren laat je zijn code staan waar die staat en schrijf je een dunne functie zoals deze die hem aanroept en een dictionary teruggeeft. De functie mag async zijn. Oloproof roept haar aan; het host, sandboxt of reset je applicatie niet, dus elke toestand die je applicatie tussen aanroepen bewaart beheer je zelf.

oloproof.yaml noemt die functie en de evaluators:

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_match

Uitvoer wordt gecachet op de broncode van de functie, de gedeclareerde version en config. Als de functie andere bestanden leest (een prompt, een regeltabel), zet die dan onder system.code_paths, zodat het systeem opnieuw draait wanneer je ze bewerkt.

De dataset

Eén case per regel. input is precies wat je functie als case ontvangt; expected is de referentie waarmee de evaluator exact_match vergelijkt:

{"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"}}

De functie geeft voor elke case een object terug zoals {"answer": "Use password reset, ...", "label": "account"}.

De evaluators kiezen

CriteriumEvaluatorHeeft expected nodigWat het meet
exact_labelexact_match op labeljataaksucces: het label is het juiste
format_validjson_schema over de hele uitvoerneeformaat: het object heeft precies de twee tekstvelden
pii_freeregex op answer, pass_if: no_matchneeeen veiligheidseigenschap van de tekst

Een formaatcontrole laat een goedgevormd fout antwoord door, dus ze kan nooit in de plaats komen van taaksucces. Een taakcontrole heeft voor elke case een referentie nodig; waar een case er geen heeft, kan exact_match haar niet scoren. Deterministische evaluators hoeven niet tegen mensen gevalideerd te worden: twee keer draaien geeft hetzelfde oordeel.

De policy

release.yaml is waartegen de run wordt beslist:

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: 0

exact-label-floor zegt dat het label in minstens 70% van de gevallen juist moet zijn, en slaagt alleen als het hele 95%-interval op of boven 0.70 ligt. De twee observed_count-regels staan geen enkele mislukking toe op de cases die je draaide; ze beschrijven deze cases, niet elke vraag die gebruikers zullen stellen.

Draaien

oloproof run

Echte uitvoer, ingekort:

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 miss

Zo lees je het:

  • 13 van de 18 labels zijn juist, 72.2%. Dat is boven 0.70, maar het interval reikt tot 46.5%: 18 cases kunnen niet aantonen dat het werkelijke percentage minstens 0.70 is. Dus de regel is INSUFFICIENT_EVIDENCE, niet PASS en niet FAIL.
  • Elke uitvoer heeft de juiste vorm en geen bevat een SSN-achtig nummer, dus beide formaatregels slagen.
  • block_on noemt INSUFFICIENT_EVIDENCE, dus de gate blokkeert en het commando eindigt met 3. Exit 0 zou betekenen dat er niets is waarop de policy blokkeert; CI gaten noemt elke code.

Draai het opnieuw en de cacheregel luidt execution 18 hit/0 miss: er is niets veranderd, dus de functie wordt niet aangeroepen.

De mislukkingen bekijken

oloproof inspect RUN_ID --failures
oloproof inspect RUN_ID --case refund_04

RUN_ID is het id op de eerste regel van de uitvoer van de run.

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: failed
case 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: passed

Het patroon is duidelijk zodra je de inputs leest: "money back", "reverse the payment", "sign in" en "two-factor" staan niet in de trefwoordlijsten, en other_04 zegt "I don't want a refund", waar het woord "refund" toch op past. Merk op dat refund_04 beide formaatcontroles haalt terwijl het fout is: dat is het gat tussen formaat controleren en succes meten.

Twee vervolgstappen zijn hier zinvol. Los de missers op (hieronder), of voeg cases toe: met meer cases bij dezelfde nauwkeurigheid wordt het interval smaller, en oloproof plan RUN_ID --run schat hoeveel.

Een echte wijziging maken

Kopieer ../support-change/app.py over app.py. Het voegt de gemiste formuleringen toe:

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):
        ...

en zet version: keywords-v2 onder system in oloproof.yaml, zodat de run als de nieuwe versie wordt vastgelegd. Daarna:

oloproof run
Gate: 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 van de 18 zijn juist en de ondergrens van het interval, 72.7%, haalt 0.70, dus de regel slaagt en het commando eindigt met 0. other_04 mislukt nog steeds: de oplossing raakte ontkenning niet.

De kandidaat met de baseline vergelijken

De runregel vraagt of de kandidaat je ondergrens haalt. Een vergelijking vraagt hoe hij verschilt van de baseline, case voor case. Kopieer ../support-change/compare.yaml naar het project; het bevat één vergelijkingsregel:

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.10
oloproof compare CANDIDATE_RUN_ID BASELINE_RUN_ID --policy compare.yaml
Comparison 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)

De kandidaat repareerde vier cases en brak er geen, een geschatte winst van 22 punten. Maar slechts vier cases veranderden, en 18 gepaarde cases laten een interval over van 12.9 punten slechter tot 57 punten beter, dat de marge van 10 punten kruist. De vergelijking kan nog niet uitsluiten dat de kandidaat slechter is dan je accepteert, dus is ze INSUFFICIENT_EVIDENCE en eindigt met 3. De regel eronder is de schatting van de omvang. Zonder --policy print compare de verschillen, zegt dat de release.yaml van het project geen vergelijkingsregel declareert, en eindigt met 0 omdat er niets werd beslist.

Een kandidaat met een baseline vergelijken legt de marge en de andere soorten regels uit.

Problemen oplossen

SymptoomOorzaak en oplossing
ModuleNotFoundError voor appDraai vanuit de map met app.py, of geef callable een modulepad dat van daaruit importeerbaar is.
Een regel noemt een metriek die geen evaluator produceertDe metric van de regel moet gelijk zijn aan het criterion van een evaluator; de fout noemt de metrieken die er zijn.
Je bewerkte de classifier en de run hergebruikte elke uitvoerDe cache volgt de broncode van de callable; een hulpbestand dat hij leest moet onder system.code_paths staan.
exact_label meldt cases als ontbrekendDie uitvoeringen gaven een fout of time-out; oloproof inspect RUN_ID --failures toont elke fout.
De run eindigt met 3 bij een hoge schattingHet interval beslist, niet de schatting. Voeg cases toe of accepteer een lagere ondergrens, besloten vóór de run.

Beperkingen

  • De SDK en YAML rapporteren slagingspercentages per evaluator. Er is geen confusion matrix of precision en recall per klasse voor een classifier als deze; een predictive:-blok doet dat voor een model met scores (Classifiers en regressors).
  • observed_count-regels beschrijven de cases die je draaide; ze doen geen uitspraak over ongeziene inputs.
  • Een vergelijking over 18 cases onderscheidt alleen grote verschillen. Vijftig of meer echte cases zijn een bruikbaardere ondergrens.
  • oloproof.yaml kan geen eigen @evaluator noemen; daarvoor is de SDK nodig (De Python-API).