Dzielnik tekstu dla embeddingów i RAG — podział na tokeny

Podziel długi tekst na fragmenty odmierzone w tokenach z zakładką, na potrzeby embeddingów i RAG. Tnie na granicach zdań i akapitów, nigdy w połowie słowa, i pokazuje każde cięcie.

Dzielenie tekstu na fragmenty pod embeddingi: to, gdzie pada cięcie, decyduje, co wyszukasz

Systemy wyszukiwania zawodzą po cichu. Wektoryzujesz korpus, podpinasz wyszukiwanie, zadajesz pytanie i dostajesz coś, co tematycznie leży obok, ale na pytanie nie odpowiada. Odruchowo obwiniasz model embeddingów albo miarę podobieństwa. O wiele częściej problem wydarzył się wcześniej: tekst został pocięty na kawałki, z których nie każdy zawiera kompletną myśl, a żadne sprytne rankowanie nie odtworzy znaczenia zniszczonego w momencie podziału.

To narzędzie dzieli tekst na fragmenty ograniczone liczbą tokenów, z nakładaniem, i tnie na najbardziej sensownej dostępnej granicy zamiast pod stałym offsetem. Pokazuje ci każdą granicę, bo złe cięcie widać wyłącznie wtedy, gdy przeczytasz, gdzie kończy się jeden fragment, a zaczyna następny. Wszystko działa w tej karcie przeglądarki.

Rekurencyjny podział po granicach

Naiwne podejście to ciąć co N tokenów. Jest szybkie i jest błędne, bo granica tokenu nie ma żadnego związku z granicą znaczeniową — potniesz w środku zdania, czasem w środku słowa, a powstały wektor opisuje fragment, którego nikt nie napisał.

Metoda tutaj schodzi po drabince separatorów i używa pierwszego, który daje kawałki dość małe, by się zmieściły. Po kolei: przed nagłówkiem Markdowna, potem puste wiersze, potem dowolne złamanie wiersza, potem granice zdań, potem granice fraz na przecinkach i średnikach, wreszcie białe znaki między słowami. Dopiero gdy nie ma żadnego z nich — zminifikowany bundle, jedno gigantyczne słowo — schodzi do cięcia po indeksie tokenu.

Praktyczny efekt jest taki, że struktura zostaje zachowana wszędzie tam, gdzie tekst jakąkolwiek ma. Dokument z nagłówkami dzieli się na sekcjach. Proza dzieli się na akapitach albo na zdaniach, gdy akapit jest za długi. Ściana nieprzerwanego tekstu dzieli się na słowach. Każdy fragment kończy się tam, gdzie zakończyłby go człowiek — chyba że tekst nie dał takiej okazji.

Do czego naprawdę służy nakładanie

Nakładanie powtarza ogon jednego fragmentu na początku następnego. Istnieje dlatego, że fakt i to, do czego się odnosi, często leżą po przeciwnych stronach granicy. „Migracja poszła w nocy. Wywaliła się na trzeciej tabeli.” Podziel między tymi zdaniami, a drugi fragment zawiera awarię bez podmiotu, a pierwszy migrację bez wyniku. Żaden nie wyszuka się sensownie. Z nakładaniem drugi fragment niesie ze sobą poprzednik.

Kosztem jest duplikacja: nakładające się tokeny są przechowywane, wektoryzowane i przeszukiwane więcej niż raz. Dziesięć do piętnastu procent rozmiaru fragmentu to zwyczajowy kompromis i tam właśnie siedzą gotowe ustawienia. Panel statystyk podaje dokładnie, ile tokenów dołożyło nakładanie, więc kompromis jest liczbą, a nie przeczuciem.

Jedno zabezpieczenie warto znać: nakładanie równe rozmiarowi fragmentu albo od niego większe oznaczałoby, że każdy fragment zaczyna się wszystkim, czym skończył poprzedni, i proces nigdy nie ruszyłby do przodu. To jest przycinane, zamiast zawiesić się na dobre.

