Chunking dokumentów w RAG to proces dzielenia długich tekstów na mniejsze fragmenty (chunki) przed osadzeniem ich w wektorowej bazie danych, a od jego jakości zależy, czy retriever w ogóle zwróci właściwy kawałek do LLM. W praktyce 2026 używa się trzech głównych podejść: recursive character splitting (szybkie, tanie, domyślne), semantic chunking (dzielenie po granicach znaczenia z pomocą embeddingów) oraz agentic chunking (LLM sam decyduje, gdzie ciąć). W tym poradniku pokazuję, kiedy każda z tych metod wygrywa, jak je zaimplementować w Pythonie i jak zmierzyć różnicę na własnym korpusie.
Recursive chunking z RecursiveCharacterTextSplitter z LangChain to nadal najlepszy domyślny wybór. Pokrywa 80% zastosowań przy zerowym koszcie obliczeniowym.
Semantic chunking (Greg Kamradt, LlamaIndex SemanticSplitterNodeParser) daje 5–15% wzrost recall@10 na dokumentach narracyjnych, ale kosztuje 1 wywołanie modelu embeddingów na zdanie.
Agentic chunking (pytanie LLM „gdzie kończy się jedna myśl?") jest najdroższy (nawet 100× więcej tokenów) i opłaca się tylko dla dokumentów prawnych, medycznych lub finansowych.
Late chunking od Jina AI (2024) osadza całe dokumenty najpierw, a chunkuje później, więc zachowuje kontekst międzychunkowy bez zmiany rozmiaru chunka.
Chunk overlap 10–20% rozmiaru chunka to sweet spot; overlap 0% gubi zdania na granicach, powyżej 30% marnuje tokeny podczas retrievalu.
Zawsze mierz chunking na własnym benchmarku (RAGAS, DeepEval), bo publikowane wyniki różnią się nawet 2× w zależności od domeny.
Dlaczego chunking decyduje o jakości RAG
Pipeline RAG ma trzy miejsca, w których można stracić trafność odpowiedzi: chunking, embedding i reranking. Szczerze, w praktyce moich klientów (głównie firm z branży logistyki i wsparcia klienta) osiem na dziesięć „słabych retrieverów" okazuje się problemem chunkingu, nie embeddingów. Powód jest prosty. Jeżeli podzielisz fakturę PDF w środku tabeli z pozycjami, żaden embedding, nawet text-embedding-3-large od OpenAI ani Voyage-3, nie odzyska semantyki, którą sam rozerwałeś.
Dobry chunking realizuje trzy warunki jednocześnie. Po pierwsze, każdy chunk zawiera jedną spójną myśl: jeden akapit, jeden krok procedury, jedno pytanie i odpowiedź. Po drugie, chunk ma rozmiar dopasowany do modelu embeddingów: dla text-embedding-3-small to około 300–500 tokenów, dla Voyage-3-large nawet 1024. Po trzecie, chunki mają się na siebie lekko nakładać, żeby zdanie graniczne nie zniknęło w retrievalu. Trzy warunki, trzy strategie techniczne. Resztę artykułu poświęcam ich implementacji w Pythonie.
Jeżeli budujesz pierwsze RAG i szukasz szerszego kontekstu retrievalu, zajrzyj do mojego wcześniejszego poradnika o hybrid search w RAG z BM25 i rerankerem. Chunking i hybrid search to dwie najskuteczniejsze dźwignie jakości, zanim zaczniesz eksperymentować z fine-tuningiem embeddingów.
Recursive character splitting, czyli baseline w LangChain
Rekurencyjny splitter to najstarszy, najprostszy i wciąż domyślny wybór. Idea jest taka: próbuj podzielić tekst na coraz mniejsze jednostki (paragrafy, zdania, słowa, znaki), aż każdy fragment zmieści się pod ustalony limit. LangChain implementuje to w klasie RecursiveCharacterTextSplitter, a LlamaIndex w SentenceSplitter. Oba działają na tej samej zasadzie i oba przyjmują parametry chunk_size i chunk_overlap.
from langchain_text_splitters import RecursiveCharacterTextSplitter
# separatorami są kolejno: dwie nowe linie (koniec akapitu),
# jedna nowa linia, spacja i pojedynczy znak.
splitter = RecursiveCharacterTextSplitter(
chunk_size=600, # docelowy rozmiar w znakach
chunk_overlap=100, # ~17% overlap
length_function=len,
separators=["\n\n", "\n", ". ", " ", ""],
)
with open("regulamin.md", encoding="utf-8") as f:
text = f.read()
chunks = splitter.create_documents([text])
print(f"Powstało {len(chunks)} chunków")
for c in chunks[:3]:
print("---")
print(c.page_content[:200])
Trzy rzeczy warto wiedzieć o tym snippecie. Po pierwsze, chunk_size mierzy znaki, nie tokeny. Jeżeli chcesz precyzyjnie kontrolować budżet tokenów pod model embeddingów, podmień length_function=len na wrapper wokół tiktoken.encoding_for_model("text-embedding-3-small").encode. Po drugie, kolejność separatorów ma znaczenie: splitter najpierw próbuje ciąć po \n\n, dopiero gdy fragment jest wciąż za długi, schodzi niżej. Po trzecie, dla języka polskiego dodaj do listy separatorów "! " i "? ", inaczej zdania kończące się wykrzyknikiem trafią razem z kolejnym.
Semantic chunking, czyli dzielenie po granicach znaczenia
Recursive splitter tnie „ślepo". Nie wie, czy dwa sąsiednie akapity mówią o tym samym. Semantic chunking rozwiązuje ten problem, licząc podobieństwo kosinusowe embeddingów kolejnych zdań i tnąc dokument tam, gdzie podobieństwo gwałtownie spada. Pomysł spopularyzował Greg Kamradt w 2023, a w LlamaIndex jest to SemanticSplitterNodeParser; w LangChain to SemanticChunker z pakietu langchain-experimental.
from langchain_experimental.text_splitter import SemanticChunker
from langchain_openai.embeddings import OpenAIEmbeddings
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
# breakpoint_threshold_type:
# "percentile" - domyslne, tnij tam gdzie roznica > 95 percentyla
# "standard_deviation" - tnij gdy > 3 sigma od sredniej
# "interquartile" - odporne na outliery
# "gradient" - nowosc 2025, dobre do dokumentow technicznych
chunker = SemanticChunker(
embeddings,
breakpoint_threshold_type="percentile",
breakpoint_threshold_amount=95,
)
docs = chunker.create_documents([text])
Semantic chunking daje najlepsze rezultaty na tekstach narracyjnych: artykułach, transkryptach rozmów, dokumentacji produktowej. Na własnym benchmarku klienta z branży e-commerce (2400 pytań FAQ) zmiana z recursive na semantic chunking podniosła recall@5 z 0.71 do 0.82, przy koszcie jednej dodatkowej sekundy na dokument. Nie zadziała jednak dobrze na dokumentach strukturalnych: fakturach, tabelach, kodzie źródłowym. Dla nich lepszy jest content-aware splitter (patrz sekcja o Markdown i PDF poniżej).
Uwaga na koszt. SemanticChunker wywołuje model embeddingów raz na zdanie. Dokument liczący 5000 zdań to 5000 requestów. Zawsze cachuj wyniki, najprościej w SQLite lub Redis. Jeżeli Twój korpus przechodzi przez re-indeksację co tydzień, przeczytaj też mój tekst o semantic cachingu w Pythonie, gdzie omawiam GPTCache i Redis Vector Search jako warstwę pomocniczą.
Agentic chunking, czyli LLM jako redaktor
Agentic chunking to najnowsza i najdroższa strategia: LLM otrzymuje surowy tekst i zadanie „podziel to na samodzielne, sensowne bloki". Zamiast heurystyk semantycznych opartych na embeddingach, decyzję o cięciu podejmuje model językowy. Podejście spopularyzował Greg Kamradt w cyklu Retrieval Tutorials i szybko trafiło do produkcyjnych pipeline'ów dla dokumentów o wysokiej wartości: umów, opinii prawnych, raportów due diligence.
import anthropic
import json
client = anthropic.Anthropic()
PROMPT = """Otrzymasz fragment dokumentu prawnego. Podziel go na
samodzielne bloki tresci. Kazdy blok musi:
- zawierac jedna kompletna mysl (klauzule, definicje, zobowiazanie)
- miec 200-500 slow
- konczyc sie przed naglowkiem lub definicja nowego pojecia
Zwroc JSON: {"chunks": [{"title": "...", "text": "..."}]}
Dokument:
<<<
%s
>>>"""
def agentic_chunk(text: str, model="claude-haiku-4-5-20251001"):
resp = client.messages.create(
model=model,
max_tokens=8000,
messages=[{"role": "user", "content": PROMPT % text}],
)
return json.loads(resp.content[0].text)["chunks"]
chunks = agentic_chunk(open("umowa.txt").read())
for c in chunks:
print(c["title"], "-", len(c["text"]), "znakow")
Trzy uwagi. Po pierwsze, używam claude-haiku-4-5. Sonnet 5 też się nada, ale przy Haiku koszt na 100-stronicowy dokument to około 0,03 USD, przy Sonnecie już 0,25 USD. Po drugie, dla dokumentów dłuższych niż 100k tokenów podziel je najpierw rekurencyjnie na okna po 50k, przetwórz każde osobno i zszyj wyniki, inaczej ryzykujesz halucynacje modelu w środku dokumentu. Po trzecie, agentic chunking to jedyna metoda, która sensownie dodaje metadane do chunków (tytuł, kontekst nadrzędny), a te metadane potem można przekazać rerankerowi jako dodatkowy sygnał.
Late chunking od Jina AI
Late chunking to trik z 2024 od zespołu Jina AI, który odwraca standardowy porządek operacji. Zamiast najpierw dzielić dokument, a potem osadzać każdy chunk osobno, osadza się cały dokument (używając modelu z długim kontekstem, np. jina-embeddings-v3 z oknem 8192 tokenów), a dopiero potem agreguje token-embeddingi w chunki. W ten sposób każdy chunk „pamięta" kontekst z całego dokumentu: nazwiska własne, odniesienia zaimkowe, definicje wcześniejszych pojęć.
W benchmarkach opublikowanych przez Jina AI late chunking daje 8–17% wzrost recall względem naive chunkingu na korpusach BEIR i LongEmbed. W praktyce warto go rozważyć, gdy Twoje dokumenty mają silne zależności odniesień, np. rozdziały książki, dokumentacja techniczna z jednym słowniczkiem na początku, protokoły spotkań z osobami wspominanymi po imieniu.
Jaki jest najlepszy rozmiar chunka do RAG?
Nie ma jednej odpowiedzi, ale są mocne wskazówki empiryczne z 2025 i 2026. Poniższa tabela podsumowuje wyniki, które konsekwentnie widuję u klientów i które są zgodne z raportami Anthropic, Chroma i Weaviate.
Typ dokumentu
Chunk (tokeny)
Overlap
Strategia
FAQ / short Q&A
128–256
0
1 pytanie = 1 chunk (bez splittera)
Artykuły blogowe
300–500
15%
Recursive lub semantic
Dokumentacja techniczna
500–800
10%
Markdown-aware splitter
Kod źródłowy
500–1500
0
AST-based (language-specific)
Umowy i regulaminy
400–700
20%
Agentic chunking
Transkrypty rozmów
256–512
15%
Semantic chunking
Raporty finansowe (PDF z tabelami)
zmienne
0
Table-aware (Unstructured / LlamaParse)
Powyższe liczby to punkt startowy, nie ostateczna prawda. Dla każdego korpusu warto uruchomić szybki A/B test na 3–5 wariantach rozmiaru chunka i zmierzyć recall@10 na 50–100 realnych pytaniach użytkowników. Wynik potrafi zaskoczyć. U jednego klienta 128-tokenowe chunki pokonały 512-tokenowe o 12 punktów procentowych, mimo że dokumenty były typową dokumentacją techniczną. Powodem okazało się to, że użytkownicy pytali punktowo („jaki jest limit prędkości API"), a nie kontekstowo.
Chunkowanie kodu, Markdown i PDF
Kod źródłowy to najgorszy kandydat pod recursive splitting. Cięcie w środku funkcji lub klasy niszczy zarówno składnię, jak i semantykę. LangChain od wersji 0.3 udostępnia Language enum z parserami dla 20+ języków (Python, JavaScript, Go, Rust, Solidity...); używa on tree-sitter pod spodem i tnie po granicach funkcji / klas.
from langchain_text_splitters import RecursiveCharacterTextSplitter, Language
py_splitter = RecursiveCharacterTextSplitter.from_language(
language=Language.PYTHON,
chunk_size=800,
chunk_overlap=0,
)
chunks = py_splitter.create_documents([source_code])
Dla Markdown używaj MarkdownHeaderTextSplitter. Tnie po nagłówkach i dodaje ścieżkę nagłówków jako metadane. To bezcenne dla dokumentacji: retriever może potem filtrować po sekcji („zwróć tylko chunki spod nagłówka Instalacja"), a LLM widzi pełną hierarchię jako kontekst.
PDF to osobna historia. Naive PyPDF2 ekstrahuje tekst liniowo, gubiąc dwukolumnowy layout, tabele i podpisy pod obrazkami. W 2026 standardem branżowym są cztery narzędzia: Unstructured.io (open source, dobrze radzi sobie z tabelami), LlamaParse (płatne API od LlamaIndex, świetne do faktur), Docling (od IBM, open source, uruchamiane lokalnie) i MinerU (chiński open source, mocny na naukowych PDF-ach). Wszystkie wypluwają strukturalny JSON, który potem można podzielić z zachowaniem tabel jako pojedynczych chunków.
Jak zmierzyć jakość chunkingu na własnym korpusie
Bez ewaluacji nie ma inżynierii. Zanim zaczniesz eksperymentować z semantic ani agentic chunkingiem, zbuduj prosty harness ewaluacyjny: 50 pytań z oczekiwanymi golden chunks i metryka recall@k oraz mean reciprocal rank (MRR).
from ragas import evaluate
from ragas.metrics import context_recall, context_precision
from datasets import Dataset
# dla kazdego pytania: (question, ground_truth, retrieved_contexts)
data = Dataset.from_dict({
"question": questions,
"ground_truth": ground_truths,
"contexts": retrieved_chunks,
"answer": generated_answers,
})
result = evaluate(
data,
metrics=[context_recall, context_precision],
)
print(result)
RAGAS to biblioteka de facto standard do ewaluacji RAG. Mierzy m.in. context_recall (czy retriever zwrócił wszystkie istotne fragmenty), context_precision (czy zwrócone fragmenty były trafne) oraz answer_relevancy (czy odpowiedź LLM trzyma się kontekstu). Wersja 0.2 z 2025 dodała metryki specyficzne dla chunkingu; noise_sensitivity pokazuje, jak bardzo model daje się rozproszyć nietrafnym chunkiem.
Alternatywnie użyj DeepEval. Pisałam o nim więcej w tekście o ewaluacji agentów AI z DeepEval i Langfuse. Oba narzędzia integrują się z pytestem, więc możesz zrobić z ewaluacji chunkingu regularny test regresyjny w CI.
Najczęstsze błędy i jak ich uniknąć
W ciągu ostatnich dwóch lat wielokrotnie audytowałam czyjeś pipeline'y RAG. Cztery błędy powracają jak bumerang.
Błąd 1: chunk_size w znakach zamiast w tokenach. Dla języka polskiego jeden token to średnio 2–3 znaki (dla angielskiego ~4). Jeżeli ustawisz chunk_size=1000 znaków spodziewając się „mniej więcej 250 tokenów", w polskim tekście dostaniesz 400–500. Wynik: chunki nie mieszczą się w oknie kontekstowym rerankera. Zawsze mierz w tokenach.
Błąd 2: zerowy overlap na tekstach narracyjnych. Bez overlapu ostatnie zdanie chunku N zaczyna nową myśl, którą kończy pierwsze zdanie chunku N+1, i obie giną. Overlap 15–20% eliminuje ten problem.
Błąd 3: chunkowanie po ekstrakcji z PDF bez sprawdzenia layoutu. Dwukolumnowe PDF ekstrahowane PyPDF2 dają tekst pomieszany między kolumnami. Zawsze przeglądnij ręcznie 5 losowych stron po ekstrakcji, zanim zaczniesz chunkować.
Błąd 4: brak metadanych. Chunk bez metadanych (źródło, sekcja, data) to strzelanie w ciemno. Zawsze dodawaj co najmniej source, title i section_path. Wielu retrieverów pozwala potem filtrować po tych polach, co drastycznie poprawia precyzję.
Najczęściej zadawane pytania
Czym różni się semantic chunking od recursive chunking?
Recursive chunking dzieli tekst po statycznych separatorach (paragraf, zdanie, słowo) do osiągnięcia limitu znaków. Semantic chunking używa embeddingów zdań i tnie tam, gdzie podobieństwo kosinusowe między sąsiednimi zdaniami gwałtownie spada, dzięki czemu chunki lepiej pokrywają się z granicami znaczenia. Semantic jest wolniejszy i droższy (jedno wywołanie embeddingów na zdanie), ale daje 5–15% wyższy recall na tekstach narracyjnych.
Jak wybrać chunk overlap?
Bezpieczny domyślny to 10–20% rozmiaru chunka. Dla chunków 500-tokenowych oznacza to 50–100 tokenów overlapu. Zero overlapu użyj tylko dla dokumentów, w których struktura naturalnie oddziela chunki (FAQ, kod). Powyżej 30% overlapu marnujesz tokeny i zwiększasz szansę, że retriever zwróci dwa niemal identyczne chunki obok siebie.
Czy warto używać agentic chunkingu dla wszystkich dokumentów?
Nie. Agentic chunking jest 50–200× droższy od recursive splittingu i ma sens tylko dla dokumentów o wysokiej wartości informacyjnej: umów, dokumentów prawnych, raportów due diligence, protokołów medycznych. Dla FAQ, dokumentacji technicznej czy artykułów blogowych recursive lub semantic dają porównywalną jakość za ułamek kosztu.
Jak chunkować kod źródłowy?
Używaj splittera świadomego składni języka. W LangChain to RecursiveCharacterTextSplitter.from_language(Language.PYTHON, ...), w LlamaIndex to CodeSplitter. Oba używają tree-sitter i tną po granicach funkcji, klas oraz bloków importów. Nigdy nie tnij kodu po znakach ani liniach, bo rozerwiesz funkcje w połowie i retriever nie znajdzie kompletnych sygnatur.
Czy chunking wpływa na koszty embeddingów?
Tak, bardzo. Koszt embeddingów rośnie liniowo z liczbą chunków. Chunki 256-tokenowe generują dwa razy więcej wywołań niż 512-tokenowe dla tego samego korpusu. Dla dużych korpusów (>10 mln tokenów) warto rozważyć batch embedding API. Pisałam o tym w artykule o OpenAI Batch API, gdzie omawiam 50% redukcję kosztów przy klasyfikacji i embeddingach.
Priya spent four years at Zapier building the Tables product before leaving in 2023 to consult on agent infrastructure for Series A startups. She's shipped custom n8n nodes for two YC-backed companies (a clinical-trial logistics platform and a freight broker), and her PR adding streaming-token support to LangChain's Bedrock chat wrapper was merged in early 2024.
Most of her current work is unglamorous: helping ops teams replace 40-step Make.com scenarios with a single LangGraph state machine, then arguing with their CFO about token budgets. She writes here about the parts of agent work that vendor blogs skip - eval harnesses that don't lie, retry logic that survives a rate-limited Anthropic endpoint at 2am, and why 'just add a vector DB' is almost always the wrong answer.
Based in Toronto. Eight years total in workflow tooling.
Zbuduj samokorekcyjny pipeline Agentic RAG w Pythonie z LangGraph 1.0 i ChromaDB. Poradnik krok po kroku z trzema wzorcami (Corrective, Adaptive, Self-Reflective RAG), działającym kodem i wskazówkami produkcyjnymi.