Chunkingstrategier i RAG-jämförelse: Alternativ, kompromisser och exempel
Jämförelse av chunkningsstrategier i RAG
Chunking är den * mest undervärderade * hyperparametern i Retrieval ‑ Augmenterad Generering (RAG): den bestämmer tyst och osynligt vad din LLM “ser”, hur dyrt ingångsarbete blir, och hur mycket av LLM:s kontextfönster du förbrukar per svar.
Detta artikel behandlar chunking som ett ingenjörsproblem: definiera mål, välj en strategi, mät, sedan iterera.
Om du är ny i RAG-arkitektur, börja med huvudartikeln Retrieval-Augmented Generation (RAG) Tutorial: Architecture, Implementation, and Production Guide.

TL;DR (Exekutiv sammanfattning)
RAG-system hämtar chunkar, inte dokument. Chunking definierar därmed enheten för hämtning, enheten för inbäddningskostnad, och enheten för bevis som du kan visa eller citera. I den ursprungliga RAG-formuleringen levererar hämtningen passager till genereringen; passagergränserna är effektivt dina chunkgränser.
En bra chunkingstrategi söker en Paretofront över: hämtningens kvalitet (återhämtning/precision av bevis), koherens (chunkar måste vara tolkbara), och kostnad (inbäddning, lagring och frågefördröjning). Det finns ingen globalt optimal chunkstorlek eller metod, och produktionsystem rutinmässigt blandar strategier (t.ex. strukturmedveten chunking för PDF:er + semantiskt medvetna delar för prosa + AST-chunking för kod).
För de flesta “dokumentation QA” och interna kunskapsbankar, är en säker standard en strukturrespekterande rekursiv splitter med modig överlapp (för att minska gränsförluster), stödd av en
vektorlager
med metadatafiltrering och valfri omrankning. LangChain:s RecursiveCharacterTextSplitter är en vanlig implementering av detta hierarkiska-separator-idé; överlapp existerar specifikt för att minska informationsförlust när relevant kontext skäras vid gränser.
När dokumenten har stark struktur (PDF:er med rubriker, tabeller, listor, rubriker), kan elementbaserad / strukturmedveten chunking överträffa tokenräkningens skärning medan den producerar färre chunkar. En 2024-studie på SEC-filningar fann att elementtypbaserad chunking förbättrade RAG-resultat och också minskade antalet chunkar (och därmed vektorer) ungefär med hälften jämfört med strukturagokra metoder – minskade indexkostnad och potentiellt förbättrade frågeförsening.
Om du kan erbjuda mer uppföljningsberäkning, semantisk chunking (skär vid ämnesbyten med hjälp av inbäddningssimilaritet) kan betydligt förbättra hämtningens exakthet för narrativ text och blandade ämnesidor. Äldre ämnessegmenteringsalgoritmer som TextTiling visar den allmänna principen: starka ord/vectorspråksbyten är goda gränskandidater.
För mycket långa, interna tvärvetenskapliga material (policyer, RFC:er, standarder, stora handböcker), kan hierarkisk chunking + hierarkisk hämtning/merging (förälder/barnnoder) återställa större kontinuerliga kontext vid efterfrågan. LlamaIndex:s hierarkiska nodparser producerar grov-till-fin chunkhierarkier, och AutoMergingRetriever kan slå samman bladnoder till föräldernoder vid hämtningstid när tillräckligt många relaterade barn hämtas.
Chunkingmål och avvägningar
Chunking är inte bara “del text så att den passar in i en inbäddningsmodell”. Det styr flera nedströms och driftbeteenden.
Hämtningens detaljgrad vs hämtningsskumma. Mindre chunkar ökar chansen att den exakta meningen som innehåller ett svar är hämtbar (högre återhämtning vid fast top-k). Men de producerar också fler vektorer, vilket ökar indexstorleken och ibland visar “nära matchar” som är semantiskt lika men inte faktiskt bevisande (lägre precision). Täta hämtare som DPR byggdes runt att hämta passager effektivt för QA, vilket visar att passagergränser spelar roll för slutpunkt QA-prestanda.
Kontextens koherens vs gränsförlust. Koherenta chunkar hjälper LLM att resonera korrekt och minskar hallucinationer genom att ge komplett lokal kontext (definitioner, begränsningar, förutsättningar). Överlapp minskar gränsförlust men skapar duplicerad text, vilket kan leda till redundanta hämtresultat och inflationerad promptlängd om du inte deduplicerar/merger.
Inbäddnings- och indexkostnad. Inbäddningskostnad är vanligtvis proportionell till inbäddade token, och ingångstid skalar med antalet chunkar (plus vektor DB skrivöverskott). För OpenAI-inbäddningar har begäran en per-ingång maxtokengräns (8192 token för alla inbäddningsmodeller) och en max total token som summeras över ingångar per begäran (300 000 token). För stora korpusar kan Batch API minska kostnaden med cirka 50% med en asynkron, 24-timmars omvändning – användbar för backfills och periodiska omindexeringar.
Vektorindexstorlek, RAM och försening. Fler chunkar innebär fler vektorer och potentiellt fler minne och långsammare frågor (beroende på indextyp). FAISS framställer indexdesign explicit som en uppsättning avvägningar mellan sökningstid, sökningens kvalitet och minne per indexvektor; det erbjuder också GPU-implementationer för snabb exakt och ungefärlig sökning.
Nedströms LLM-promptlängd / kontextfönsteranvändning. Hämtarens utdata blir promptbudget. En chunkingstrategi som konsistently hämtar “just rätt” kontext kan förbättra svarskvalitet och minska kostnad. Tvärtom kan överlapp och för stora chunkar inflationera promptlängd. I praktiken justerar du ofta: (chunkstorlek, överlapp, top-k, omrankning/merger) tillsammans.
Uppdaterings/ingångskostnad och deduplivering. Chunking påverkar hur dyrt det är att uppdatera data. Mindre chunkar gör delvisa uppdateringar billigare (du kan om-embed bara den ändrade sektionen) men gör också deduplivering svårare om överlappande eller nära-duplicerade chunkar sprids.
Var chunking sitter i RAG-arbetsflödet

