AI Engineering Academy · Lektion

Strategier til opdeling: Fast størrelse, sætninger og rekursion

Implementér og sammenlign tekstsplittere med fast størrelse, ved sætningers grænser og med rekursiv opdeling, og forstå, hvordan bidstørrelse og overlap påvirker kvaliteten af hentningen.

Lektion 2 af 413 trin

Strategier til opdeling: Fast størrelse, sætninger og rekursion er en gratis AI Engineering Academy-lektion på CoddyKit. Dette er lektion 2 af 4. Du kan læse hele lektionen gratis nedenfor — og derefter øve dig praktisk i browseren med en indbygget kodeeditor og en AI-vejleder, der er tilgængelig døgnet rundt. Den er en del af læringsforløbet i AI Engineering Academy, og dine fremskridt synkroniseres på tværs af nettet og CoddyKit-appen. AI Engineering Academy-kurset indeholder 4 lektioner i alt.

Hvorfor kvaliteten af tekststykker er vigtig

Opdeling i tekststykker er processen med at dele indlæste dokumenter op i mindre dele, der kan være inden for LLM'ens kontekstvindue og indekseres og hentes individuelt. Måden, du opdeler på, afgør kvaliteten af hentningen mere end næsten nogen anden faktor. Hvis et tekststykke deler et svar mellem to dele, er ingen af delene alene tilstrækkelig til at besvare spørgsmålet. Et tekststykke, der blander to urelaterede emner, bliver hentet ved spørgsmål om begge emner, men er ikke nyttigt til nogen af dem.

Opdeling i tekststykker med fast størrelse

Opdeling i tekststykker med fast størrelse deler tekst op i tekststykker med præcis N tegn eller N tokens, uanset grænserne mellem sætninger eller afsnit. Det er den enkleste strategi og hurtig at implementere. Den største ulempe er, at den ofte skærer sætninger midt over, så tekststykker begynder eller slutter midt i en tanke. Det er acceptabelt for tæt, ensartet formateret tekst som eksporterede databasedata, men giver dårlig kvalitet ved hentning af fortællende prosa eller teknisk dokumentation.

def fixed_size_chunks(text, chunk_size=500, overlap=50):
    '''Split text into fixed-size character chunks with overlap'''
    chunks = []
    start = 0
    while start < len(text):
        end = start + chunk_size
        chunk = text[start:end]
        chunks.append(chunk)
        start += chunk_size - overlap  # overlap keeps context at boundaries
    return chunks

example = 'This is a long document. ' * 100
chunks = fixed_size_chunks(example, chunk_size=200, overlap=20)
print(f'Produced {len(chunks)} chunks, first: {chunks[0][:80]}...')

Tilføjelse af overlap til tekststykker med fast størrelse

Den vigtigste forbedring af opdeling i tekststykker med fast størrelse er overlap: hvert tekststykke deler N tegn med det foregående tekststykke. Det sikrer, at oplysninger tæt på grænsen mellem tekststykker optræder i begge tilstødende tekststykker. Med et overlap på 50-100 tokens bliver en sætning, der går på tværs af en grænse, gengivet fuldt ud i mindst ét af de to tekststykker. Mere overlap giver bedre dækning, men også et større indeks og overflødigt indhold i resultaterne fra hentningen.

# Overlap example with a sentence at the boundary
text = 'A B C D E F G H I J'
chunk_size = 6  # characters
overlap = 2

i = 0
while i < len(text):
    print(repr(text[i:i+chunk_size]))
    i += chunk_size - overlap

# Output shows overlapping content:
# 'A B C D'
# 'C D E F'
# 'E F G H'
# Each adjacent pair shares 2 chars

Opdeling ved sætningsgrænser

Opdeling ved sætningsgrænser bruger NLP-biblioteker som nltk eller spacy til at identificere slutningen af sætninger, før teksten deles op. Det garanterer, at ingen sætning skæres midt over. Du samler sætninger, indtil den næste ville overskride målestørrelsen, og starter derefter et nyt tekststykke. Resultatet er tekststykker, der altid indeholder hele tanker, hvilket giver markant bedre kvalitet af indlejringer end opdeling efter et fast antal tegn.

import nltk
nltk.download('punkt', quiet=True)

def sentence_chunks(text, max_tokens=300):
    sentences = nltk.sent_tokenize(text)
    chunks = []
    current = []
    current_len = 0

    for sent in sentences:
        sent_tokens = len(sent.split())
        if current_len + sent_tokens > max_tokens and current:
            chunks.append(' '.join(current))
            current = []
            current_len = 0
        current.append(sent)
        current_len += sent_tokens

    if current:
        chunks.append(' '.join(current))
    return chunks

Rekursiv opdeling af tekst efter tegn

Rekursiv opdeling efter tegn er den mest udbredte strategi i RAG-systemer til produktion. Den prøver et hierarki af separatorer i rækkefølge: først afsnitsskift (\n\n), derefter linjeskift (\n), så punktummer ved sætningsafslutninger, derefter mellemrum og til sidst individuelle tegn. Den deler ved den største meningsfulde grænse, der holder tekststykket under målestørrelsen, hvilket resulterer i tekststykker, der respekterer dokumentstrukturen mest muligt.

