Strona główna / Blog / Inżynieria
InżynieriaUsunęliśmy parser wydatków w języku naturalnym
Nasz parser regułowy przetrwał tydzień: cztery zwykłe zdania przypisały pieniądze niewłaściwej osobie, a jedna reguła odmowy zastąpiła cały stos strażników.
Usunęliśmy parser, którego pisanie właśnie skończyliśmy. Pierwsze podejście Dimesum do rozpoznawania wydatków w języku naturalnym było silnikiem reguł, a wycofały je cztery zwykłe zdania: dinner 12-08 400 zostało odczytane jako 12 zł, ravioli 600 dodało do podziału osobę o imieniu Ravindra, kirana 500 obciążyło osobę o imieniu Kiran, a refund -483.50 stało się obciążeniem 483,50 zł.
Rozumienie zdania to zadanie modelu i niczego innego. Silnik reguł zastąpił jeden szczebel o nazwie amount_only: pojedyncza liczba, zwracana tylko wtedy, gdy tekst zawiera dokładnie jednego jednoznacznego kandydata na kwotę, i nigdy nie orzeka o osobach. Dwóch kandydatów oznacza brak kwoty. Jedna reguła zastąpiła rosnący stos strażników.
Cztery zdania zakończyły parser regułowy
Usunięty parser obejmował cały zestaw encji opisany w naszym dokumencie projektowym AI: dopasowanie członków i pseudonimów, leksykon wykluczeń, wnioskowanie o płatniku, leksykon kategorii oraz dostrojone poziomy pewności dla poszczególnych pól. Każda z czterech poniższych porażek miała oczywistą poprawkę. Każda poprawka była nowym strażnikiem z własnym martwym polem.
| Co wpisał użytkownik | Co zrobił parser | Dlaczego tak się stało |
|---|---|---|
dinner 12-08 400 | Odczytał kwotę jako 12 zł | Człowiek widzi datę i sumę. Regex widzi trzy liczby i bierze pierwszą. |
ravioli 600 | Dodał do podziału osobę o imieniu Ravindra | Rozmyte dopasowanie pseudonimu przypisało potrawę do osoby. |
kirana 500 | Obciążył osobę o imieniu Kiran | To samo dopasowanie, tym razem z nazwą sklepu. |
refund -483.50 | Zaksięgował obciążenie 483,50 zł | Cyfry przetrwały, a znak nie, więc pieniądze skierowały się w przeciwną stronę. |
Dwie z tych czterech to jeden błąd w różnych przebraniach. Rozmyte dopasowanie nie odróżni potrawy od osoby ani sklepu od osoby, ponieważ na poziomie znaków ravioli i Ravindra naprawdę wyglądają podobnie. Wymuś dłuższe dopasowanie prefiksu, a zepsujesz ravi, czyli przypadek, dla którego to dopasowanie w ogóle istnieje.
Każdy dodany strażnik ujawnia dwa nowe sformułowania
Parser regułowy zawodzi w jeden charakterystyczny sposób: odpowiada pewnie i błędnie. Puste pole kosztuje użytkownika jedno dotknięcie. Błędny podział kosztuje zaufanie do księgi, a w aplikacji do dzielenia wydatków to księga jest produktem. Cztery powyższe wiersze to nie drobne pomyłki, to błędy pieniężne.
Prawdziwym argumentem jest bieżnia, a nie pojedyncza usterka. Dodaj strażnika daty, a pojawi się przypadek numeru zamówienia. Dodaj strażnika numeru zamówienia, a pojawią się gołe liczby, potem ilości, potem numery stolików. Lista strażników rośnie i nigdy się nie domyka, ponieważ język naturalny nie ma skończonego zbioru sformułowań do wyliczenia.
Wprowadzanie w języku naturalnym to funkcja AI, a nic we wzorcach repozytorium Dimesum jej nie odpowiada. Decyzja zapadła 20 sierpnia 2026, spisana wraz z czterema porażkami, aby nikt przypadkiem nie odbudował strażników.
To, co trafiło na produkcję, to jedna liczba i odmowa
amount_only to zdegradowany szczebel z naszego dokumentu projektowego AI wdrożony dosłownie. Ten szczebel brzmi „zwykły formularz z wypełnianiem kwoty regexem po stronie klienta”, więc szczebel zwraca kwotę i nic więcej: bez opisu, bez kategorii, bez płatników, bez uczestników, bez wykluczeń. Nic nie kosztuje i nikogo nie wywołuje, dlatego działają na nim testy i CI.
Niejednoznaczność to odmowa, a nie rozstrzyganie remisu
Cała reguła mieści się w jednej funkcji, extract_amount_minor. Liczba wraca tylko wtedy, gdy tekst zawiera dokładnie jednego kandydata, więc order 90210 dinner 400 i flat 402 rent 15000 zwracają puste pole zamiast wybierać zwycięzcę. Liczba oznaczona walutą liczy się jako jednoznaczna nawet obok gołych liczb, dlatego split 3 ways 1200 zł nadal odczytuje 1200 zł.
Liczba zanegowana to w ogóle brak kwoty, a nie jej wartość bezwzględna. -500, minus 200 oraz księgowe (500) wracają puste, ponieważ to pole jest obciążeniem, a zachowanie cyfr przy odrzuceniu znaku kieruje pieniądze w stronę przeciwną do tekstu. Nawiasy liczą się tylko wtedy, gdy zamykają się na samej liczbie, więc (500 each) pozostaje wtrąceniem.
To waluta decyduje o arytmetyce
Jednostki podrzędne to jedyna postać, jaką przyjmują pieniądze w Dimesum, więc szczebel przelicza je za pomocą tabeli wykładników ISO 4217, a nie zaszytego mnożenia przez 100. Japoński jen w ogóle nie ma jednostki podrzędnej, a ×100 zawyża paragon ¥1,200 stukrotnie. Liczba drobniejsza niż najmniejsza jednostka waluty jest odrzucana, a nie zaokrąglana, ponieważ zaokrąglenie wpisanej kwoty to wymyślanie jej.
Indyjskie słowa liczbowe to część zapisu liczby, a nie część rozumienia zdania. 1.2k, 2 lakh i 500/- rozwiązują się, tak jak 1,200. Wszystko powyżej maksymalnej kwoty księgi wraca puste, więc błędnie wpisana liczba wyczyszcza jedno pole zamiast przepełniać liczbę całkowitą w dalszej części.
| Pole | Parser regułowy (usunięty) | amount_only (działa) | Szczebel modelu (podłączony, bez klucza) |
|---|---|---|---|
| Kwota | Zgadywana z kilku liczb | Jedna jednoznaczna liczba, inaczej puste | Odczytana w kontekście |
| Opis, kategoria | Dopasowanie leksykonu | Zawsze null | Wydobyte ze zdania |
| Uczestnicy, wykluczenia | Rozmyte dopasowanie imion | Zawsze puste | Rozwiązane do realnych identyfikatorów członków |
| Płatnik | Wywnioskowany ze sformułowania | Zawsze puste | Nazwany, z kwotą dopuszczającą null |
| Pewność ogólna | Dostrojona dla pola | Ustalona na 0.3 | Dla parsowania |
| Raportowany prompt | Żaden nie istniał | Null, nie odczytano promptu | Identyfikator i wersja promptu |
Działający szczebel nigdy nie przekracza progu użyteczności 0.6
Nasz kontrakt parsowania porzuca parsowanie poniżej 0.6 ogólnej pewności i przenosi użytkownika do zwykłego formularza. amount_only raportuje 0.3 przy każdej odpowiedzi, a stała jest strukturalna, a nie dostrojona. Sama kwota to nie parsowanie, więc szczebel siedzi na połowie progu, cokolwiek znalazł. Jedna stała dla każdej odpowiedzi utrzymuje go tam: wynik liczony dla przypadku to wynik, który ktoś w końcu podciągnie w górę.
Szczebel nie raportuje też żadnego promptu. Zarówno prompt_id, jak i prompt_version wracają puste, ponieważ szczebel nie odczytał żadnego promptu. Nazwanie któregoś przypisałoby każdy wynik ewaluacji do wersji promptu, której szczebel nigdy nie widział, a zestaw ewaluacyjny to jedyny instrument uprawniony do awansowania szczebla do podpowiedzi na jedno dotknięcie, przy 95% precyzji łącznie dla kwoty i uczestników.
Zadeklarowano dwa kolejne szczeble i żaden nie działa. Szczebel cheap-fast ma adapter Groq dla openai/gpt-oss-120b i nie ma klucza; szczebel pośredni nie ma adaptera. Wybór któregokolwiek zawodzi przy starcie, a nie przy pierwszym żądaniu użytkownika, ponieważ LLM nalicza opłatę za wywołanie, a płatna zależność musi zawodzić bezpiecznie.
Nic nie księguje się automatycznie, więc puste pole kosztuje jedno dotknięcie
Puste pole kosztuje tak mało tylko dlatego, że żadne przechwycenie w Dimesum nie może zapisać pieniędzy. Przechwycenie tworzy sugestię, człowiek ją potwierdza, a to potwierdzenie tworzy wydatek. Decyzja D5 z briefu ustala tę regułę, a .go-arch-lint.yml ją egzekwuje: kontekst pozyskiwania danych ma zabronioną jakąkolwiek zależność od wydatku czy księgi, więc przechwycenie nie zaksięguje wpisu nawet przez pomyłkę. CI odrzuca import, co sprawdziliśmy, dodając jeden.
Przechwycenie zachowuje surowy tekst niezależnie od tego, co zrobi parser, i nazywa, które pola pozostają nierozwiązane. Klient podświetla te puste pola, zamiast pokazywać wymyślony szkic, i to jest różnica między parserem, który nic nie mówi, a takim, który zgaduje. Potwierdzenie wyprowadza identyfikator wydatku z identyfikatora sugestii, więc podwójne dotknięcie odtwarza się zamiast obciążać dwa razy.
Reguła warta skradzenia
Licz swoich strażników, nie swoje błędy. Lista strażników, która rośnie co tydzień, mówi ci, że zadaniem jest rozumienie, a rozumienie należy do modelu. Naszym kolejnym krokiem jest uruchomienie złotego zestawu w contracts/parse_expense/eval/ na prawdziwym szczeblu modelu, ponieważ nic tutaj nie stanie się podpowiedzią na jedno dotknięcie bez tego werdyktu.
Często zadawane pytania
Dlaczego Dimesum usunął parser wydatków oparty na regułach?
Dimesum usunął go, ponieważ cztery zwykłe zdania dawały błędy pieniężne, a każdy dodany strażnik ujawniał dwa nowe sformułowania. dinner 12-08 400 było odczytane jako 12 zł, ravioli 600 dodało osobę o imieniu Ravindra, kirana 500 obciążyło osobę o imieniu Kiran, a refund -483.50 stało się obciążeniem 483,50 zł. Rozumienie zdania to zadanie modelu.
Co właściwie zwraca poziom amount_only?
Poziom amount_only zwraca jedną liczbę i nic więcej. Podaje kwotę tylko wtedy, gdy tekst zawiera dokładnie jednego jednoznacznego kandydata na kwotę, i nigdy nie nazywa uczestnika, płatnika, opisu ani kategorii. Dwóch kandydatów oznacza brak kwoty. Wszystko inne w kontrakcie parsowania czeka na szczebel modelu.
Dlaczego amount_only pokazuje pewność 0.3 zamiast realnego wyniku?
Wartość 0.3 jest strukturalna, a nie dostrojona. Nasz kontrakt parsowania porzuca parsowanie poniżej 0.6 i przenosi użytkownika do zwykłego formularza, a sama kwota to nie parsowanie, więc szczebel siedzi na połowie tego progu, cokolwiek znalazł. Jedna stała dla każdej odpowiedzi powstrzymuje późniejsze podciąganie wyniku liczonego dla przypadku.
Czy wpis w języku naturalnym może zapisać się w księdze bez człowieka?
Nie. Przechwycenie tworzy sugestię, a człowiek ją potwierdza, zgodnie z decyzją D5 z briefu. Regułę egzekwuje .go-arch-lint.yml, który odmawia kontekstowi pozyskiwania danych jakiejkolwiek zależności od wydatku czy księgi, więc CI odrzuca import, jeśli przechwycenie kiedykolwiek sięgnie do dziennika. To potwierdzenie tworzy wydatek.
Co się dzieje z parsowaniem wydatków, gdy żaden szczebel modelu nie jest dostępny?
Dimesum degraduje się do amount_only i pokazuje puste pola. Szczebel cheap-fast jest podłączony do openai/gpt-oss-120b od Groq i dostarczany bez klucza, a szczebel pośredni nie ma adaptera, więc wybór któregokolwiek zawodzi przy starcie, a nie przy pierwszym żądaniu użytkownika. Przechwycenie w obu przypadkach zachowuje surowy tekst.
Popularne wpisy
- Dopisywalna księga dla dokładnych sald wydatków8 min czytania
- Edycja wydatku musi na nowo określić podział8 min czytania
- Sześć błędów walutowych w podziale wydatków8 min czytania
- Rozliczenie: wspólne wydatki w kilku przelewach4 min czytania
- Jak sprawiedliwie podzielić rachunek w restauracji8 min czytania