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.
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.
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
PHANTOMdla 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 wynosiclaimed_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.
Ż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ę.
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ć.