from langchain_text_splitters import RecursiveCharacterTextSplitter

splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,       # target size in characters
    chunk_overlap=200,     # overlap between consecutive chunks
    separators=['\n\n', '\n', '. ', '! ', '? ', ' ', ''],
    length_function=len
)

with open('document.txt') as f:
    text = f.read()

chunks = splitter.split_text(text)
print(f'Split into {len(chunks)} chunks')
for i, chunk in enumerate(chunks[:3]):
    print(f'Chunk {i}: {len(chunk)} chars — {chunk[:60]}...')

Tokenbevidst opdeling

Antallet af tegn er en upræcis tilnærmelse til antallet af tokens. Et tekststykke på 1000 tegn kan være på 200 eller 400 tokens afhængigt af ordlængde og sprog. Hvis du vil have præcis kontrol, skal du opdele efter tokenantal ved hjælp af tiktoken. Det er vigtigt, når tekststykker skal passe til modellens kontekstvindue, og når omkostninger skal estimeres præcist. LangChains TokenTextSplitter fungerer som en wrapper omkring tiktoken til tokenbevidst opdeling.

from langchain_text_splitters import TokenTextSplitter
import tiktoken

# Split by actual token count, not characters
splitter = TokenTextSplitter(
    encoding_name='cl100k_base',  # GPT-4 tokenizer
    chunk_size=256,               # target tokens per chunk
    chunk_overlap=32              # overlap in tokens
)

chunks = splitter.split_text(text)

# Verify token count
enc = tiktoken.get_encoding('cl100k_base')
for chunk in chunks[:3]:
    tokens = len(enc.encode(chunk))
    print(f'Chunk: {tokens} tokens')

Valg af den rigtige størrelse på tekststykker

Størrelsen på tekststykker er en kritisk hyperparameter. Små tekststykker (100-200 tokens) er præcise — de indeholder tæt fokuserede oplysninger, som indlejres godt. Men de mister den omgivende kontekst, så LLM'en måske ikke har nok information til at svare. Store tekststykker (500-1000 tokens) giver mere kontekst, men deres indlejringer udgør et gennemsnit over et bredere emne, hvilket gør dem sværere at hente ved specifikke forespørgsler. De fleste produktionssystemer bruger 256-512 tokens med empirisk test på deres data.

Tilføjelse af kontekst til tekststykker

En effektiv forbedring er kontekstuelle overskrifter til tekststykker: indsæt dokumenttitlen og sektionsoverskriften foran teksten i hvert tekststykke, før det indlejres. På den måde indeholder indlejringen ikke kun tekststykkets indhold, men også oplysninger om, hvor det stammer fra i dokumentet. Et tekststykke med teksten Fordele og feriepolitik: Medarbejdere optjener 15 dage om året... hentes langt mere præcist ved spørgsmål om feriepolitik end den samme tekst uden overskriften.

def create_contextual_chunks(doc, splitter):
    title = doc['metadata'].get('title', '')
    section = doc['metadata'].get('section', '')
    text = doc['text']

    raw_chunks = splitter.split_text(text)
    contextual_chunks = []
    for chunk in raw_chunks:
        # Prepend document context to each chunk
        context_header = f'{title}\n{section}\n\n' if title else ''
        contextual_chunks.append({
            'text': context_header + chunk,
            'metadata': doc['metadata']
        })
    return contextual_chunks

Sammenligning af strategier på dine data

Ingen strategi for opdeling i tekststykker er universelt bedst. Lav en hurtig evaluering: Tag 20 spørgsmål, som du kender svarene på, kør hentning med hver strategi, og mål, hvor ofte det korrekte tekststykke ligger blandt de 5 øverste resultater (træfprocent@5). Det tager en time at sætte op og sparer dig for ugers gætterier. Du vil ofte opdage, at netop dit dokumentformat (tæt juridisk prosa over for strukturerede tekniske dokumenter) klart favoriserer én strategi frem for de andre.

def evaluate_chunking_strategy(questions_and_answers, retriever):
    hits = 0
    for qa in questions_and_answers:
        results = retriever.retrieve(qa['question'], top_k=5)
        result_texts = [r['text'] for r in results]
        # Check if answer text appears in any retrieved chunk
        if any(qa['answer'] in text for text in result_texts):
            hits += 1
    hit_rate = hits / len(questions_and_answers)
    print(f'Hit rate@5: {hit_rate:.1%} ({hits}/{len(questions_and_answers)})')
    return hit_rate

Håndtering af særlige dokumenttyper

Nogle dokumenttyper kræver opdeling til særlige formål. Kodefiler bør opdeles efter funktions- eller klassegrænser, ikke efter antal tegn. Markdown-filer bør opdeles ved overskriftsgrænser, så hvert tekststykke svarer til én sektion. Tabeller bør bevares intakte som ét tekststykke (hvis en tabel deles midt over, bliver indholdet uforståeligt). Planlæg din strategi for opdeling ud fra dokumenttyperne i dit korpus i stedet for at anvende én opdeling, der passer til alt.

