AI Engineering Academy · leksjon

Indeksering: embedde og lagre deler

Embed hver del ved hjelp av OpenAI embeddings API, og upsert de resulterende vektorene med metadata i et vektorlager for å bygge en søkbar indeks over dokumentene.

Leksjon 3 av 413 trinn

Indeksering: embedde og lagre deler er en gratis leksjon i AI Engineering Academy på CoddyKit. Dette er leksjon 3 av 4. Du kan lese hele leksjonen gratis nedenfor – og deretter øve praktisk i nettleseren med en innebygd kodeeditor og en AI-veileder som er tilgjengelig døgnet rundt. Den er en del av læringsløpet i AI Engineering Academy, og fremdriften din synkroniseres mellom nettet og CoddyKit-appen. Kurset i AI Engineering Academy inneholder totalt 4 leksjoner.

Indekseringsfasen: oversikt

Etter at dokumentene er lastet inn og delt opp i tekstbiter, kommer De til indekseringsfasen: tekstbiter konverteres til vektorembeddinger og lagres i en søkbar vektordatabase. Dette er det siste frakoblede trinnet før spørsmål kan besvares. Kvaliteten på embeddingene og effektiviteten til lagrings- og indekseringsstrategien avgjør direkte hvor raskt og nøyaktig RAG-systemet er når spørsmål skal besvares.

Generere embedding-vektorer via OpenAI API

Den vanligste fremgangsmåten er å kalle OpenAIs embeddings-API med teksten i tekstbitene. Modellen text-embedding-3-small produserer vektorer med 1536 dimensjoner og koster 0,02 USD per million token – svært rimelig for de fleste arbeidsmengder. Send flere tekster i ett enkelt API-kall (opptil 2048 inndata) for å maksimere gjennomstrømningen. Svaret inneholder én embedding-vektor per inndatatekst i samme rekkefølge.

from openai import OpenAI

client = OpenAI()

def embed_batch(texts, model='text-embedding-3-small'):
    response = client.embeddings.create(
        model=model,
        input=texts  # up to 2048 texts per call
    )
    return [item.embedding for item in response.data]

# Embed one batch of 100 chunk texts
texts = [chunk['text'] for chunk in chunks[:100]]
vectors = embed_batch(texts)
print(f'Embedding dimension: {len(vectors[0])}')
print(f'Embedded {len(vectors)} chunks')

Batching for effektivitet

Når De indekserer tusenvis av tekstbiter, er effektivitet viktig. Behandle tekstbitene i batcher på 100–500 for å balansere gjennomstrømning og minnebruk. Spor posisjonen, slik at De kan fortsette etter en feil uten å lage embedding på nytt for tekstbiter som allerede er behandlet. Logg fremdriften regelmessig. For 100 000 tekstbiter, med 500 per batch, gjør De 200 API-kall – dette blir vanligvis ferdig på noen få minutter.

