Automatyczne porównanie ofert z hurtowni cz. 2
Model językowy mylił się w odczycie ofert od pewnych dostawców za każdym razem.
W pierwszej części opisaliśmy, jak dział logistyki porównuje oferty z kilku hurtowni i co z tego porównania zostaje w systemie. Model dostaje tam cały dokument wraz z opisem, czego w nim szukamy. Ta część jest o tym, jak ten opis powstawał i co się przy nim psuło.
Instrukcja wspólna i instrukcja dostawcy
Podczas prac zauważyliśmy, że błędy modelu powtarzają się u niektórych dostawców i biorą się z układu oferty. W tych ofertach kłopot sprawiało zawsze to samo:
- niektórzy dostawcy umieszczają kilka informacji w jednej kolumnie (np. nazwa produktu i indeks dostawcy w nowej linii),
- niektórzy łączą ilość i jednostkę w jedną kolumnę, inni je rozdzielają,
- niektórzy powtarzają nagłówki i stopkę tabeli na każdej stronie oferty, inni nie,
- formaty dat i separatory dziesiętne różnią się między ofertami.
Najpierw sięgnęliśmy po mocniejsze modele z rodziny Opus zamiast Sonnet i zmieniliśmy konfigurację tak, aby model poświęcił więcej czasu, zanim zwróci odpowiedź (parametr effort).
Niestety nie poprawiało to znacznie jakości odpowiedzi, za to wydłużało jej czas.
Spróbowaliśmy więc innego podejścia. Podzieliśmy nasz prompt do modelu językowego na dwie części:
- Część ogólną opisującą strukturę danych na wyjściu. Ta część jest wspólna dla wszystkich firm.
- Część specyficzną dla oferty danego kontrahenta. Tu opisujemy format oferty i zawieramy wszystkie informacje pomocne w jej odczycie, jak separator dziesiętny czy format daty.
Część wspólna brzmi tak:
Analizujesz oferty z hurtowni budowlanej w formacie PDF. Odczytaj dokument i zwróć strukturę JSON zgodną ze schematem.
Informacje do odnalezienia w nagłówku oferty:
- numer oferty (offerNumber),
- data wystawienia oferty (issueDate) - w formacie ISO 8601 (np. 2023-10-01).
Dla każdej pozycji w tabeli produktów wyodrębnij:
- nazwę produktu (name),
- ilość (quantity),
- jednostkę (unit),
- cenę jednostkową netto (price) - pracuj wyłącznie na kwotach netto, brutto ignoruj,
- wartość netto (value),
- producenta (manufacturer),
- kod producenta (manufacturerCode),
- kod dostawcy (supplierCode) - często nazywany również "indeks".
Jeśli dane nie są dostępne, zwróć null dla danego pola.
Instrukcja dostawcy jest krótsza i opisuje układ jego oferty. Ta poniżej należy do hurtowni, która w jednej kolumnie podaje dwa kody:
Dokument zawiera kolumny:
- Lp.
- Zdjęcie
- Indeks/ Kod producenta
- Nazwa
- Ilość
- Jm (jednostka miary)
- Cena jednostkowa netto po rabacie
- Wartość netto
Kolumna "Indeks/Kod producenta" zawiera kod dostawcy w pierwszej linii, a kod producenta w drugiej linii.
Separatorem dziesiętnym jest przecinek.
To podejście zadziałało dużo lepiej. Model nie musi już odkrywać układu oferty na nowo, bo dostaje go opisany wprost.
Początkowo wszystkie instrukcje trzymaliśmy razem z kodem aplikacji, szybko jednak okazało się, że to rozwiązanie jest niepraktyczne. Każda poprawka treści instrukcji wymagała wdrożenia nowej wersji systemu. Dlatego instrukcję wspólną zostawiliśmy w kodzie, a instrukcje dostawców przenieśliśmy do bazy danych. Poprawia się je wprost w aplikacji do zamówień towaru, a zmiana obowiązuje od następnego odczytu.
Przy dopasowaniu pozycji między ofertami też próbowaliśmy kilku opcji, ale najlepiej sprawdził się nam jeden prompt dla wszystkich dokumentów:
Dopasowujesz pozycje dokumentów magazynowych z listy źródłowej do listy docelowej.
KRYTERIA DOPASOWANIA (w kolejności ważności):
1. Nazwa - szukaj nazw produktów identycznych lub bliskich znaczeniowo
2. Jednostka - musi być zgodna (np. kg, kilogram, kilogramy są równoważne)
3. Ilość - pozycja docelowa powinna mieć ilość wystarczającą do pokrycia źródłowej
ZASADY:
- Każdy element źródłowy MUSI wystąpić w wyniku dokładnie raz
- Każdy element docelowy może zostać dopasowany tylko raz (jeden do jednego)
- Jeśli nie ma odpowiedniego dopasowania, wstaw null w polu Target
- Przy porównywaniu nazw pomijaj wielkość liter i drobne różnice w pisowni
- Przy porównywaniu jednostek uwzględniaj popularne skróty i synonimy
FORMAT WEJŚCIA: CSV z kolumnami: Index,Name,Unit,Quantity
Index to liczba całkowita numerowana od 1, identyfikująca pozycję w obrębie jej listy.
Każde dopasowanie łączy jeden Index źródłowy z co najwyżej jednym Index docelowym. Używaj
wartości Index dokładnie takich, jakie są w danych wejściowych - nigdy ich nie wymyślaj
ani nie zmieniaj.
Zaskoczyło nas, że zamiana identyfikatorów GUID na zwykłe liczby porządkowe znacznie skróciła czas odpowiedzi przy dłuższych ofertach. Liczba porządkowa zajmuje kilka znaków, GUID kilkadziesiąt, a model musi przepisać każdy z nich do odpowiedzi co do znaku i przy długich ciągach losowych potrafił je przekręcić.
Testy regresyjne na rzeczywistych dokumentach
Poprawialiśmy instrukcję pod jedną ofertę i psuliśmy przy tym odczyt kolejnych ofert tego samego dostawcy. Niestety, dowiadywaliśmy się o tym zwykle od klienta.
Dlatego zaczęliśmy zostawiać u siebie każdy problematyczny PDF jako osobny test. Zapisujemy dokładnie ten plik, który sprawił kłopot, razem z tym, co powinno się z niego odczytać. Na jednego dostawcę wychodzi zwykle jeden albo dwa takie pliki i to w zupełności wystarcza.
Oferty potrafią mieć kilkadziesiąt pozycji, więc nie porównujemy każdego wiersza z osobna. Sprawdzamy kilka pierwszych i kilka ostatnich pozycji tabeli oraz sumę wartości netto całego dokumentu. Ta suma sprawdza się u nas najlepiej, bo wystarczy, że model zgubi jedną pozycję albo przekłama kwotę, a wynik już się nie zgadza.
Zestaw uruchamiamy po każdej zmianie instrukcji, a także po zmianie modelu. Nowe modele wychodzą co kilka miesięcy i za każdym razem musimy sobie odpowiedzieć na to samo pytanie: czy nowszy model odczyta nasze oferty co najmniej tak samo dobrze. Mając ten zestaw, możemy sprawdzić kilka konfiguracji modelu i promptu, zanim zdecydujemy, co trafia na produkcję.
Nowego dostawcę obsługujemy dziś zawsze tak samo. Kiedy model nie radzi sobie z jego ofertą albo robi to wolno, dodajemy problematyczny PDF do testów i piszemy instrukcję specyficzną dla jego formatu.
Wyniki naszych testów dla różnych modeli
Na potrzeby tego artykułu uruchomiliśmy zestaw na czterech modelach Claude’a w sierpniu 2026 roku, po jednym uruchomieniu na każdy.
| model | zaliczone testy | mediana czasu | najdłuższa oferta |
|---|---|---|---|
| Opus 5 | 6/6 | 13 s | 169 s |
| Opus 4.8 | 6/6 | 14 s | 273 s |
| Sonnet 5 | 6/6 | 16 s | 213 s |
| Haiku 4.5 | 4/6 | 10 s | 76 s |
Mediana i najdłuższy czas różnią się znacznie, ponieważ cztery oferty z zestawu mają po kilkanaście pozycji, a dwie po sto kilkadziesiąt. Na tych dwóch model pracuje kilka minut i to one decydują o rachunku na koniec miesiąca. Odczyt średniej wielkości oferty kosztuje kilkanaście groszy, a największej z zestawu od kilkudziesięciu groszy do kilku złotych, zależnie od modelu.
Liczby pochodzą z jednego uruchomienia testów. Zauważyliśmy jednak, że czas odczytu oferty potrafi się znacznie różnić pomiędzy uruchomieniami. Tę samą dużą ofertę Sonnet 5 odczytał raz w 143 sekundy, a drugi raz jedynie 93.
W środowisku produkcyjnym zdecydowaliśmy się na wybór modelu Sonnet 5. Opus 5 czyta te same oferty równie dokładnie i nieco szybciej, ale koszt odpowiedzi jest około dwa i pół raza wyższy.
Haiku 4.5 znajduje się w tym zestawieniu na innych zasadach niż pozostałe trzy modele.
Nie przyjmuje ani adaptacyjnego myślenia, ani parametru effort, więc odczytuje ofertę bez namysłu.
Część jego przewagi w czasie bierze się właśnie stąd.
W ofercie z wierszem usługi transportowej indeks dostawcy nie jest liczbą, tylko tekstem „USŁUGA 23%”. Model zwrócił w tym polu null, choć wartość znajduje się w dokumencie. W innej ofercie odczytał ilość dziewięć tam, gdzie w tabeli była jedynka, a to już poważniejszy błąd. Model zinterpetował w obu wypadkach, co w polu powinno być, zamiast przepisać to, co tam jest.
Mimo tych dwóch pomyłek jesteśmy pod wrażeniem tego, co Haiku daje w stosunku do ceny. Możliwe, że inaczej napisana instrukcja albo kolejna wersja Haiku odczyta te oferty równie dokładnie, a taniej. Sprawdzenie tego zajmie tyle, co jedno uruchomienie zestawu.
Model w systemie produkcyjnym
Przy pracy z modelem łatwo o złudzenie, że skoro instrukcja zadziałała na dokumencie, który mamy przed sobą, to zadziała też na następnym. Nasze poprawki wyglądały dobrze dokładnie do chwili, w której ten sam dostawca przysłał kolejną ofertę. Dziś chroni nas przed tym tylko zestaw testów na znanym z góry zbiorze dokumentów.
W systemie działającym w środowisku produkcyjnym jeden udany odczyt niewiele znaczy. Liczy się to, że ten sam dokument odczytamy tak samo za miesiąc, po kolejnej poprawce instrukcji i po zmianie modelu.