Dział controllingu pyta asystenta: „Jaki był przychód netto oddziału w Gdańsku w Q3 2024?" Model zwraca liczbę. Problem: nikt nie wie, z którego arkusza pochodzi, czy to wersja po korekcie, czy draft, i czy model ją odczytał poprawnie czy zaokrąglił na podstawie podobnych wierszy. Dane strukturalne w RAG to osobna klasa problemu, znacznie trudniejsza niż retrieval po dokumentach tekstowych.
Dlaczego standardowy chunking niszczy tabele
#Standardowy splitter tekstowy widzi tabelę jak ciągły ciąg znaków i dzieli go po liczbie tokenów. Jeśli tabela ma 40 wierszy, splitter przetnie ją mniej więcej w połowie. Fragment A dostanie nagłówki kolumn i wiersze 1-20. Fragment B dostanie wiersze 21-40 bez nagłówków.
Konsekwencje są poważne. Model retrieval-uje fragment B i nie wie, że kolumna trzecia to „Przychód netto [PLN]", a nie „Marża [%]". Generacja odpowiedzi na podstawie takiego fragmentu jest w zasadzie zgadywaniem. Przy pytaniach o konkretną wartość procentowy błąd wynosi tu często 100%, bo model bierze liczbę z niewłaściwej kolumny.
Ten problem dotyczy arkuszy kalkulacyjnych (XLSX, CSV), eksportów z systemów ERP i CRM, raportów finansowych w formacie tabelarycznym oraz wyników zapytań SQL zapisanych jako pliki. W każdym przypadku semantyczna spójność wiersza zależy od nagłówka, który standardowy splitter odcina.
Dwa tryby retrieval dla danych strukturalnych
#Nie ma jednego podejścia, które działa na wszystkie pytania o dane tabelaryczne. W Cashcrown stosujemy dwa tryby i wybieramy między nimi na etapie klasyfikacji zapytania.
Text-to-SQL (lub text-to-query) sprawdza się, gdy pytanie jest agregacyjne lub punktowe: „Jaki był łączny przychód w Q3?", „Pokaż 5 klientów z najwyższym zadłużeniem", „Ile zamówień miało wartość powyżej 50 tys. zł?". LLM tłumaczy pytanie naturalne na zapytanie SQL, które wykonuje się na prawdziwej bazie lub na schemacie załadowanym do pamięci. Wynik jest deterministyczny i ma jednoznaczną proweniencję: konkretna tabela, konkretne kolumny, konkretny warunek WHERE.
Ograniczenia text-to-SQL są realne: model musi znać schemat, pytania o dane rozsiane po wielu tabelach wymagają złożonych JOIN-ów, a błędnie wygenerowane zapytanie zwróci złą liczbę bez ostrzeżenia. Każde generowane zapytanie powinien sprawdzić człowiek przed pierwszym uruchomieniem na danych produkcyjnych, a system powinien logować każde wykonane query z timestampem i użytkownikiem.
Semantyczny retrieval po zdenormalizowanych wierszach sprawdza się przy pytaniach kontekstowych i wzorcowych: „Który klient ma profil podobny do firmy X?", „Znajdź produkty, które miały zbliżone cykle sprzedaży", „Jakie kategorie kosztów rosły nieproporcjonalnie w ostatnim roku?". Tu nie chodzi o jedną liczbę, lecz o relacje, anomalie i podobieństwa, które nie dają się wyrazić prostym zapytaniem SQL.
Schema-aware chunking: jak zachować nagłówki
#Niezależnie od wybranego trybu, etap przygotowania danych jest krytyczny. Dla danych tabelarycznych stosujemy podejście schema-aware, które różni się od standardowego chunkingu opisanego szerzej w artykule o chunkingu dokumentów do RAG.
Zasada jest prosta: każdy wiersz tabeli, który trafia do indeksu wektorowego, musi nieść nagłówki kolumn. Nie w osobnym fragmencie, ale inline, w tym samym chunku. Format Markdown sprawdza się dobrze, bo jest czytelny dla modelu i zachowuje relację kolumna-wartość:
Oddział: Gdańsk | Okres: Q3 2024 | Przychód netto [PLN]: 1 247 890 | Marża brutto [%]: 34,2 | Wersja: po korekcie 2024-10-15
Każdy taki wiersz-chunk dostaje metadane: nazwa pliku źródłowego, zakładka lub nazwa tabeli, numer wiersza w oryginalnym pliku, data wersji dokumentu, typ danych (finansowe, sprzedażowe, kadrowe). Metadane pozwalają na filtrowanie przed wyszukiwaniem wektorowym, co szczegółowo opisujemy przy okazji hybrydowego wyszukiwania BM25 i wektorów.
Dla dużych tabel (powyżej 500 wierszy) nie indeksujemy wszystkiego. Indeksujemy wiersze, które mają potencjał odpowiedzi na pytania biznesowe, czyli wiersze z wartościami niezerowymi, flagami wyjątków, agregacjami na poziomie działu lub okresu. Wiersze granularne trafiają do bazy SQL dostępnej przez text-to-SQL.
Podejście hybrydowe: kiedy łączyć oba tryby
#| Typ pytania | Zalecany tryb | Przykład | Walidacja ludzka |
|---|---|---|---|
| Konkretna wartość z jednej komórki | Text-to-SQL | „Przychód oddziału X w marcu" | Query do review przed produkcją |
| Agregacja warunkowa | Text-to-SQL | „Suma zamówień powyżej 10 tys. w Q2" | Query do review + wynik do weryfikacji |
| Podobieństwo, wzorzec | Semantyczny retrieval | „Klienci podobni do segmentu A" | Top-k wyniki do oceny |
| Anomalia, odchylenie | Semantyczny retrieval + SQL | „Które koszty rosły szybciej niż przychody?" | Oba kroki do weryfikacji |
| Pytanie mieszane | Hybrydowy (retrieval + SQL join) | „Podsumuj Q3 i wskaż podobne okresy historycznie" | Pełna ścieżka do audytu |
Pytania mieszane są najtrudniejsze. Asystent musi najpierw pobrać konkretne liczby przez SQL, a potem wzbogacić je kontekstem semantycznym z podobnych okresów lub produktów. Taka architektura wymaga orkiestratora, który skleja oba wyniki zanim trafi do generacji. Human-gate na takim złożonym wyniku jest obowiązkowy, jeśli odpowiedź zasila decyzję zarządczą.
Proweniencja liczby: cytuj, nie przybliżaj
#To jest twardy wymóg architektury, który w Cashcrown egzekwujemy na poziomie promptu systemowego i weryfikujemy w golden secie ewaluacyjnym.
Każda liczba zwrócona przez asystenta musi mieć atrybucję do konkretnego źródła. Format proweniencji zależy od trybu:
Dla text-to-SQL: „1 247 890 PLN (źródło: raport_wyniki_Q3_2024.xlsx, zakładka Oddziały, wiersz 47, wersja po korekcie 2024-10-15, zapytanie: SELECT przychod_netto FROM oddzialy WHERE nazwa='Gdańsk' AND okres='Q3_2024')".
Dla semantycznego retrieval: „34,2% (źródło: fragment wiersza 47 z raport_wyniki_Q3_2024.xlsx, odległość cosinusowa 0,97)".
Model nigdy nie powinien zaokrąglać ani interpolować liczby, której nie znalazł w indeksie. Jeśli dane nie są dostępne, odpowiedź brzmi: „Nie mam tej liczby w bazie. Sprawdź plik X lub poproś analityka o eksport." To lepsze niż liczba, która wygląda wiarygodnie, ale pochodzi z najbliższego podobnego wiersza.
Więcej o tym, jak budować systemy AI odpowiadające za dane firmowe, w artykule o firmowym GPT na bazie wiedzy.
Spróbuj: RAG nad danymi finansowymi w Twojej firmie
#Filtrowanie i bezpieczeństwo danych strukturalnych
#Dane finansowe, kadrowe i handlowe rzadko powinny być dostępne dla wszystkich użytkowników asystenta. W architekturze RAG nad danymi strukturalnymi filtrowanie dostępu realizuje się przez metadane chunka: każdy wiersz nosi etykietę access_level (np. „zarząd", „controlling", „sprzedaż") i tenant_id w systemach wielofirmowych.
Zapytanie wektorowe filtruje po tych metadanych zanim przejdzie do rankingu semantycznego. Dzięki temu kontroler nie widzi danych kadrowych, a sprzedawca nie widzi marż netto. Szczegóły implementacji izolacji dostępu opisuje artykuł o bazie wektorowej i kryteriach decyzji.
Drugi wymiar bezpieczeństwa to świeżość danych. Arkusze finansowe są wersjonowane. Indeks wektorowy zbudowany na drafcie z 12 października będzie zwracał inne liczby niż ten zbudowany na wersji po korekcie z 15 października. System musi przechowywać wersję dokumentu jako metadaną każdego chunka i eksponować ją użytkownikowi przy każdej odpowiedzi. Decyzja o tym, którą wersję uznać za obowiązującą, należy do człowieka, nie do asystenta.
Dla raportów regulacyjnych (np. sprawozdania finansowe, raporty do KNF) zalecamy podejście tylko do odczytu z loggingiem każdego zapytania i odpowiedzi. Wymagania AI Act dotyczące dokumentowania systemów wysokiego ryzyka mogą obejmować tego rodzaju asystentów, gdy wpływają na decyzje finansowe. Więcej o analizie danych i BI w artykule AI do analizy danych i BI.
FAQ
#Czy text-to-SQL jest bezpieczny na danych produkcyjnych?
#Text-to-SQL powinien działać na kopii tylko do odczytu (read replica) lub na schematach z uprawnieniami SELECT wyłącznie dla konta asystenta. Każde generowane zapytanie warto logować z timestampem i identyfikatorem sesji. Przed wdrożeniem produkcyjnym zalecamy przegląd co najmniej 50 przykładowych zapytań przez analityka lub developera, który zna schemat. Błędnie wygenerowane JOIN lub pomylony warunek WHERE może zwrócić liczbę wyglądającą wiarygodnie, ale obliczoną na złym podzbiorze.
Co zrobić z tabelami, których kolumny mają niejednoznaczne nazwy?
#Etap przygotowania danych powinien obejmować mapowanie technicznych nazw kolumn na opisy biznesowe. Kolumna rev_net_adj_eur w schemacie staje się „Przychód netto po korekcie [EUR]" w metadanych chunka i schemacie text-to-SQL. To praca analityczna, którą musi wykonać człowiek znający domenę; LLM może zaproponować mapowanie, ale ostateczna decyzja o nazwie biznesowej należy do działu, który z tych danych korzysta.
Jak obsłużyć tabele z połączonymi komórkami (merge cells) w XLSX?
#Scalzone komórki to jeden z najczęstszych problemów przy parsowaniu XLSX. Biblioteki takie jak openpyxl zwracają wartość tylko dla lewej górnej komórki scalonego obszaru, reszta jest pusta. Na etapie parsowania należy propagować wartość scalonej komórki do wszystkich wierszy, które obejmuje, zanim dane trafią do chunkingu. Bez tego propagacji model widzi wiersze bez nagłówka grupy, co niszczy kontekst i prowadzi do błędnych atrybucji.
Jak ewaluować jakość RAG nad danymi tabelarycznymi?
#Golden set dla danych tabelarycznych powinien zawierać pytania z dokładnymi, weryfikowalnymi odpowiedziami: konkretna liczba z konkretnej komórki. Metryka oceny to nie tylko trafność semantyczna, ale dokładność liczbowa: czy zwrócona wartość zgadza się z wartością w źródle co do grosza (dla kwot finansowych tolerancja powinna być zerowa). Dla RAG nad danymi mieszanymi warto budować osobne golden sety dla trybu SQL i trybu semantycznego, bo różnią się metryką sukcesu.
Czy wyszukiwanie hybrydowe poprawia precyzję dla danych liczbowych?
#W ograniczonym stopniu. BM25 dobrze radzi sobie z dokładnym dopasowaniem tekstowym, np. nazwy oddziałów, kody produktów, numery umów. Dla samych liczb sygnał BM25 jest słaby, bo te same cyfry pojawiają się w wielu wierszach. Hybrydowe wyszukiwanie pomaga przy pytaniach mieszanych, gdzie użytkownik podaje nazwę i pyta o liczbę. Dla pytań czysto agregacyjnych text-to-SQL jest bardziej niezawodny niż dowolna forma retrieval wektorowego.