def embed_all_chunks(chunks, batch_size=200):
    embedded = []
    total = len(chunks)
    for i in range(0, total, batch_size):
        batch = chunks[i:i+batch_size]
        texts = [c['text'] for c in batch]
        vectors = embed_batch(texts)
        for chunk, vector in zip(batch, vectors):
            embedded.append({
                **chunk,
                'embedding': vector
            })
        if (i // batch_size) % 10 == 0:
            print(f'Progress: {min(i+batch_size, total)}/{total}')
    return embedded

Håndtering av hastighetsbegrensninger under indeksering

OpenAIs embeddings-API har hastighetsbegrensninger målt i token per minutt (TPM). Store indekseringsjobber når disse grensene og mottar RateLimitError. Implementer eksponentiell backoff med jitter: Når det oppstår en feil på grunn av hastighetsbegrensningen, venter De et kort, tilfeldig tidsintervall før De prøver på nytt, og dobler ventetiden ved hver påfølgende feil. Dette sprer nye forsøk utover og hindrer alle parallelle arbeidere i å overbelaste API-et samtidig.

import time
import random
from openai import RateLimitError

def embed_batch_with_retry(texts, max_retries=5):
    for attempt in range(max_retries):
        try:
            return embed_batch(texts)
        except RateLimitError:
            if attempt == max_retries - 1:
                raise
            wait = (2 ** attempt) + random.uniform(0, 1)
            print(f'Rate limited. Waiting {wait:.1f}s...')
            time.sleep(wait)
    return []

Skrive vektorer til Pinecone

Etter at embeddingene er generert, setter du dem inn i vektorlageret med upsert. Upserting betyr å sette inn nye vektorer eller oppdatere eksisterende vektorer hvis samme ID allerede finnes – idempotent av hensikt. I Pinecone inneholder hver upsertede post vektor-ID-en, embeddingverdiene og en metadatadictionary med felter du vil filtrere på eller vise senere. Utfør upsert i grupper på opptil 100 poster per kall for optimal gjennomstrømming.

import pinecone

pc = pinecone.Pinecone(api_key='YOUR_KEY')
index = pc.Index('rag-index')

def upsert_to_pinecone(embedded_chunks, batch_size=100):
    for i in range(0, len(embedded_chunks), batch_size):
        batch = embedded_chunks[i:i+batch_size]
        vectors = [
            (
                chunk['id'],
                chunk['embedding'],
                {
                    'text': chunk['text'],
                    'source': chunk['metadata']['source'],
                    'page': chunk['metadata'].get('page', 0)
                }
            )
            for chunk in batch
        ]
        index.upsert(vectors=vectors)
        print(f'Upserted {min(i+batch_size, len(embedded_chunks))}/{len(embedded_chunks)}')

Lagring i pgvector

Med pgvector setter du embeddingene direkte inn i en PostgreSQL-tabell ved hjelp av standard SQL. Datatypen vector godtar en Python-liste med flyttall serialisert som en streng. Når alle radene er satt inn, oppretter du en HNSW-indeks for raske omtrentlige nærmeste-nabo-spørringer. Indeksering av en eksisterende tabell med millioner av rader kan ta flere minutter, så bygg indeksen etter masseinnsettingen i stedet for før.

import psycopg2
from psycopg2.extras import execute_values

def upsert_to_pgvector(conn, embedded_chunks):
    with conn.cursor() as cur:
        records = [
            (
                chunk['id'],
                chunk['text'],
                chunk['metadata']['source'],
                chunk['metadata'].get('page', 0),
                chunk['embedding']   # list of floats
            )
            for chunk in embedded_chunks
        ]
        execute_values(cur, '''
            INSERT INTO document_chunks (id, text, source, page, embedding)
            VALUES %s
            ON CONFLICT (id) DO UPDATE
            SET text = EXCLUDED.text, embedding = EXCLUDED.embedding
        ''', records)
    conn.commit()

Bygge HNSW-indeksen

HNSW (Hierarchical Navigable Small World) er indek 유형en som muliggjør raskt søk etter omtrentlige nærmeste naboer. I motsetning til brute-force-søk, som sammenligner spørringsvektoren med hver eneste lagrede vektor, bygger HNSW en flerlags grafstruktur som begrenser søkeområdet. Parameteren m styrer hvor mange forbindelser hver node har (høyere verdi = bedre gjenfinning, men mer minne), mens ef_construction styrer indekskvaliteten under byggingen.

-- Build HNSW index after bulk insertion
CREATE INDEX CONCURRENTLY ON document_chunks
USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 64);

-- Set ef_search at query time to trade recall vs speed
SET hnsw.ef_search = 100;

-- Verify index was created
SELECT indexname, indexdef
FROM pg_indexes
WHERE tablename = 'document_chunks';

Utforming av metadataskjema

