Przejdź do treści

Przewodniki

Samouczek: klasyfikator lub wynik ustrukturyzowany

Oceń funkcję w Pythonie, która nadaje etykiety pytaniom do wsparcia, przeczytaj, dlaczego wydanie jest zablokowane, napraw pomyłki i porównaj poprawkę z oryginałem, wszystko na własnej maszynie, bez konta, bez sieci i bez modelu.

Co zbudujesz

Bota wsparcia, który zwraca obiekt JSON z answer i label (refund, account lub other). Postawisz mu trzy wymagania: etykieta jest poprawna wystarczająco często, wynik zawsze ma właściwy kształt i żadna odpowiedź nie ujawnia niczego, co wygląda jak amerykański numer ubezpieczenia społecznego. Dwa z nich to sprawdzenia formatu, które nie potrzebują odpowiedzi wzorcowej; jedno mierzy sukces zadania względem etykiety wzorcowej. Ta różnica ma znaczenie i ta strona trzyma je osobno.

Terminy używane poniżej (przypadek, przebieg, metryka, przedział, reguła, bramka) są zdefiniowane w Pojęciach.

Wymagania wstępne

  • Python 3.11 lub nowszy.
  • Oloproof zainstalowany w środowisku wirtualnym:
python3 -m venv .venv
. .venv/bin/activate
pip install oloproof
  • Przykładowy projekt i zmiana kandydująca, dostarczane z pakietem. Skopiuj oba do nowych katalogów i pracuj w pierwszym; każdy plik jest też pokazany poniżej, więc możesz je przepisać:
oloproof init --example support_bot support-classifier
oloproof init --example classification support-change
cd support-classifier

Nigdzie na tej stronie nie są używane klucz API, konto u dostawcy ani dostęp do sieci.

Pliki

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

Uruchamiaj każde polecenie z katalogu support-classifier/. Oloproof trzyma tam swój magazyn w .oloproof/; usuń ten katalog, aby zacząć od zera.

Aplikacja i jej adapter

Do Twojej aplikacji dociera się przez adapter. Dla aplikacji w Pythonie adapterem jest sama funkcja: Oloproof ją importuje, wywołuje raz na przypadek z input przypadku i zapisuje zwrócony słownik jako wynik tego przypadku.

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

Aby ocenić własny klasyfikator, zostaw jego kod tam, gdzie jest, i napisz cienką funkcję taką jak ta, która go wywołuje i zwraca słownik. Funkcja może być async. Oloproof ją wywołuje; nie hostuje, nie izoluje ani nie resetuje Twojej aplikacji, więc każdy stan, który aplikacja przechowuje między wywołaniami, jest Twoim zmartwieniem.

oloproof.yaml wskazuje tę funkcję i ewaluatory:

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

Wyniki są cache'owane według kodu źródłowego funkcji, zadeklarowanej version i config. Jeśli funkcja czyta inne pliki (prompt, tabelę reguł), wymień je w system.code_paths, aby ich edycja ponownie uruchamiała system.

Zbiór danych

Jeden przypadek na wiersz. input to dokładnie to, co Twoja funkcja otrzymuje jako case; expected to wzorzec, z którym porównuje ewaluator exact_match:

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

Funkcja zwraca dla każdego przypadku obiekt taki jak {"answer": "Use password reset, ...", "label": "account"}.

Wybór ewaluatorów

KryteriumEwaluatorWymaga expectedCo mierzy
exact_labelexact_match na labeltaksukces zadania: etykieta jest właściwa
format_validjson_schema na całym wynikunieformat: obiekt ma dokładnie dwa pola tekstowe
pii_freeregex na answer, pass_if: no_matchniewłasność bezpieczeństwa tekstu

Sprawdzenie formatu przepuszcza poprawnie sformułowaną błędną odpowiedź, więc nigdy nie może zastąpić sukcesu zadania. Sprawdzenie zadania potrzebuje wzorca dla każdego przypadku; gdy przypadek go nie ma, exact_match nie może go ocenić. Ewaluatory deterministyczne nie wymagają walidacji względem ludzi: dwukrotne uruchomienie daje ten sam werdykt.

Polityka

release.yaml to to, względem czego rozstrzyga się przebieg:

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 mówi, że etykieta musi być poprawna w co najmniej 70% przypadków, i przechodzi tylko wtedy, gdy cały przedział 95% jest na poziomie 0.70 lub wyżej. Dwie reguły observed_count nie dopuszczają żadnego niepowodzenia na przypadkach, które uruchomiłeś; opisują te przypadki, a nie każde pytanie, jakie zadadzą użytkownicy.

Uruchom

oloproof run

Prawdziwy wynik, skrócony:

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

