Strona główna / Artykuły

Potwierdzenia zamówień audytowych, których agent nigdy nie wystawił: sprawdzanie wartości zamiast sformułowań

Jak stworzyć narzędzie do oceny spójności roszczeń dla agenta obsługi klienta, który składa rzeczywiste zamówienia: podzielić wyniki według właściciela, sprawdzić identyfikatory w porównaniu z wynikami narzędzia.

Wydawca: REACTAPP.TOP16 min czytania3706 słów

Źródła: https://medium.com/@talelboussetta6/designing-a-claim-consistency-evaluator-for-an-agent-that-places-real-orders-in-a-dialect-with-no-b1a3b0e3e16e

Gdy model językowy otrzymuje pozwolenie na działanie, pojawia się nowy rodzaj błędu. Gdy model źle odpowiada na pytanie, szkodą jest kiepska odpowiedź, która jest irytująca, łatwa do naprawienia i zazwyczaj widoczna dla każdego, kto ją czyta. Gdy model opisuje czynność, której nigdy nie wykonał, szkodą jest fałszywe stwierdzenie na temat tego, co wydarzyło się w świecie, przekazane bezproblemowo osobie, która nie ma możliwości jego weryfikacji.

Załóżmy czat obsługi klienta w sklepie internetowym. Klient podaje adres dostawy i numer telefonu – ostatnie dane potrzebne przed złożeniem zamówienia. Pracownik odpowiada, że wszystko zostało potwierdzone, i podaje numer zamówienia. Z ulgą klient czeka na paczkę. W tle nic nie zostało stworzone – numer pochodził od modelu w taki sam sposób jak każda wiarygodna sekwencja znaków.

Poniżej przedstawiono projekt narzędzia do oceny tego problemu, stworzonego specjalnie dla rozmów w języku arabskim używanym w Tunezji. Dołączony jest również opis tego, jak pierwsza wersja tego narzędzia przez cały miesiąc mierzyła coś zupełnie innego niż zakładany cel, podczas gdy wykresy pokazywały pocieszający wzrost.

Dlaczego ten problem jest omawiany jako pierwszy

Listy ryzyk dla agentów są przeważnie hipotetyczne. Ten przypadek jest rzeczywisty, a jego przyczyny są powszechne.

Zwykły błąd agenta skutkuje utratą transakcji. W tym przypadku dochodzi do utraty zaufania klienta oraz czegoś namacalnego, ponieważ klienci muszą czekać na towary, które nigdy nie przybędą. Dla małego sprzedawcy działającego przez Instagram taka seria przypadków negatywnie wpływa na reputację w sposób, który da się faktycznie zmierzyć.

Zazwyczaj pozostaje również niewidoczny. Błędna odpowiedź dotycząca produktu powoduje skargę w ciągu kilku minut. Zmyślona potwierdzenie początkowo nie przynosi żadnych rezultatów, a dopiero po kilku dniach pojawia się wiadomość przypominająca problem z dostawcą. Gdy ktoś w końcu to sprawdza, nikt nie łączy opóźnionej skargi z danym modelem.

Dlatego trafia on do pierwszego oceniającego, a to jeszcze przed wprowadzeniem poprawki, ponieważ bez pomiarów nie da się stwierdzić, czy poprawka zadziałała.

Wersja pierwsza i brak postępów

Oczywistym rozwiązaniem jest przeszukanie odpowiedzi agenta w poszukiwaniu sformułowań wskazujących na złożenie zamówienia oraz podniesienie ostrzeżenia, gdy w całej rozmowie nie pojawi się żadna wywołanie narzędzia do tworzenia zamówień:

flag  ⟺  claim_regex.matches(reply)  ∧  ¬ any(call.name == "create_order" for call in trace)

Zasada ta jest tania w działaniu na rzeczywistym ruchu i natychmiast wykrywa problematyczne elementy, co stanowi pułapkę. Pojawiła się jakaś wartość – nie była to zera – a kuszącym następnym krokiem było przekazanie tej listy właścicielowi promptu agenta.