Chunkingstrategier och alternativ
Här nedan är de huvudsakliga chunkfamiljer du kommer att stöta på i modern RAG. I praktiken blandar du ofta två: strukturförst chunking (respektera dokumentgränser) plus tokenbudgetenforcement (se till att chunkar passar dina inbäddnings- och promptbudgeter).
Fixstorlek chunking
Vad det är. Dela texten i lika stora block genom tecken eller token.
Varför det finns. Det är enkelt, snabbt, förutsägbart och lätt att parallellisera. Det är också den enklaste strategin för streaming-ingång där du inte har full dokumentkontext.
Var det misslyckas. Det ignorerar gränser (sätten, avsnitt, kodblock) så det kan bryta definitioner eller dela upp “fråga/svarpar” över chunkar, vilket ökar hämtfel.
Operativ profil. Lägsta ingångskomplexitet; förutsägbart antal chunkar; lättaste cachelagring. Men du behöver ofta överlapp (nedan) för att undvika gränsförlust.
Överlapp chunking
Vad det är. Alla strategier där konsekutiva chunkar delar en fix överlappningsregion (t.ex. 10–20% av chunkstorlek). Överlapp är standard i många ramverk eftersom det minskar informationstap när kontext delas.
Varför det spelar roll. Överlapp är effektivt en “mässig gräns” – det låter hämtning fånga en fakta som sträcker sig över en gräns.
Kostnader och fällor. Fler token inbäddade; fler duplicerad text i indexet; högre risk för att hämta flera nästan identiska chunkar om du inte deduplicerar vid hämtningstid (t.ex. slå samman efter källförskjutningar eller använd MMR).
Sats- och styrsbaserad chunking
Vad det är. Dela texten vid sats- eller styrsgränser, sedan packa samman sats/styrs i chunkar upp till en tokenbudget.
Varför ingenjörer gillar det. Det förbättrar koherens för naturligt språk och är robust för dokument med konventionella punkt och avstånd.
Verktyg. NLTK:s sent_tokenize() använder Punkt-satsgränsdetektering som standard, och spaCy erbjuder regelbaserade satsgränsverktyg som Sentencizer (nyttigt när du vill ha satsdelar utan ett fullständigt beroendemodell).
Misslyckningslägen. Ovanlig punkt (loggar, chatttranskript), tabeller, kod och punktlistor kan bryta satssegmenteringssuppositioner.
Glidande fönster chunking
Vad det är. Skapa chunkar med en fix fönsterstorlek och ett steg (strid). Detta är den “systematiska överlappning” versionen av chunking.
När det är bra. Tidsserietext, transkript, chattloggar, mötesanteckningar – något där relevanta fakta kan visas över lokala nabolägen och du vill ha robust återhämtning.
När det är dåligt. Det förstärker redundans och kan vara dyrt på stora skala. Det tenderar också att hämta redundanta fönster om inte deduplicerat.
Rekursiv / separatorhierarki chunking
Vad det är. Starta med stora “naturliga” separerare (t.ex. \n\n för styrs) och rekursivt dela upp i mindre enheter (sats, utrymme) endast när det behövs för att hålla under en storleksbudget. LangChain dokumenterar detta beteende explicit: en rekursiv splitter försöker behålla större enheter intakta och endast går tillbaka till mindre separerare när en enhet fortfarande överskrider chunkstorlek.
Varför det är en stark standard. Det respekterar struktur utan att kräva komplex dokumentparsing. Det är en praktiskt smart lösning för Markdown, HTML-som-text och dokumentation.
Viktiga justeringsknappar. chunk_size, chunk_overlap, och length_function (tecken vs token), samt anpassade separerare för flerspråkiga kodbaser.
Semantisk (inbäddningsmedveten) chunking
Vad det är. Detektera ämnesbyten med hjälp av semantiska representationer (inbäddningar) och dela upp där likhet sjunker. Det speglar klassiska segmenteringsidéer som TextTiling, som använder förändringar i lexikala koherens för att hitta underämnesgränser.
Varför det kan överträffa storleksbaserat chunking. Du stannar upp vid godtyckliga tokenräkningar och istället justera chunkar med ämnesgränser – ofta förbättra hämtningens precision för flerämnesdokument (bloggar, designdokument, tickets, incidentrapporter).
Kostnader. Du kan behöva ytterligare inbäddningar under chunking (satsnivå eller styrsnivå inbäddningar) innan slutgiltiga chunkinbäddningar. Det kan fördubbla eller trippela inbäddningsanrop om du inte återanvänder mellanliggande inbäddningar.
Praktiskt trick. “Semantiskt medveten packning”: beräkna satsinbäddningar en gång, gruppera satsen i ämneskoherenta segment, sedan inbäddning varje slutgiltigt segment.
Hierarkisk chunking (förälder/barn)
Vad det är. Bygg en flergransskap representation: grova förälderchunkar (t.ex. avsnittsstorlek) med finare barnchunkar (t.ex. styrsstorlek). LlamaIndex:s hierarkiska nodparsing producerar “grovtillfint” hierarkier som standard (t.ex. 2048 → 512 → 128 tokenstegrader), och AutoMergingRetriever kan slå samman barnnodar till föräldrar vid hämtningstid när tillräckligt många relaterade barn hämtas.
Varför det hjälper. Det undviker att välja mellan “små chunkar för återhämtning” och “stora chunkar för koherens” genom att lagra båda, och välja vid frågetid.
Kostnader. Mer komplex ingångs- och hämtninglogik, samt potentiellt mer lagring (eftersom du lagrar flera granulariteter).
Anpassningsbar / LLM-baserad chunking
Vad det är. Använd en LLM för att bestämma chunkgränser (och eventuellt generera sammanfattningar eller kontextuella rubriker). Weaviate beskriver LLM-baserad chunking explicit som att låta LLM skapa semantiskt koherenta chunkar, istället för att förlita sig på fasta regler eller inbäddningssimilaritet.
När det är värt. Högverdiga korpusar där rättighet dominerar kostnad (juridisk, komplian, support runbooks), och där dokumenten är otydliga, heterogena och dåligt segmenterade.
Risker. Kostnad, fördröjning och oavgjärlighet. Du vill ha cachelagring, avgjärlig dekodning och regressionstester (se bedömningssnitt).
Struktur- och elementbaserad chunking (dokumenten är inte ren text)
Vad det är. Parsa dokumentet till element (rubriker, styrs, listor, tabeller, rubriker) med en dokumentförståelselager, sedan chunka med dessa element. Unstructured:s chunkningsfunktioner använder explicit metadata och dokumentelement (producerade av partitionering) för att skapa chunkar för RAG. Docling:s HierarchicalChunker skapar chunkar per detekterat dokumentelement och kopplar strukturell metadata som rubriker/rubriker.
Bevis från nylig forskning. En 2024-studie på SEC-filningar hävdar att endast styrschunking försummar dokumentstruktur och föreslår chunking efter strukturella element; det rapporterar förbättrade RAG-resultat och färre chunkar/vektorer än strukturagokra metoder.
Varför det spelar roll för multimodal. Tabeller, figurer och rubriker innehåller ofta den sanna verkligheten. “Flätning” dem till ren text kan förstöra signaler som hämtning annars skulle utnyttja.
Kodmedveten chunking (AST/struktur)
Vad det är. Chunka kod efter syntaktiska enheter (funktioner, klasser, moduler), eventuellt inklusive dokumentsträngar och kommentarer.
Varför det spelar roll. Faststorleks token-splits tenderar att skära funktioner i halva och separera dokumentsträngar från implementeringar – dåligt för kod sökning och “förklara denna funktion” RAG-användningsscenarier.
Implementeringsalternativ. För Python är den inbyggda ast-modulen ofta tillräcklig. För flerspråkiga repos är tree-sitterbaserade chunkers vanliga.
Bedömningsdimensioner och hur man jämför chunkstrategier
Chunking bör bedömas som en systemkomponent.
Hämtningens kvalitetsmått
Använd standard IR-mått för hämtningslager:
- Recall@k / Precision@k: Innehöll de top-k de gyllene bevisen?
- MRR / nDCG: Rankade de gyllene bevisen högt?
BEIR är ett vidare använda heterogent benchmark för IR-bedömning över uppgifter/områden, och markerar avvägningar mellan sparsamma, tät, sen-interaktion och omrankningsmetoder.
Chunking påverkar dessa mått eftersom det definierar vad som räknas som “en relevant hämtad artikel”.
Slutpunkt RAG-svars kvalitetsmått
Om du bygger QA eller assistenter, är hämtningens mått nödvändiga men inte tillräckliga. Du behöver också:
- Kontext återhämtning / precision: om hämtade kontexter innehåller relevant bevis och undviker brus.
- Tillitvärdhet: om det genererade svaret stöds av hämtad kontext.
RAGAS ger konkreta definitioner och implementeringar för “tillitvärdhet” och andra RAG-orienterade mått.
Systemkostnad och prestandadimensioner
Chunking ändrar dessa levers:
Fördröjning (p50/p95). Frågeförsening ökar vanligtvis med fler vektorer och fler efterbearbetningar. Din vektorindex spelar också roll: FAISS-indextyper avväger sökningstid, kvalitet, minne och träning/addingstid.[^faiss]
Inbäddningskostnad och throughputs. OpenAI-inbäddningar faktureras per token; embeddings-API har explicita per-ingångs och per-begärandegräns.[^openai_embed_create] För offline-ingång, minskar Batch-API kostnaden och erbjuder högre kvot i utbyte för icke-realtid omvändning.[^openai_batch]
Indexstorlek och minne. Runt, att lagra N float32-vektorer av dimension d kostar ~4 * N * d bytes endast för de råa vektorerna (plus metadata + indexöverhäng). Chunking påverkar N. Inbäddningsdimensionalitet påverkar d, och OpenAI:s embeddings-API tillåter kontroll av utdata-dimensionen via dimensions-parametern.[^openai_embed_create]
LLM-promptbudget. Större chunkar och överlapp inflationerar prompttoken. Detta kan öka försening och kostnad, och öka “förlorad i mitten” typ av misslyckanden där modeller betonar mindre på vissa kontexter. I praktiken justerar du ofta:
- hämta små chunkar,
- slå samman/deduplicera,
- eventuellt sammanfatta,
- skicka en kompakt bevisuppsättning till LLM.
Uppdaterings/ingångskostnad. Mindre chunkar gör delvis om-embed men ökar bokföring. För streaming-ingång, föredra deterministisk, inkrementell chunking (fast eller glidande fönster) och koppla stabila ID:er (dokument_id, offsetintervall, hash).
Experimentell design: en pragmatisk benchmarkloop
En återproducerbar chunkingbenchmark har typiskt:
- En fast korpusnapshot + fast uppsättning frågor med gyllene bevis (eller minst förväntade svarsintervall).
- En fast inbäddningsmodell och vektorindexkonfiguration.
- En “endast hämtning” bedömning (recall@k, nDCG) plus “RAG”-bedömning (tillitvärdhet, svarsrelevans).
- Kostnadstelemetri: #chunkar, inbäddade token, $/månad lagring, p95 frågeförsening, prompttoken.
Unstructured SEC-filningarpaper är ett bra exempel på att bedöma chunkstrategier med både hämtningssorterade mått och QA-precisionmått.
Praktiska riktlinjer, beslutsdiagram och rekommenderade standarder
Rekommenderade standarder som fungerar förvånansvärt bra
Om du behöver en robust “dag 1”-strategi för allmän dokumentation QA:
- Parse lätt: bevara rubriker och grundläggande metadata (källa, avsnittstitel, URL/sökväg, tidsstämpel).
- Chunka med en rekursiv separator splitter (avsnitt → sats → ord), med modig överlapp.
- Inbäddning med en stark generell inbäddningsmodell.
- Index med metadata (dokument_id, avsnitt, ACLs) och deduplicera under hämtning.
- Lägg till omrankning eller hierarkisk sammanslagning endast om din bedömning visar ett gap.
Detta överensstämmer med hur vanliga RAG-ramverk beskriver chunköverlapp och strukturrespekterande delning.
Vilken chunkingmetod att använda - Beslutsdiagram