Jak go czytać:

  • 13 z 18 etykiet jest poprawnych, 72,2%. To powyżej 0.70, ale przedział sięga w dół do 46,5%: 18 przypadków nie może wykazać, że prawdziwy odsetek wynosi co najmniej 0.70. Dlatego reguła ma stan INSUFFICIENT_EVIDENCE, a nie PASS i nie FAIL.
  • Każdy wynik ma właściwy kształt i żaden nie zawiera liczby przypominającej SSN, więc obie reguły formatu przechodzą.
  • block_on wymienia INSUFFICIENT_EVIDENCE, więc bramka blokuje, a polecenie kończy się kodem 3. Kod 0 oznaczałby, że nie wystąpiło nic, na czym polityka blokuje; Bramkowanie CI wymienia wszystkie kody.

Uruchom ponownie, a wiersz cache pokaże execution 18 hit/0 miss: nic się nie zmieniło, więc funkcja nie jest wywoływana.

Przejrzyj niepowodzenia

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

RUN_ID to identyfikator z pierwszego wiersza wyniku przebiegu.

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

Wzorzec jest oczywisty, gdy przeczytasz wejścia: "money back", "reverse the payment", "sign in" i "two-factor" nie ma na listach słów kluczowych, a other_04 mówi "I don't want a refund", co słowo "refund" i tak dopasowuje. Zauważ, że refund_04 przechodzi oba sprawdzenia formatu, będąc błędnym: to jest luka między sprawdzaniem formatu a mierzeniem sukcesu.

Sensowne są tu dwa kolejne działania. Napraw pomyłki (poniżej) albo dodaj przypadki: przy większej liczbie przypadków i tej samej trafności przedział się zwęża, a oloproof plan RUN_ID --run szacuje, ile ich trzeba.

Wprowadź prawdziwą zmianę

Skopiuj ../support-change/app.py na app.py. Dodaje on pominięte sformułowania:

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

i ustaw version: keywords-v2 pod system w oloproof.yaml, aby przebieg został zapisany jako nowa wersja. Następnie:

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 z 18 jest poprawnych, a dolna granica przedziału, 72,7%, przekracza 0.70, więc reguła przechodzi, a polecenie kończy się kodem 0. other_04 nadal zawodzi: poprawka nie dotknęła negacji.

Porównaj kandydata z punktem odniesienia

Reguła przebiegu pyta, czy kandydat spełnia Twój próg. Porównanie pyta, czym różni się od punktu odniesienia, przypadek po przypadku. Skopiuj ../support-change/compare.yaml do projektu; zawiera jedną regułę porównania:

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)

Kandydat naprawił cztery przypadki i nie zepsuł żadnego, co daje szacowany zysk 22 punktów. Ale zmieniły się tylko cztery przypadki, a 18 sparowanych przypadków zostawia przedział od 12,9 punktu gorzej do 57 punktów lepiej, który przecina margines 10 punktów. Porównanie nie może jeszcze wykluczyć, że kandydat jest gorszy o więcej, niż akceptujesz, więc wynik to INSUFFICIENT_EVIDENCE i kod wyjścia 3. Wiersz pod nim to oszacowanie wielkości próby. Bez --policy polecenie compare wypisuje różnice, informuje, że release.yaml projektu nie deklaruje żadnej reguły porównania, i kończy się kodem 0, ponieważ niczego nie rozstrzygnięto.

Porównywanie kandydata z punktem odniesienia objaśnia margines i inne rodzaje reguł.

Rozwiązywanie problemów

ObjawPrzyczyna i rozwiązanie
ModuleNotFoundError dla appUruchamiaj z katalogu zawierającego app.py albo podaj w callable ścieżkę modułu importowalną stamtąd.
Reguła nazywa metrykę, której nie produkuje żaden ewaluatormetric reguły musi być równe criterion ewaluatora; błąd wymienia istniejące metryki.
Edytowałeś klasyfikator, a przebieg ponownie użył każdego wynikuCache podąża za kodem źródłowym funkcji; plik pomocniczy, który czyta, musi być wymieniony w system.code_paths.
exact_label zgłasza przypadki jako brakująceTe wykonania zgłosiły wyjątek lub przekroczyły limit czasu; oloproof inspect RUN_ID --failures pokazuje każdy błąd.
Przebieg kończy się kodem 3 przy wysokiej estymacieDecyduje przedział, a nie estymata. Dodaj przypadki albo przyjmij niższy próg, ustalony przed przebiegiem.

Ograniczenia

  • SDK i YAML raportują odsetki zaliczeń dla każdego ewaluatora. Nie ma macierzy pomyłek ani precyzji i czułości dla każdej klasy dla klasyfikatora takiego jak ten; blok predictive: robi to dla modelu zwracającego wyniki liczbowe (Modele predykcyjne).
  • Reguły observed_count opisują uruchomione przypadki; nie twierdzą niczego o niewidzianych wejściach.
  • Porównanie na 18 przypadkach rozstrzyga tylko duże różnice. Pięćdziesiąt lub więcej prawdziwych przypadków to bardziej użyteczne minimum.
  • oloproof.yaml nie może wskazać własnego @evaluator; to wymaga SDK (SDK).