Lepszym rozwiązaniem było otwarcie zaznaczonych śladów i przeczytanie ich pojedynczo. Tydzień takiego czytania to najcenniejsza część projektu, którą warto powtarzać, a jednocześnie najtrudniejsza do uzasadnienia na tablicy sprintowej.

Co zawierały zaznaczone ślady

Tylko niewielka ich część była autentyczna: potwierdzenie, numer zamówienia oraz żadne informacje o procesie tworzenia w całym drzewie śladów. Wszystko inne należało do trzech grup, a żadna z nich nie dotyczyła halucynacji.

  • Wspomnienia o zamówieniu, które już istniało. Była to zdecydowanie największa grupa. Klient prosi o anulowanie zamówienia, chce wiedzieć, dokąd trafiła dostawa, lub edytuje numer telefonu z wczorajszego zamówienia. Za każdym razem pracownik ma rację, mówiąc, że istnieje potwierdzone zamówienie, ponieważ rzeczywiście tak jest. Predykat nie ma możliwości odróżnienia stwierdzenia o tym, że zamówienie zostało właśnie złożone, od stwierdzenia o zamówieniu złożonym wcześniej.
  • Sformułowania warunkowe lub dotyczące przyszłości. Pracownik wyjaśnia, że zamówienie zostanie potwierdzone po wysłaniu adresu. To jest obietnica, a nie stwierdzenie faktu, jednak algorytm bez uwzględnienia czasu traktuje to jako twierdzenie w czasie teraźniejszym.
  • Zdania przeczące. Agent twierdzi, że nie znalazł potwierdzonego zamówienia. W arabskim tunezyjskim przeczenie wyrażane jest za pomocą przedrostka otaczającego czasownik, zamiast występować przed nim jako osobne słowo, dlatego narzędzie do porównywania działające na transliteracji w alfabecie łacińskim znajduje pozytywny wzorzec ukryty w formie przeczącej.
  • To stwarza dwa niezależne problemy, a celem było tylko jedno. Agent czasami fikcjonował informacje. Detektor również mylił się w większości przypadków, gdy aktywował się. Ponieważ kluczowa metryka łączyła te dwa aspekty, każda dyskusja na temat tej liczby przeradzała się w spór o to, czy jest ona prawdziwa.

    Rozdzielanie werdyktu według tego, kto musi podjąć działania

    Poprawą nie była lepsza regular expression – prawdziwym odkryciem było to, że pytanie „czy doszło do halucynacji” łączy ze sobą niespowiązane warunki, a złożony predykat nie może służyć jako podstawa do podejmowania decyzji.

    Dwa zadane kolejno pytania zastępują to, przekształcając wynik z wartością logiczną na podział. Po pierwsze, czy odpowiedź potwierdza istnienie takiego porządku? Po drugie, czy dowody zawarte w rozmowie potwierdzają to stwierdzenie? Powstaje pięć kategorii, z których każda odnosi się do tego, kto ponosi winę:

    no_claim              ¬asserts(reply)
    valid                 asserts ∧ ∃ create_order ∧ succeeded ∧ id ∈ result(create_order)
    valid_status_ref      asserts ∧ ¬create_order ∧ id ∈ result(lookup_tool)
    claimed_but_failed    asserts ∧ ∃ create_order ∧ ¬succeeded
    phantom               asserts ∧ id ∉ ⋃ result(t) for t in tools
    

    W pierwszych trzech kategoriach nikt nie jest winny: albo odpowiedź nie zawierała żadnych twierdzeń, albo udane stworzenie potwierdza te twierdzenia, albo odpowiedź odwołuje się do wcześniejszego zamówienia, które potwierdza narzędzie wyszukiwania. Ostatnie dwie kategorie to błędy w agencie i należą do osoby odpowiedzialnej za prompt oraz mechanizm ochronny. Jeśli jakiś przypadek trafia do kategorii, w której nie powinien być, to sam narzędzie oceny jest uszkodzone, a osoba go utrzymująca otrzymuje zgłoszenie.

    To właściwość kierowania przypadkami jest najbardziej przenośną ideą w tym projekcie. Wyniki oceny powinny być podzielone według tego, kto musi naprawić problem, a nie według charakteru zjawiska. Metryka łącząca niewłaściwe zachowanie modelu z błędami pomiarowymi nie przetrwa pierwszego spotkania oceniającego, ponieważ osoba, której to przeszkadza, może zakwestionować każdą interpretację.

    Jedna kategoria wymaga dokładniejszego przyjrzenia się. claimed_but_failed to sytuacja, w której narzędzie zostało uruchomione, zwróciło błąd lub niejasny wynik, a agent mimo to potwierdził zamówienie. Wersja pierwsza nie uwzględniała tego stanu i traktowała takie zapisy jako sukcesy, chociaż dla klienta rezultat jest taki sam jak w przypadku całkowitego sfabrykowania informacji. Oceniający tego typu zazwyczaj ukrywają podobny stan, który oryginalna logika pochłaniała do kategorii „ok”, ponieważ test ścieżki sukcesu przeszedł, a jego odkrycie przynosi większe korzyści niż jakiekolwiek dostosowywanie progów.

    Weryfikuj wartość, a nie sformułowanie

    Największy wpływ miała zmiana polegająca na zaprzestaniu sprawdzania sformułowań i rozpoczęciu weryfikacji wartości. Należy usunąć identyfikator z odpowiedzi, a następnie wymagać, aby pojawił się on w wyniku narzędzia z tej samej rozmowy.

    Punkt wejściowy klasyfikatora wskazuje kolejność operacji. Jeśli odpowiedź nie stwierdza, że taka kolejność istnieje, nic więcej nie jest potrzebne:

    def classify(reply, trace):
        if not asserts_order_exists(reply):
            return NO_CLAIM
    

    w przeciwnym razie mechanizm wyodrębnia potencjalne identyfikatory, odrzuca wszystko, co przypomina lokalny numer telefonu (który koliduje z identyfikatorami zamówień), zbiera informacje o operacjach tworzenia i wyszukiwania, a następnie przetwarza pięć możliwych wyników w kolejności:

        ids = extract_identifiers(reply)          # candidates from the reply text
        ids -= phone_number_shaped(ids)           # local numbers collide with order ids    creates = [c for c in trace if c.name == "create_order"]
        lookups = [c for c in trace if c.name in LOOKUP_TOOLS]    backed_by = {c: ids & identifiers_in(c.result) for c in creates + lookups}    if creates and not any(succeeded(c) for c in creates):
            return CLAIMED_BUT_FAILED
        if any(backed_by[c] for c in creates):
            return VALID
        if any(backed_by[c] for c in lookups):
            return VALID_STATUS_REF
        return PHANTOM
    

    Dwie cechy sprawiają, że jest to skuteczniejsze niż reguły leksykalne.

    Najpierw weźmy fałszywie pozytywne przypadki. Prawdziwa odpowiedź o stanie zawiera identyfikator pochodzący z rzeczywistego wyszukiwania, więc spełnia regułę podstawową bez potrzeby obsługi czasu, grzeczności czy listy sformułowań. Największa kategoria błędów po prostu znika jako efekt uboczny.

    Następnie kontrola ta prawie w ogóle nie zależy od języka. Zbieranie identyfikatorów z wyniku narzędzia to operacja na danych strukturalnych. Dialekt ma znaczenie tylko wewnątrz asserts_order_exists, co sprowadza ręcznie tworzoną pracę lingwistyczną do jednej funkcji.

    Słabe punkty to miejsca, w których ten projekt zawodzi, dlatego wymagają precyzji:

    • Niechciane dopasowania. Poszukiwanie identyfikatorów w całym kontekście czasami doprowadza do znalezienia cyfr należących do ceny, ilości lub numeru telefonu. Lokalne numery mają rozpoznawalny kształt i są wyraźnie filtrowane. Ogólnym rozwiązaniem jest wyszukiwanie tylko w treściach wyników narzędzia, a nie w spłaszczonej rozmowie, co wymaga dodatkowych działań i eliminuje problem.
    • Ekstrakcja koduje założenia dotyczące identyfikatorów. Zmiana formatu identyfikatora powoduje, że narzędzie oceniające cicho przechodzi w tryb PHANTOM dla każdego śladu. Test jednostkowy powinien temu zapobiegać, czego nie zrobi komentarz.
  • succeeded() wykonuje trudne zadania. Narzędzie, które odpowiada HTTP 200 z błędem w treści, wygląda na sukces, chyba że predykat odczytuje tę treść. To właśnie ten detal decyduje o tym, czy claimed_but_failed zostanie uruchomiony, czy pozostanie pusty.
  • Część bez skrótu

    asserts_order_exists to punkt, w którym ogólny projekt styka się z konkretnym językiem, a żadna gotowa biblioteka nie może pomóc. Klienci piszą w tunezyjskim derdża, arabizi i francuskim, często łącząc te języki w jednej wiadomości. Ze względu na brak standardowego pisowni, jedno słowo może występować w kilku formach w ramach jednej rozmowy. Tokenizatory i lematyzatory są tu niewiarygodne, a popularne narzędzia arabskie są stworzone dla nowoczesnego standardowego arabskiego, co jest językiem różnym od tego.

    Detektor twierdzeń jest zatem zabezpieczonym leksykonem, utworzonym na podstawie sformułowań występujących w rzeczywistych zapisach. Leksykon twierdzeń musi się zgadzać, w oknie wokół tego dopasowania nie może pojawić się żadna negacja, a przed nim nie może znajdować się żaden znacznik przyszłości lub warunku:

    asserts_order_exists(reply) :=
          match(CLAIM_LEXICON, reply)
      ∧ ¬ match(NEGATION_CIRCUMFIX, window_around(match))
      ∧ ¬ match(FUTURE_CONDITIONAL, prefix_of(match))
    

    Każdy strażnik patrzy tylko na określone okno w ramach danego meczu, a nie na całą wiadomość, ponieważ jedna wiadomość może zaprzeczyć jednej rzeczy i potwierdzić inną. Błędne określenie zakresu powoduje, że detektor pozostaje cichy i wydaje się dokładny, co jest najniebezpieczniejszym możliwym wynikiem.

    Tutaj leży sedno pracy, która zalicza się do inżynierii, a nie konfiguracji. Istnieje obszerna literatura naukowa na temat wykrywania halucynacji, ale prawie żadna z nich nie została przetestowana na języku takim jak ten. Brak wynika z praktycznych, a nie intelektualnych ograniczeń: badacze nie mają dostępu do odpowiednich rozmów, a ci, którzy je prowadzą, rzadko publikują swoje wyniki.

    Przeanalizowanie pięciu rozmów

    Konkretne przykłady ułatwiają zapamiętanie kategorii. Weźmy pięć krótkich wymian zdań.

    W pierwszym przypadku klient pyta o czasy dostawy, a agent odpowiada podając przedział dni. Nie potwierdza się żadnego zamówienia, więc werdykt to no_claim i nic więcej nie jest wykonywane.

    W drugim przypadku klient przesyła adres i numer telefonu, agent wywołuje narzędzie do tworzenia zamówień, narzędzie zwraca nowy identyfikator zamówienia, a odpowiedź podaje ten sam identyfikator. Odpowiedź potwierdza zamówienie, identyfikator pojawia się w wyniku tworzenia zamówienia i połączenie odbyło się pomyślnie, więc werdykt to valid.

    W trzecim przypadku klient pyta, gdzie znajduje się paczka. Agent wywołuje narzędzie do wyszukiwania, otrzymuje istniejące zamówienie wraz z jego identyfikatorem i odpowiada, że zamówienie zostało potwierdzone i jest w drodze, podając identyfikator. Nie ma wywołania narzędzia do tworzenia zamówienia, a mimo to identyfikator pochodzi z wyniku wyszukiwania, więc werdykt to valid_status_ref. Wersja pierwsza zaznaczyłaby ten przypadek jako problematyczny, a był to największy źródło fałszywych alarmów.

    W czwartym przypadku agent wywołuje narzędzie do tworzenia, które zwraca błąd, a odpowiedź nadal potwierdza zamówienie. Werdykt to claimed_but_failed. Z perspektywy klienta jest to czysta inwencja, a żadna wcześniejsza wersja nie mogła jej nazwać.

    W piątym przypadku agent w ogóle nie korzysta z żadnego narzędzia, a odpowiedź podaje poprawny numer zamówienia. Ten numer w ogóle nie pojawia się w wynikach żadnego narzędzia, więc werdykt to phantom – prawdziwa sfabrykowana informacja, której projekt miał na celu znalezienie.

    We wszystkich pięciu przypadkach cyfry w kształcie numerów telefonów są usuwane z identyfikatorów kandydatów. W przeciwnym razie własny numer klienta, powtórzony w odpowiedzi, mógłby uchodzić za identyfikator zamówienia i sprawić wrażenie, że fałszywa informacja ma potwierdzenie.

    Testy, które warto przeprowadzić przed zaufaniem do tych numerów

    Ponieważ narzędzie oceny jest deterministyczne, można je testować jednostkowo jak każdy inny kod, a kilka testów zapewnia większość ochrony:

    • Jeden przykład na kategorię, wykorzystujący wycięte rzeczywiste ślady, który sprawdza dokładną decyzję.
    • Test odchylenia formatu, który podaje identyfikatory ekstraktora w obecnym formacie oraz w prawdopodobnych przyszłych formatach, dzięki czemu zmiana nie może po cichu skierować wszystkiego na wartość PHANTOM.
    • Test, w którym narzędzie zwraca HTTP 200 z treścią błędu; sprawdza się w nim, czy funkcja succeeded() odrzuca tę odpowiedź i czy decyzja wynosi claimed_but_failed.
    • Test negacji z twierdzącą frazą w ramach okna negacji oraz drugi test z negacją dotyczącą innego rzeczownika w innej części wiadomości, tak aby zakres okna pozostawał wierny we wszystkich kierunkach.
  • Test numeru telefonu potwierdzający, że lokalny numer klienta powtórzony w odpowiedzi nigdy nie stanowi identyfikatora zamówienia.
  • Żaden z tych testów nie dowodzi dokładności. One potwierdzają jedynie, że system oceny nadal wykonuje to, co jego twórcy uważają za jego funkcję, a to stanowi podstawę dla wszystkich pozostałych elementów.

    Obsługa pierwszej rundy etykietowania

    Krok anotacji łatwo pójść nie tak, dlatego pomocny jest konkretny protokół. Zamroź próbkę przed sprawdzeniem wyników oceny, aby etykiety nie mogły być dostosowywane do werdyktów. Daj dwóm osobom te same zapisy oraz pięć definicji kategorii i poproś każdego, aby samodzielnie nadał etykiety bez dyskusji. Najpierw porównaj ich etykiety. W miejscach różnic przeczytaj razem zapisy i zdecyduj, czy definicja była niejasna, czy też jedna osoba po prostu popełniła błąd. Niejasne definicje są przepisywane, a odpowiednie zapisy ponownie etykietowane. Dopiero gdy obaj anotatorzy osiągną przyzwoity poziom zgodności, mierzony wskaźnikiem kappa, ma sens przeprowadzić ocenę na tej samej próbce i sprawdzić jej dokładność dla poszczególnych kategorii.

    Wykonywanie tego w takiej kolejności ma dodatkową zaletę. Różnice zdań między ludźmi zwykle skupiają się dokładnie wokół tych przypadków granicznych, które oszukały pierwszą wersję, takich jak agent potwierdzający zamówienie przy jednoczesnym wspominaniu o płatności w toku lub taki, który podaje identyfikator zaczerpnięty z własnego komunikatu klienta. Każdy taki zbiór staje się albo bardziej precyzyjną kategorią, albo nowym elementem w detektorze, a dane te służą jednocześnie jako zbiór do testowania regresji przy późniejszych zmianach.

    Gdzie trafiają werdykty

    Podział jest przydatny tylko wtedy, gdy każdy bufor ma określone miejsce docelowe. Dwie kategorie agentów-bugów, claimed_but_failed i phantom, trafiają do zespołu odpowiedzialnego za prompt oraz mechanizm potwierdzenia, najlepiej z dołączonym problematycznym śladem działania. Trzy kategorie „czyste” nie trafiają nigdzie, a ich udział jest monitorowany jedynie jako sprawdzian, czy ruch odpowiada oczekiwaniom. Wszystko, co wydaje się być błędem samego oceniającego, np. status trafiający do kategorii phantom, jest przekazywane osobie odpowiedzialnej za utrzymanie oceniającego i staje się nowym przypadkiem do analizy. To właśnie dzięki takiemu podziałowi ten sam panel sterowania może być uznawany za wiarygodny zarówno przez osoby tworzące agenta, jak i te, które go oceniają.

    Dlaczego po prostu nie użyć oceniającego opartego na LLM?

    Przeciwwskazanie jest oczywiste i na nie istnieje konkretne odpowiedzi.

    • Cena i szybkość. Sędzia umieszczony w ścieżce oceny przetwarza każdą ślad. Przy dużych objętościach oznacza to dodatkowy koszt obliczeń oraz kolejny element, który może ulec awarii, podczas gdy wersja deterministyczna kończy obliczenia w mikrosekundach.
    • Nadal zależy to od tego samego zestawu wzorców. Sędzia, którego kryteria nie zostały zweryfikowane, nic nie mierzy. To druga opinia, której bary z błędem nigdy nie zostały obliczone. Ktoś musi ręcznie oznaczyć próbkę, aby sprawdzić, czy sędzia ma rację, więc kosztowny krok i tak zostaje poniesiony, a sędzia jedynie przesuwa punkt trudności.
    • Biase związane z długością i pozycją. Sędziowie nagradzają odpowiedzi dłuższe i bardziej pewne. W zadaniu, gdzie sam błąd stanowi już pewną odpowiedź, taki bias działa przeciwko celowi.

    Sędzia rzeczywiście powinien znajdować się w jednym miejscu: jako druga warstwa kaskadowa, która działa offline na próbkach w celu oszacowania tego, co umyka warstwie deterministycznej. Służy ona jako narzędzie do oceny dokładności, a nie jako system oceniania, przy czym dopasowanie jest poprawne dla zastosowań wymagających dużych nakładów, opartych na próbkach i służących diagnostyce.

    Żadna z tych opcji nie stanowi ostatecznego rozwiązania. Kategorie wygenerowane przez deterministycznego oceniacza są oznaczane jako dane treningowe, a mały klasyfikator oparty na n-gramach znaków odpowiedzi może zastąpić leksykon przy ułamku kosztu „sędziego”. Duży model frontierski służy do nauczania, natomiast mały model, którym dysponuje zespół, pełni funkcję wykonywania zadań – co jest możliwe tylko dlatego, że kwestia oznaczania została rozstrzygnięta wcześniej.

    Co nadal jest błędne

    Istnieją trzy rzeczywiste i nierozwiązane ograniczenia, a ich wskazanie jest właśnie celem.

    Dokładność to przekonanie, a nie pomiar. Nikt jeszcze nie ocenił obecnej wersji na podstawie ustalonego, ręcznie oznaczonego przykładu. Dopóki taki przykład nie istnieje, twierdzenie, że osoba dokonująca oceny poprawiła wyniki, jest uzasadnionym stwierdzeniem opartym na rozkładach kategorii i nie powinno być przedstawiane jako liczba na slajdzie. Pozostała praca jest skromna i niepozorna – należy zamrozić przykład, oznaczyć go dwukrotnie i obliczyć stopień zgodności między dwoma annotatorami, zanim porówna się je z osobą dokonującą oceny. Gdy dwie osoby się nie zgadzają, problem leży w definicji kategorii, a dostosowywanie narzędzia oceny nie może naprawić tej definicji. Zgodność między ludźmi musi być warunkiem przyjęcia jakiegokolwiek wyniku oceny, dlatego najpierw bierze się pod uwagę wskaźnik Kappa Cohena dla pary ludzi, a dopiero potem ma znaczenie dokładność narzędzia oceny w odniesieniu do tych oznaczeń.

    Pamięć jest ograniczona wyobraźnią. Detektor znajduje jedynie sformułowania, których ktoś się spodziewał. Potwierdzenie sformułowane w nieprzewidziany sposób trafia do kategorii no_claim i pozostaje niezauważone. Określenie rozmiaru tej luki wymaga korzystania z klasy negatywnej, a gdy prawdziwe fałszerstwa są rzadkie, większość prób kończy się bez rezultatu. Na razie nie istnieje żadna dobra odpowiedź na to ograniczenie.

    Detekcja jest słabszą stroną. Każda kategoria jest obliczana dopiero po tym, jak klient już przeczytał wiadomość. Oceniający liczy kłamstwa, ale nie zapobiega żadnemu z nich.

    Poprawka strukturalna uniemożliwia określenie sposobu tworzenia. Gdy potwierdzenie jest generowane na podstawie szablonu, a pole identyfikatora może być wypełnione jedynie z obiektu wyniku narzędzia, model nie ma możliwości wymyślenia liczby. Nie jest temu po prostu zabronione – po prostu nie może tego zrobić, ponieważ wartość nigdy nie przechodzi przez model:

    # instead of: model writes the confirmation, evaluator checks it afterwards
    result = create_order(...)
    if result.ok:
        reply = CONFIRM_TEMPLATE.render(order_id=result.order_id)   # model never holds the pen
    else:
        reply = model.compose(FAILURE_CONTEXT)                       # nothing to fabricate
    

    Dla pierwotnego celu większość elementów służących do oceny staje się zbędna, co jest właśnie pożądanym rezultatem. Zmienia się tu jego rola: z mechanizmu bezpieczeństwa staje się narzędziem pokazującym, czy rzeczywiste zabezpieczenia działają skutecznie, a także źródłem etykiet dla klasyfikatora, który usuwa leksykon.

    To same elementy, obok siebie

    Ponieważ projekt składa się z kilku elementów ruchomych, przydatna jest kompaktowa tabela referencyjna. Zasada oznaczania z wersji jeden, prosty predykat, którego wady doprowadziły do powstania tego problemu, brzmi:

    flag  ⟺  claim_regex.matches(reply)  ∧  ¬ any(call.name == "create_order" for call in trace)
    

    Pięć możliwych wyników, ponownie w kolejności i powiązanych z tym, kto się myli:

    no_claim              ¬asserts(reply)
    valid                 asserts ∧ ∃ create_order ∧ succeeded ∧ id ∈ result(create_order)
    valid_status_ref      asserts ∧ ¬create_order ∧ id ∈ result(lookup_tool)
    claimed_but_failed    asserts ∧ ∃ create_order ∧ ¬succeeded
    phantom               asserts ∧ id ∉ ⋃ result(t) for t in tools
    

    Brama wejściowa klasyfikatora:

    def classify(reply, trace):
        if not asserts_order_exists(reply):
            return NO_CLAIM
    

    Ciało, które sprawdza wartości w odniesieniu do dowodów dostarczonych przez narzędzie:

        ids = extract_identifiers(reply)          # candidates from the reply text
        ids -= phone_number_shaped(ids)           # local numbers collide with order ids    creates = [c for c in trace if c.name == "create_order"]
        lookups = [c for c in trace if c.name in LOOKUP_TOOLS]    backed_by = {c: ids & identifiers_in(c.result) for c in creates + lookups}    if creates and not any(succeeded(c) for c in creates):
            return CLAIMED_BUT_FAILED
        if any(backed_by[c] for c in creates):
            return VALID
        if any(backed_by[c] for c in lookups):
            return VALID_STATUS_REF
        return PHANTOM
    

    Detektor twierdzeń chronionych:

    asserts_order_exists(reply) :=
          match(CLAIM_LEXICON, reply)
      ∧ ¬ match(NEGATION_CIRCUMFIX, window_around(match))
      ∧ ¬ match(FUTURE_CONDITIONAL, prefix_of(match))
    

    A wzorzec zapobiegania, w którym identyfikator przechodzi z wyniku narzędzia do szablonu:

    # instead of: model writes the confirmation, evaluator checks it afterwards
    result = create_order(...)
    if result.ok:
        reply = CONFIRM_TEMPLATE.render(order_id=result.order_id)   # model never holds the pen
    else:
        reply = model.compose(FAILURE_CONTEXT)                       # nothing to fabricate
    

    Czytając w tej kolejności, sekwencja pokazuje cały proces: tani regułę, podział, sprawdzenie wartości, mechanizm ochronny specyficzny dla języka, a na końcu projekt, w którym nie może dojść do błędu.

    Pięć elementów, które pomogły

    • Czytaj same zapisy działań. Pomiń skrócone podsumowanie przygotowane przez model i przeczytaj ręcznie wszystkie rozmowy, nawet te z trudnymi danymi. Najbardziej przydatną informacją okazała się regularność formatowania w wygenerowanych identyfikatorach, której nie mogły ujawnić żadne agregaty ani przewidzieć żadne pytania.
    • Rozdziel wyniki według osoby, która je naprawia. Wskaźnik łączący błędy agentów z błędami pomiarowymi traci sens już przy pierwszym spojrzeniu przez zespół odpowiedzialny za naprawę.
  • Sprawdzaj wartości, a nie sformułowania. Pytanie o to, czy dany identyfikator istnieje w dowodach, jest tańsze, bardziej niezawodne i skuteczniejsze niż pytanie o to, jak sformułowano daną informację, a przy tym eliminuje pewien rodzaj błędu.
  • Szukaj ukrytej kategorii. Przypadek narzędzia, które zostało wywołane, ale zawiodło, przez cały czas uznawany był za sukces. Każdy oceniający ma swoją własną kategorię, zazwyczaj odpowiadającą stanie, który początkowy mechanizm sprawdzania zaakceptował.
  • Wolimy zapobieganie i musimy jasno określić, jaka wersja została wdrożona. Wykrywanie błędów jest łatwiejsze do stworzenia i znacznie prostsze do zademonstrowania. Ich pomieszanie powoduje, że wykresy pokazują rosnące wartości przy niezmienionej stopie błędów.
  • Budowa narzędzia do oceny była łatwiejszą częścią zadania. Prawdziwym wyzwaniem było określenie poprawnej odpowiedzi w języku pozbawionym odpowiednich narzędzi, w taki sposób, aby maszyna mogła ją sprawdzić, a dwóch ludzi mogło się co do niej zgodzić.