Dobieranie rozmiaru

Rozmiar fragmentu to kompromis między precyzją a kontekstem i nie ma tu uniwersalnie poprawnej odpowiedzi:

  • Małe fragmenty, około 256 tokenów, dają ostre trafienia. Wektor rozpięty na dwóch czy trzech zdaniach jest zdominowany przez to, co te zdania mówią. Dobre do odpowiadania na pytania po gęstym materiale referencyjnym. Ryzyko to fragmenty, które trafiają dobrze, ale nie mają otaczającego kontekstu potrzebnego, żeby faktycznie odpowiedzieć.
  • Średnie fragmenty, około 512, to powszechny domyślny wybór i rozmiar, przy którym większość modeli embeddingowych czuje się najlepiej. Mniej więcej solidny akapit.
  • Duże fragmenty, 1024 i więcej, niosą więcej kontekstu na trafienie, ale rozwadniają wektor. Fragment obejmujący cztery tematy siedzi w średniej z całej czwórki i nie trafia mocno w żaden.
  • Bardzo duże fragmenty, po kilka tysięcy tokenów, przestają być jednostką wyszukiwania i stają się jednostką paginacji — do przepuszczania długiego dokumentu przez model kolejnymi przebiegami, a nie do szukania w nim.

Cztery strategie podziału

Podział inteligentny to opisana wyżej rekurencyjna drabinka i jest właściwy dla większości prozy. Akapity pakuje całe akapity i nigdy żadnego nie dzieli, co pasuje do tekstów, w których akapit i tak jest jednostką myśli, a wolisz nierówne rozmiary fragmentów niż jeden rozbity. Nagłówki Markdowna dają jeden fragment na sekcję, co jest naturalnym wyborem dla dokumentacji, gdzie nagłówek nazywa dokładnie to, co pod nim stoi. Wiersze pakuje całe wiersze, pod logi, wyciągi CSV i listy, w których każdy wiersz jest niezależny.

W każdym trybie każda jednostka, która wciąż przekracza limit, jest przed pakowaniem redukowana rekurencyjnym dzielnikiem, więc wybór strategii nigdy nie wyprodukuje zbyt dużego fragmentu.

Liczenie i co ta liczba znaczy

Tokeny liczone są tym samym tokenizatorem, którym ta strona mierzy repozytoria, z rodziny używanej przez modele OpenAI. Liczby dla Claude'a i Gemini trochę się różnią — inne słowniki inaczej dzielą ten sam tekst — ale trzymają się na tyle blisko, żeby zmierzyć fragment względem limitu. Jeśli budżet masz napięty, zostaw margines, zamiast traktować liczbę jak dokładną.

Zwróć też uwagę, że deklarowany limit modelu embeddingowego to twardy punkt ucięcia, a nie cel. Przekrocz go, a ogon zostanie po cichu odrzucony — tryb awarii, który nie zwraca żadnego błędu i pogarsza wyszukiwanie w sposób szczerze trudny do zdiagnozowania.

Wynik, który da się czymś przetworzyć

Trzy pobrania. Zwykły tekst z opcjonalnymi nagłówkami --- Chunk n/N ---, do czytania i sprawdzania. JSON jako tablica obiektów z indeksem, liczbą tokenów i tekstem, pod skrypt, który wciąga całość. JSONL z jednym obiektem na wiersz, czyli to, czego oczekuje strumieniowa ingestia i większość wsadowych endpointów embeddingowych, i to, czego chcesz, gdy korpus jest za duży, by trzymać go w pamięci jako jedną sparsowaną tablicę.

To naturalnie łączy się z resztą strony: najpierw zamień repozytorium, PDF, stronę WWW albo transkrypcję na tekst, a potem potnij wynik tutaj. Na żadnym z tych kroków nic nie jest wysyłane.