from langchain_text_splitters import MarkdownHeaderTextSplitter

# Split Markdown by header hierarchy
md_splitter = MarkdownHeaderTextSplitter(
    headers_to_split_on=[
        ('#', 'h1'),
        ('##', 'h2'),
        ('###', 'h3')
    ]
)

with open('documentation.md') as f:
    md_text = f.read()

# Each chunk gets metadata from its heading hierarchy
chunks = md_splitter.split_text(md_text)
for chunk in chunks[:3]:
    print('Section:', chunk.metadata)
    print('Text:', chunk.page_content[:80])
    print()

Sporing af tekststykkers ophav

Alle tekststykker har brug for et stabilt, entydigt ID, der er afledt af kildedokumentet og placeringen. Brug dette ID til at opdatere bestemte tekststykker, når dokumenter ændres, uden at genindeksere hele korpuset. En deterministisk hash af kildestien plus tekststykkets indeks fungerer godt. Registrér også tekststykkets placering i metadataene (tekststykke 3 af 12 fra dokument X) — det gør det lettere at samle hele sektioner igen, når flere tilstødende tekststykker hentes sammen.

import hashlib

def assign_chunk_ids(chunks, source_path):
    for i, chunk in enumerate(chunks):
        key = f'{source_path}::chunk_{i}'
        chunk_id = hashlib.sha256(key.encode()).hexdigest()[:16]
        chunk['id'] = chunk_id
        chunk['metadata']['chunk_index'] = i
        chunk['metadata']['total_chunks'] = len(chunks)
    return chunks

# IDs are stable across re-runs if source and position match
chunks = sentence_chunks(text)
chunks = [{'text': c, 'metadata': {}} for c in chunks]
assign_chunk_ids(chunks, 'docs/policy_v3.pdf')

Hurtigt tjek

Test din forståelse af begreberne inden for AI Engineering fra denne lektion.

Opsummering af lektionen

I denne lektion har du lært, at opdeling i tekststykker med fast størrelse er enkel, men skærer sætninger midt over, at opdeling ved sætningsgrænser bevarer hele tanker, at rekursiv opdeling efter tegn respekterer dokumentstrukturen og er det mest almindelige valg i produktion, samt om avancerede teknikker, herunder tokenbevidst opdeling, kontekstuelle overskrifter, opdelere til særlige formål for Markdown og kode samt stabile ID'er til tekststykker ved trinvise opdateringer. Næste gang indlejrer vi disse tekststykker og gemmer dem i en vektordatabase.

Gratis at komme i gang

Lær Python med en AI-underviser — gratis

Skriv og kør rigtig kode i din browser, få øjeblikkelig hjælp fra en AI-underviser døgnet rundt, og fortsæt, hvor du slap, på web eller i appen.

Kurser
30
Lektioner
120

Ofte stillede spørgsmål

Er lektionen “Strategier til opdeling: Fast størrelse, sætninger og rekursion” gratis?

Ja — hele teksten til “Strategier til opdeling: Fast størrelse, sætninger og rekursion” kan læses gratis her på nettet. Hvis du vil øve dig interaktivt med en indbygget kodeeditor og en AI-vejleder døgnet rundt og få adgang til resten af AI Engineering Academy-kurset, skal du opgradere til CoddyKit PRO. AI Engineering Academy-kurset indeholder 4 lektioner i alt.

Hvad lærer jeg i “Strategier til opdeling: Fast størrelse, sætninger og rekursion”?

Implementér og sammenlign tekstsplittere med fast størrelse, ved sætningers grænser og med rekursiv opdeling, og forstå, hvordan bidstørrelse og overlap påvirker kvaliteten af hentningen. Du øver dig i AI Engineering Academy med praktisk kode, som du kører direkte i browseren, og en AI-vejleder døgnet rundt besvarer dine spørgsmål, mens du arbejder dig gennem lektionen.

Skal jeg have erfaring for at begynde på AI Engineering Academy?

Der kræves ingen tidligere erfaring. AI Engineering Academy på CoddyKit er tilrettelagt for både begyndere og øvede, så du kan starte her eller fra begyndelsen og lære i dit eget tempo. Dette er lektion 2 af 4.

Hvor lang tid tager lektionen “Strategier til opdeling: Fast størrelse, sætninger og rekursion”?

De fleste CoddyKit-lektioner tager cirka 5–10 minutter. Hver lektion er kort og interaktiv, så du gør løbende fremskridt og kan fortsætte, hvor du slap – på både web og app.

Kan jeg skrive og køre kode i denne AI Engineering Academy-lektion?

Ja. Alle AI Engineering Academy-lektioner har en indbygget kodeeditor, så du kan skrive og køre rigtig kode direkte i din browser og få øjeblikkelig feedback fra AI – uden lokal opsætning.

Alle lektioner i dette kursus

  1. Indlæsning af dokumenter og tekstudtræk
  2. Strategier til opdeling: Fast størrelse, sætninger og rekursion
  3. Indeksering: Embedding og lagring af bidder
  4. Forespørg, hent, og generér
← Tilbage til AI Engineering Academy