Metadata som lagres sammen med hver vektor, muliggjør kraftig filtrert gjenfinning. Utform metadataskjemaet før indeksering – hvis du legger til nye felter senere, må du indeksere på nytt. Ta med felter du skal filtrere på (avdeling, doc_type, datointervall), felter du skal vise i kildehenvisninger (tittel, side, forfatter) og felter som er nyttige ved feilsøking (chunk_index, total_chunks, indexed_at). Hold metadataverdiene enkle: strenger, tall og boolske verdier indekseres og filtreres effektivt; nøstede objekter gjør ikke det.

# Well-designed metadata schema
METADATA_SCHEMA = {
    # For filtering at retrieval time
    'department': 'HR',         # string
    'doc_type': 'policy',       # string
    'year': 2025,               # integer
    'is_active': True,          # boolean

    # For display in citations
    'title': 'Employee Handbook 2025',
    'author': 'HR Team',
    'page': 12,
    'source': 's3://docs/handbook_2025.pdf',

    # For debugging and updates
    'chunk_index': 3,
    'total_chunks': 24,
    'indexed_at': '2025-09-01T10:00:00Z'
}

Kontrollpunkter for lange indekseringsjobber

Indeksering av et stort korpus kan ta flere timer. Et krasj underveis gjør at all fremdrift går tapt. Implementer en kontrollpunktfil som registrerer hvilke deler som er indeksert. Ved omstart hopper du over deler som allerede er indeksert, og fortsetter der du slapp. Dette gjør indekseringsjobben idempotent og trygg å gjenoppta. Lagre kontrollpunktet som et sett med behandlede del-ID-er i en JSON-fil eller en databasetabell.

import json
from pathlib import Path

CHECKPOINT_FILE = '/tmp/index_checkpoint.json'

def load_checkpoint():
    if Path(CHECKPOINT_FILE).exists():
        return set(json.loads(Path(CHECKPOINT_FILE).read_text()))
    return set()

def save_checkpoint(indexed_ids):
    Path(CHECKPOINT_FILE).write_text(json.dumps(list(indexed_ids)))

def index_with_checkpoint(chunks, index):
    done = load_checkpoint()
    remaining = [c for c in chunks if c['id'] not in done]
    print(f'Resuming: {len(done)} done, {len(remaining)} remaining')
    for chunk in remaining:
        upsert_to_pinecone([chunk], index)
        done.add(chunk['id'])
        save_checkpoint(done)

Kontrollere at indeksen er komplett

Etter indekseringen må du kontrollere at alle delene kom med i vektorlageret. Sammenlign antallet deler som splitter-funksjonen produserte, med antallet vektorer som indeksen rapporterer. Spør indeksen med teksten fra et kjent dokument, og bekreft at det forventede resultatet vises blant de fem øverste. Kjør noen kjente spørringer fra det gyldige testsettet, og kontroller at presisjonen er på forventet nivå. Anta aldri at indeksen er komplett uten å kontrollere det.

def verify_index(index, chunks, sample_size=10):
    index_stats = index.describe_index_stats()
    total_vectors = index_stats.total_vector_count
    expected = len(chunks)
    print(f'Index vectors: {total_vectors}, Expected: {expected}')
    if total_vectors != expected:
        print('WARNING: mismatch — some chunks may not have been indexed')

    # Spot-check retrieval
    import random
    sample = random.sample(chunks, sample_size)
    for chunk in sample:
        vec = embed_batch([chunk['text']])[0]
        results = index.query(vector=vec, top_k=1, include_metadata=True)
        top_id = results.matches[0].id if results.matches else None
        if top_id != chunk['id']:
            print(f'WARNING: expected {chunk["id"]}, got {top_id}')

Lokale alternativer for embedding

For personvernfølsomme data som ikke kan forlate infrastrukturen, kan du bruke lokalt driftede embedding-modeller. Biblioteket sentence-transformers tilbyr modeller av høy kvalitet, som all-MiniLM-L6-v2 (384 dimensjoner, 22 MB, svært rask) og bge-large-en-v1.5 (1024 dimensjoner, bedre kvalitet). Kjør dem på CPU for moderate arbeidsmengder eller på GPU for store indekseringsjobber. Lokale modeller eliminerer API-kostnader og datautgående trafikk, men krever at du administrerer modellfiler og beregningsressurser.