| Användningssnitt | Rekommenderad chunkingstandard | Viktiga parametrar att justera | Vanlig misslyckningsmod | Uppgraderingsväg |
|---|---|---|---|---|
| Kortform QA över dokument (FAQ, intern wiki) | Rekursiv/separator chunking + överlapp | chunk_size, överlapp, top-k |
Saknar tvärsatsbevis vid gräns | Lägg till semantisk chunking eller omrankare |
| Långform QA (policyer, standarder, handböcker) | Hierarkisk chunking + sammanfogningshämtare | förälder/barnstorlekar, sammanfogningsgräns | Hämtar små fragment; LLM saknar full kontext | Auto-sammanfogning/hierarkisk hämtning |
| Sammanfattning (per dokument / per avsnitt) | Strukturmedvetna chunkar (avsnitt) | avsnittsidentifiering, max token | Sammanfattningar missar tvärsnittsankar | Hierarkisk sammanfattning + avsnittsgraf |
| Kod sökning & “förklara denna funktion” | AST/funktionsnivå chunkar | inkludera dokumentsträngar/kommentarer, max token | Funktionssplit; förlorar signatur/bruk | Repo-medveten hierarki (modul→klass→funktion) |
| Multimodal PDF:er (tabeller/figurer) | Elementbaserad chunking (rubrik/tabell/rubrikmedveten) | tabellserialisering, rubriksammanfogning | Tabellinnehåll förlorat eller förstörd | Använd Docling/Unstructured + strukturerade serialisatorer |
| Streaming-ingång (loggar, chattar, tickets) | Glidande fönster eller faststorleks token | fönster, steg, dedup | Överhämtning av redundant fönster | Lägg till semantisk gränsdetektering på batchar |
chunking - Kvalitativ prestandajämförelse
Behandla detta som “förväntad riktning för förändring” (validera med dina egna data).
| Strategi | Hämtningens exakthetspotential | Koherens i hämtad kontext | Ingångskomplexitet | Vektortal / indexstorlek | Inbäddningskostnad | Frågeförseningseffekt | Bäst för |
|---|---|---|---|---|---|---|---|
| Fixstorlek (ingen överlapp) | Medel | Låg | Låg | Medel | Låg | Medel | snabba prototyper, homogena text |
| Fixstorlek + överlapp | Medel–Hög | Medel | Låg | Hög | Medel–Hög | Medel–Hög | QA där gränsförlust skadar |
| Sats/paragrafpackning | Hög (prosa) | Hög | Medel | Medel | Medel | Medel | dokument, artiklar, ren prosa |
| Glidande fönster | Hög återhämtning | Medel | Medel | Mycket hög | Mycket hög | Hög | transkript, loggar, chatt |
| Rekursiv/separator | Hög | Hög | Medel | Medel | Medel | Medel | “standard” dokument RAG |
| Semantisk chunking | Hög–Mycket hög | Hög | Hög | Medel | Hög | Medel | flerämnesidor, narrativ text |
| Hierarkisk (förälder/barn) | Mycket hög | Mycket hög | Hög | Hög | Hög | Medel | långa handböcker / standarder |
| LLM-baserad/anpassningsbar | Mycket hög | Mycket hög | Mycket hög | Medel | Mycket hög | Medel–Hög | högintensiva korpusar |
| Element-/struktur-baserad | Hög–Mycket hög | Hög | Hög | Låg–Medel | Medel | Medel | PDF:er, rapporter, tabeller, blandade layouter |
| Kodmedveten (AST) | Hög (kod) | Hög | Medel | Medel | Medel | Medel | kod sökning, repoassistent |
DevOps och hårdvaranoteringar (ofta övergående)
Chunkval har påverkan på hur mycket infrastruktur du behöver:
- Mindre chunkar → fler vektorer → större index och mer RAM/disk. För självvärdbad FAISS kan det tvinga shardning eller diskbaserade index.
- Om du inbäddar lokalt, blir inbäddningsdurchput ett GPU-schemaläggningsproblem; om du inbäddar via API, blir tokenvolym ett FinOps-problem (Batch API är din vän för backfills).
- Vissa motorer (FAISS) ger GPU-accelerad sökning; detta kan flytta kostnad från RAM-begränsade CPU till GPU-minne och PCIe-throughput.
- Strukturmedveten parsing (PDF-layout, OCR, tabelluttag) är ofta CPU-begränsad och kan överväldiga inbäddningskostnaden för skannade dokument; budgetera separat.
Chunkning - Python-implementationsexempel
Alla exempel är utformade för att vara läsbara och körbara. Om du behöver en API-nyckel eller en körande databas, är det tydligt ur koden.
Delade verktyg: tokenräkning och stabila chunk-ID:or
from __future__ import annotations
import hashlib
from dataclasses import dataclass
from typing import Any, Iterable, Optional
from transformers import AutoTokenizer # pip install transformers
@dataclass(frozen=True)
class Chunk:
text: str
meta: dict[str, Any]
def sha1_id(*parts: str) -> str:
h = hashlib.sha1()
for p in parts:
h.update(p.encode("utf-8"))
h.update(b"\x1e")
return h.hexdigest()
# Använd någon tokenizer som ungefär matchar din LLM/embedding tokenisering.
TOKENIZER = AutoTokenizer.from_pretrained("bert-base-uncased")
def token_len(text: str) -> int:
return len(TOKENIZER.encode(text, add_special_tokens=False))
Fixstorlek tokenchunkning
def chunk_fixed_tokens(
text: str,
*,
chunk_size: int = 512,
) -> list[Chunk]:
token_ids = TOKENIZER.encode(text, add_special_tokens=False)
out: list[Chunk] = []
for i in range(0, len(token_ids), chunk_size):
window = token_ids[i : i + chunk_size]
chunk_text = TOKENIZER.decode(window)
out.append(
Chunk(
text=chunk_text,
meta={"strategy": "fixed_tokens", "start_token": i, "end_token": i + len(window)},
)
)
return out
Fixstorlek med överskridande + rullande fönster
def chunk_sliding_window(
text: str,
*,
window_tokens: int = 512,
stride_tokens: int = 384, # mindre steg = mer överskridande
) -> list[Chunk]:
assert 1 <= stride_tokens <= window_tokens, "steg måste vara inom (0, fönster]"
token_ids = TOKENIZER.encode(text, add_special_tokens=False)
out: list[Chunk] = []
start = 0
while start < len(token_ids):
end = min(start + window_tokens, len(token_ids))
window = token_ids[start:end]
out.append(
Chunk(
text=TOKENIZER.decode(window),
meta={"strategy": "sliding_window", "start_token": start, "end_token": end},
)
)
if end == len(token_ids):
break
start += stride_tokens
return out
Satsbaserad chunkning (NLTK) med tokenbudgetpackning
# pip install nltk
import nltk
from nltk.tokenize import sent_tokenize
nltk.download("punkt", quiet=True)
def chunk_by_sentences_nltk(
text: str,
*,
max_tokens: int = 512,
overlap_sentences: int = 1,
) -> list[Chunk]:
sents = [s.strip() for s in sent_tokenize(text) if s.strip()]
out: list[Chunk] = []
buf: list[str] = []
buf_tokens = 0
def flush():
nonlocal buf, buf_tokens
if not buf:
return
chunk_text = " ".join(buf).strip()
out.append(
Chunk(
text=chunk_text,
meta={"strategy": "sentences_nltk", "sent_count": len(buf)},
)
)
# Överskrid med att behålla de sista N-satserna
if overlap_sentences > 0:
buf = buf[-overlap_sentences:]
buf_tokens = token_len(" ".join(buf))
else:
buf, buf_tokens = [], 0
for s in sents:
s_tokens = token_len(s)
# Om en enskild sats överskrider budgeten, återgå till fixstorlek chunkning på det spannet
if s_tokens > max_tokens:
flush()
out.extend(chunk_fixed_tokens(s, chunk_size=max_tokens))
continue
if buf_tokens + s_tokens > max_tokens and buf:
flush()
buf.append(s)
buf_tokens += s_tokens
flush()
return out
Satsbaserad chunkning (spaCy) när du vill ha regelbaserad eller modellbaserad SBD
# pip install spacy
# python -m spacy download en_core_web_sm
import spacy
def chunk_by_sentences_spacy(
text: str,
*,
max_tokens: int = 512,
) -> list[Chunk]:
# För lättviktig regelbaserad delning (ingen beroende analys), använd sentencizer.
nlp = spacy.blank("en")
nlp.add_pipe("sentencizer") # regelbaserad satsgränsdetektering
doc = nlp(text)
sents = [sent.text.strip() for sent in doc.sents if sent.text.strip()]
return chunk_by_sentences_nltk(" ".join(sents), max_tokens=max_tokens, overlap_sentences=1)
Rekursiv separatorchunkning (LangChain)
# pip install langchain-text-splitters
from langchain_text_splitters import RecursiveCharacterTextSplitter
def chunk_recursive_langchain(
text: str,
*,
chunk_size: int = 1200,
chunk_overlap: int = 150,
) -> list[Chunk]:
splitter = RecursiveCharacterTextSplitter(
chunk_size=chunk_size,
chunk_overlap=chunk_overlap,
length_function=token_len, # tokenmedveten budgetplanering
separators=["\n\n", "\n", ". ", " ", ""], # anpassa efter din innehållstyp (t.ex. kod)
)
pieces = splitter.split_text(text)
return [
Chunk(text=p, meta={"strategy": "recursive_langchain", "chunk_size": chunk_size, "overlap": chunk_overlap})
for p in pieces
]
Semantisk chunkning med embeddingslikhet (OpenAI embeddings)
Den här metoden beräknar embeddings för kandidatenheter (satser/paragrafer), och hittar sedan semantiska “brytpunkter”.
# pip install openai numpy
import os
import numpy as np
from openai import OpenAI
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY", "YOUR_OPENAI_API_KEY"))
def embed_texts_openai(texts: list[str], *, model: str = "text-embedding-3-small") -> np.ndarray:
# OBS: för stora batchar, respektera begärans tokengränser och batchstorleksgränser.
resp = client.embeddings.create(model=model, input=texts)
embs = np.array([d.embedding for d in resp.data], dtype=np.float32)
return embs
def cosine_sim(a: np.ndarray, b: np.ndarray) -> float:
denom = (np.linalg.norm(a) * np.linalg.norm(b)) + 1e-8
return float(np.dot(a, b) / denom)
def chunk_semantic(
text: str,
*,
max_tokens: int = 800,
breakpoint_threshold: float = 0.70,
) -> list[Chunk]:
# 1) Börja från satskandidater
sents = [s.strip() for s in sent_tokenize(text) if s.strip()]
if len(sents) <= 1:
return [Chunk(text=text, meta={"strategy": "semantic", "note": "no_split"})]
# 2) Embed varje sats
embs = embed_texts_openai(sents)
# 3) Beräkna likhetsfall
sims = [cosine_sim(embs[i], embs[i + 1]) for i in range(len(sents) - 1)]
# 4) Skapa segment vid brytpunkter
out: list[Chunk] = []
buf: list[str] = []
buf_tokens = 0
for i, s in enumerate(sents):
# Lägg till sats
s_tok = token_len(s)
if buf and buf_tokens + s_tok > max_tokens:
out.append(Chunk(text=" ".join(buf), meta={"strategy": "semantic", "reason": "max_tokens"}))
buf, buf_tokens = [], 0
buf.append(s)
buf_tokens += s_tok
# Bestäm brytpunkt efter sats i (baserat på likhet med nästa)
if i < len(sims) and sims[i] < breakpoint_threshold:
out.append(
Chunk(
text=" ".join(buf),
meta={"strategy": "semantic", "reason": "sim_drop", "sim_to_next": sims[i]},
)
)
buf, buf_tokens = [], 0
if buf:
out.append(Chunk(text=" ".join(buf), meta={"strategy": "semantic", "reason": "final"}))
return out
Hierarkisk chunkning + sammanfogning av hämtning (LlamaIndex)
# pip install llama-index llama-index-llms-openai
from llama_index.core import Document, StorageContext, VectorStoreIndex
from llama_index.core.node_parser import HierarchicalNodeParser, get_leaf_nodes
from llama_index.core.retrievers import AutoMergingRetriever
from llama_index.core.storage.docstore import SimpleDocumentStore
def build_hierarchical_index(text: str):
docs = [Document(text=text)]
node_parser = HierarchicalNodeParser.from_defaults() # standardvärden är grov-till-fin storlekar
nodes = node_parser.get_nodes_from_documents(docs)
docstore = SimpleDocumentStore()
docstore.add_documents(nodes)
storage_context = StorageContext.from_defaults(docstore=docstore)
leaf_nodes = get_leaf_nodes(nodes)
base_index = VectorStoreIndex(leaf_nodes, storage_context=storage_context)
base_retriever = base_index.as_retriever(similarity_top_k=6)
retriever = AutoMergingRetriever(base_retriever, storage_context, verbose=True)
return retriever
Elementbaserad chunkning för PDF:er (Docling)
# pip install docling
# OBS: kvaliteten på PDF-parsning beror på din miljö (tecken, OCR, osv.).
from docling.document_converter import DocumentConverter
from docling.transforms.chunker.hierarchical_chunker import HierarchicalChunker
def chunk_pdf_docling(pdf_path: str) -> list[Chunk]:
converter = DocumentConverter()
doc = converter.convert(pdf_path).document # DoclingDocument
chunker = HierarchicalChunker()
doc_chunks = list(chunker.chunk(doc))
out: list[Chunk] = []
for c in doc_chunks:
# c.text innehåller serialiserad chunkinnehåll; c.meta bär strukturinformation
out.append(Chunk(text=c.text, meta={"strategy": "docling_hierarchical", **dict(c.meta)}))
return out
Kodmedveten chunkning för Python (AST)
import ast
def chunk_python_by_ast(code: str, *, filepath: str = "<memory>") -> list[Chunk]:
tree = ast.parse(code)
out: list[Chunk] = []
for node in tree.body:
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
start = getattr(node, "lineno", None)
end = getattr(node, "end_lineno", None)
if start is None or end is None:
continue
lines = code.splitlines()
snippet = "\n".join(lines[start - 1 : end])
kind = "class" if isinstance(node, ast.ClassDef) else "function"
name = getattr(node, "name", "<anon>")
out.append(
Chunk(
text=snippet,
meta={
"strategy": "python_ast",
"kind": kind,
"name": name,
"filepath": filepath,
"start_line": start,
"end_line": end,
},
)
)
return out
Indexeringsexempel
FAISS (lokalt) — minimal, snabb baslinje
# pip install faiss-cpu numpy
import numpy as np
import faiss
def build_faiss_index(vectors: np.ndarray) -> faiss.Index:
# vectors: shape (N, d), dtype float32
d = vectors.shape[1]
index = faiss.IndexFlatIP(d) # inre produkt; cos sim om vektorerna är normaliserade
faiss.normalize_L2(vectors)
index.add(vectors)
return index
def faiss_search(index: faiss.Index, query_vec: np.ndarray, k: int = 5):
q = query_vec.astype(np.float32).reshape(1, -1)
faiss.normalize_L2(q)
scores, ids = index.search(q, k)
return ids[0].tolist(), scores[0].tolist()
Chroma (lokalt) — enkel persistens för RAG-prototyper
# pip install chromadb
import chromadb
def build_chroma_collection(chunks: list[Chunk], embeddings: np.ndarray, *, path: str = "./chroma_store"):
client = chromadb.PersistentClient(path=path)
col = client.get_or_create_collection(name="docs")
ids = [sha1_id(c.meta.get("strategy", "chunk"), str(i), c.text[:50]) for i, c in enumerate(chunks)]
col.upsert(
ids=ids,
documents=[c.text for c in chunks],
metadatas=[c.meta for c in chunks],
embeddings=embeddings.tolist(),
)
return col
Weaviate (självvärd / moln) — ta med egna vektorer
# pip install weaviate-client
import weaviate
from weaviate.classes.config import Configure
def weaviate_upsert_self_provided(
chunks: list[Chunk],
embeddings: np.ndarray,
):
client = weaviate.connect_to_local() # eller connect_to_weaviate_cloud(...)
try:
collection = client.collections.create(
name="Chunk",
vector_config=Configure.Vectors.self_provided(), # du tillhandahåller vektorer
)
with collection.batch.dynamic() as batch:
for c, v in zip(chunks, embeddings):
batch.add_object(
properties={"text": c.text, **{f"m_{k}": str(vv) for k, vv in c.meta.items()}},
vector=v.tolist(),
)
# Fråga med närvector
q = embeddings[0]
res = collection.query.near_vector(near_vector=q.tolist(), limit=5)
for obj in res.objects:
print(obj.properties.get("text")[:200])
finally:
client.close()
Vissa Källa Dokument
- Patrick Lewis et al., “Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks”, NeurIPS 2020; arXiv:2005.11401.- Vladimir Karpukhin et al., “Dense Passage Retrieval for Open-Domain Question Answering”, EMNLP 2020; arXiv:2004.04906.
- OpenAI API Referens: Skapa embeddings (
/v1/embeddings) — tokengränser (8192 per indata; 300k totalt per förfrågan) ochdimensions-parameter. - OpenAI Batch API-guide + OpenAI API-prisinformationssida (Batch sparar ~50% med 24h omkörning).
- LangChain dokumentation: RecursiveCharacterTextSplitter och splitters integrationsguide (chunkstorlek/överskridande, rekursiv separatorhierarki).
- LlamaIndex dokumentation: HierarchicalNodeParser och AutoMergingRetriever (grov-till-fin noder; hämtningstidssammanfogning).
- Weaviate blogg: “Chunking Strategies to Improve LLM RAG Pipeline Performance” (LLM-baserad chunkningbeskrivning och avvägningar).
- Docling dokumentation: HierarchicalChunker skapar chunkar från dokumentelementstruktur och bifogar rubriker/bildtextmetadata.
- Jimeno Yepes et al., “Financial Report Chunking for Effective Retrieval Augmented Generation” (arXiv:2402.05131v3, 2024). Marti A. Hearst, “TextTiling: Segmenting Text into Multi-Paragraph Subtopic Passages”, Computational Linguistics, 1997 (ACL Anthology: J97-1003).
- FAISS dokumentation / GitHub-lagringsplats: avvägningar mellan sökningstid, kvalitet, minne; valfri GPU-stöd.
- Nandan Thakur et al., “BEIR: A Heterogeneous Benchmark for Zero-shot Evaluation of Information Retrieval Models”, NeurIPS 2021; arXiv:2104.08663.
- RAGAS dokumentation: “Faithfulness”-mått och relaterade RAG-utvärderingsmått.
- NLTK dokumentation:
nltk.tokenize.sent_tokenize- rekommenderad satsdelare baserad på Punkt. - spaCy API-dokumentation:
Sentencizer- regelbaserad satsgränsdetektering utan beroendeanalys.
Andra Nytta Länkar
- Avancerad RAG: LongRAG, Self-RAG och GraphRAG Förklarade
- Vektorlager för RAG Jämförelse
- Återrankning med embeddingsmodeller
- Återrankning av textdokument med Ollama och Qwen3 Embedding-modell - i Go
- Återrankning av textdokument med Ollama och Qwen3 Återrankningsmodell - i Go
- Selvhosting Cognee (LLM-minne): LLM-prestandatest
- LLM-värd: Lokal, självvärd och molninfrastruktur jämförd
- LLM-prestanda: Benchmarks, fläckar och optimering
- Retrieval-Augmented Generation (RAG) Guide: Arkitektur, Implementering och Produktionsguide