from sentence_transformers import SentenceTransformer

# Load once at startup
model = SentenceTransformer('sentence-transformers/all-MiniLM-L6-v2')

def embed_locally(texts, batch_size=64):
    # encode() handles batching internally
    embeddings = model.encode(
        texts,
        batch_size=batch_size,
        show_progress_bar=True,
        convert_to_numpy=True
    )
    return embeddings.tolist()  # convert numpy array to Python list

vectors = embed_locally([c['text'] for c in chunks])

Hurtigsjekk

Test forståelsen Deres av konseptene innen AI Engineering fra denne leksjonen.

Oppsummering av leksjonen

I denne leksjonen lærte De å: generere embedding-er i grupper med OpenAI API-et og håndtere hastighetsbegrensninger med eksponentiell tilbakekobling, sette vektorer inn med upsert i Pinecone og pgvector med metadata, bygge HNSW-indekser for raske omtrentlige nærmeste-nabo-søk, og følge beste praksis for produksjon, blant annet gjenopptakelse basert på kontrollpunkter, utforming av metadataskjema og kontroll av at indeksen er komplett. Neste steg er å bygge spørringsrørledningen som henter deler og genererer forankrede svar.

Gratis å komme i gang

Lær deg Python med en AI-veileder – gratis

Skriv og kjør ekte kode i nettleseren, få umiddelbar hjelp fra en AI-veileder som er tilgjengelig døgnet rundt, og fortsett der du slapp – på nettet eller i appen.

Kurs
30
Leksjoner
120

Ofte stilte spørsmål

Er leksjonen «Indeksering: embedde og lagre deler» gratis?

Ja – hele teksten i «Indeksering: embedde og lagre deler» er gratis å lese her på nettet. For å øve interaktivt med en innebygd kodeeditor og en AI-veileder som er tilgjengelig døgnet rundt, og for å låse opp resten av AI Engineering Academy-kurset, kan du oppgradere til CoddyKit PRO. Kurset i AI Engineering Academy inneholder totalt 4 leksjoner.

Hva lærer jeg i «Indeksering: embedde og lagre deler»?

Embed hver del ved hjelp av OpenAI embeddings API, og upsert de resulterende vektorene med metadata i et vektorlager for å bygge en søkbar indeks over dokumentene. Du øver på AI Engineering Academy med praktisk kode som du kjører direkte i nettleseren, mens en AI-veileder som er tilgjengelig døgnet rundt, svarer på spørsmålene dine mens du jobber deg gjennom leksjonen.

Trenger jeg erfaring for å begynne med AI Engineering Academy?

Ingen tidligere erfaring er nødvendig. AI Engineering Academy på CoddyKit er lagt opp for både nybegynnere og viderekomne, så De kan begynne her eller helt fra start og lære i Deres eget tempo. Dette er leksjon 3 av 4.

Hvor lang tid tar leksjonen «Indeksering: embedde og lagre deler»?

De fleste CoddyKit-leksjoner tar omtrent 5–10 minutter. Hver leksjon er kort og interaktiv, slik at De gjør jevne fremskritt og kan fortsette akkurat der De slapp – både på nettet og i appen.

Kan jeg skrive og kjøre kode i denne AI Engineering Academy-leksjonen?

Ja. Alle AI Engineering Academy-leksjoner har en innebygd kodeeditor, slik at De kan skrive og kjøre ekte kode direkte i nettleseren og få umiddelbar tilbakemelding fra AI – uten lokal konfigurering.

Alle leksjonene i dette kurset

  1. Laste inn dokumenter og hente ut tekst
  2. Strategier for oppdeling: fast størrelse, setninger eller rekursivt
  3. Indeksering: embedde og lagre deler
  4. Spørre, hente ut og generere
← Tilbake til AI Engineering Academy