Embedding - i praksis
Dette er anden del af introduktionen til embedding og vektorer. Hvis du ikke allerede har læst den første artikel, "Embedding og vektorer - for begyndere", så start dér. Her ser vi på, hvordan du helt konkret kan bruge embedding til at bygge et lokalt søgesystem — for eksempel til at søge i en PDF-manual eller en bog.
Forestil dig, at du har en PDF med en teknisk manual til et gammelt produkt, og gerne vil lave et søgesystem, hvor brugere kan stille spørgsmål til manualen. Målet er, at systemet skal forstå betydningen af spørgsmålene og finde de relevante dele af manualen, også selvom brugerne ikke bruger præcis de samme ord som står i teksten.
Brug eksisterende chat-bots
Den nemmeste løsning er selvfølgelig at bruge et eksisterende chat-bot system som fx ChatGPT, Gemini eller Claude som backend. De har allerede indbygget funktionalitet til at håndtere embedding og kan bruges direkte til at søge i dokumenter. Det kan ovenikøbet gøres helt gratis og uden at skulle forstå eller kode noget som helst.
Men idéen med denne artikel er at vise, hvordan du kan lave dit eget system, hvor du har fuld kontrol over dataene og kan tilpasse alt præcist til dine behov. Det kræver lidt mere hands-on, men som min gamle elektroniklærer sagde: "Hvis du ikke kan lave det selv, så forstår du det ikke ordentligt." Det var også ham, der mente, at man ikke kunne komme gennem livet uden at have brændt sig godt og grundigt på en loddekolbe og mindst én gang lugtet til en kortsluttet transistor.
Så væk med de nemme løsninger — lad os se, hvordan du kan bygge dit eget søgesystem med embedding.
Din helt egen AI agent
Lad os sige, at du gerne vil lave et lokalt søgesystem, hvor du kan søge i en gammel PDF-manual. Jeg lærte i sin tid at programmere på en HP-34C lommeregner og har faktisk stadig en eller to liggende.

Hvis jeg skulle puste liv i den igen (og det kunne være sjovt), ville jeg starte med at finde manualen. Den ligger nemt tilgængelig på nettet som en 2-300 siders PDF. Og så ville jeg hurtigt få lyst til at kunne søge i den og finde svar på spørgsmål som:
"Hvordan virker Solve-funktionen?", "Hvilke statistiske funktioner har den?" eller "Hvordan laver man betingelser og forgreninger?"
Man kunne næsten kalde det for en lille AI agent, som kan svare på alt om HP-34C — men det er nok mere retvisende at sige, at det er en simpel AI app, der bruger LLM og embeddings til at forstå og finde svar. En “rigtig” AI agent ville typisk også kunne handle, tage egne beslutninger og lære af sin omverden - eksempelvis ved at lade LLM få adgang til en HP-34C emulator, så den kan afprøve funktionerne i praksis.
Flowet
Typisk følger du et flow som dette:
-
Konverter tekstkilden.
Det starter ofte med en PDF, som du laver om til ren tekst. Er PDF’en kun billeder, må du bruge OCR. -
Del teksten op i mindre bidder.
Du splitter teksten i små chunks, fx på 500 tegn med lidt overlap. Det gør det lettere at gemme, indeksere og søge præcist. -
Lav embeddings af alle chunks.
Hver chunk bliver omdannet til en vektor ved hjælp af en sprogmodel, så du fanger meningen — ikke kun ordene. (Se første artikel for grundlæggende teori). -
Få en LLM til at opsummere brugerens spørgsmål.
Hvis en bruger stiller et langt eller rodet spørgsmål, beder du en LLM koge det ned til en kort, søgevenlig engelsk sætning. Det gør søgningen skarpere, fordi dine chunks også er på engelsk. -
Opret embedding af spørgsmålet.
Den korte søgetekst bliver også lavet til en embedding. Så kan du måle, hvilke chunks der ligger tættest på i betydning. -
Søg i dine chunks.
Du laver en similarity search i din vektor-database og finder de chunks, der matcher bedst. -
Brug de mest relevante chunks som kontekst.
Du sender de relevante tekstuddrag sammen med det originale spørgsmål til LLM’en, som kan formulere et præcist svar. Den bliver også bedt om at svare på samme sprog som spørgsmålet. -
Gem samtalehistorikken.
Så kan brugeren stille opfølgende spørgsmål, og LLM’en husker stadig, hvad I har snakket om.
Her er et eksempel på, hvordan det kan se ud i praksis, når du kører det i et PowerShell-vindue med Python-koden bagved. Loggen viser tydeligt hvordan:
- spørgsmålet bliver kogt ned til en kort søgetekst,
- systemet finder de mest relevante chunks,
- og LLM’en bruger dem til at generere et svar, baseret både på konteksten og det oprindelige spørgsmål.
(.venv) PS C:\git\ai-artikler\embeddings> py .\06_summarize_search_generate.py
📚 Loading embeddings and text snippets...
✅ Loaded 897 chunks.
🔍 What would you like to ask? (or 'exit'): jeg kunne godt tænkte mig at vide noget om betingelser
📝 Optimized English summary for search: "HP-34C calculator conditional instructions"
*(Short, technical, and optimized for searches about HP-34C conditionals.)*
- chunk_340.txt (similarity: 0.754)
✂️ ') End of Program. Conditionals and Conditional Branches Often there are times when you want a program to make a decisi
on. The conditional operations on your HP-34C keyboard are program instruc- tions ...'
- chunk_046.txt (similarity: 0.732)
✂️ 'Section 1 Meet the HP-34C Congratulations! Your HP-34C Programmable Scientific Calculator with Continuous Memory is a
truly unique and versatile calculating instrument. Using the Hewlett-Packard RPN l...'
- chunk_139.txt (similarity: 0.732)
✂️ 'calculator, you may be sure that the calculator will execute your commands faithfully, without the slips you might mak
e if you had to manually press the keys over and over again. The calculator perfor...'
- chunk_005.txt (similarity: 0.725)
✂️ 'HEWLETT |’!fi,| PACKARD The HP-34C Programmable Scientific Calculator Owner’s Handbook and Programming Guide May 1979
00034-90001 Printed in U.S.A. © Hewlett-Packard Company 1979...'
- chunk_890.txt (similarity: 0.691)
✂️ 'HP-34C Owner’s Handbook and Programming Guide Addendum This addendum contains updating information for the HP-34C Owne
r’'s Handbook and Programming Guide, manual part number 00034-90001, printed May 1...'
💬 Answer from LLM:
Her er et svar på dit spørgsmål om betingelser på HP-34C i dansk:
På HP-34C kan du bruge betingede kommandoer til at lave beslutninger i dine programmer. Manualen nævner følgende vigtige funktioner:
1. **Betingede tests**:
- Lommeregneren kan sammenligne værdier i X- og Y-registrene.
- F.eks. "x≤y?" tester om X er mindre end eller lig med Y.
2. **Betingede forgreninger**:
- Disse gør det muligt at springe til forskellige dele af programmet baseret på testresultater.
3. **Programmering**:
- Du kan bruge op til 12 etiketter til at markere forskellige programdele.
- Logikken i programmeringssproget er designet til at være let at forstå.
Hvis du har brug for mere specifik information om, hvordan du implementerer disse betingelser i et konkret program, kan jeg præcisere yderligere baseret på manualen.
🔍 What would you like to ask? (or 'exit'): exit
(.venv) PS C:\git\ai-artikler\embeddings>
Alt er pinnet ret grundigt ud her med logs, og det er helt bevidst — den bagvedliggende Python-kode skal kunne bruges i undervisningen på forskellige uddannelser.
Men koden er egentlig ikke det vigtigste. Det centrale er, at du forstår flowet og hvordan embedding fungerer. Hvis du ikke er interesseret i selve koden eller implementeringen, kan du roligt stoppe her 😊
Værktøjer og kode
Hele flowet ovenfor kan implementeres i stort set hvilket som helst sprog. Jeg har valgt Python, fordi der findes mange gode biblioteker til at arbejde med embedding og vektorer. Eller måske rettere: vi har valgt Python, for koden er i høj grad skrevet af Gemini CLI og ChatGPT (alle de emojis i eksemplet er et ret tydeligt fingeraftryk på, at det er en LLM, der har været i gang). Jeg har bare hjulpet den lidt på vej med at definere flowet og tilføjet lidt pædagogik.
Flowet benytter en række biblioteker, som skjuler de tunge tekniske detaljer. Fx genereres vektorerne ved hjælp af SentenceTransformer
, der bygger på Hugging Face’s transformers
. Det gør det let at lave embeddings af tekst og giver adgang til mange forskellige sprogmodeller — og det er gratis at bruge. Alle vektorer gemmes lokalt som JSON-filer, så det er nemt at håndtere og søge i dem. Har du brug for mere performance eller avancerede funktioner, kan det udvides til at bruge en database som Pinecone eller PostgreSQL via eksempelvis Supabase.
Den eneste eksterne komponent i mit eksempel er OpenRouter, som bruges til at generere svar fra en LLM. Det er en nem måde at få adgang til kraftige sprogmodeller uden selv at hoste dem — og der findes masser af gratis modeller. Koden her bruger fx deepseek
. Du kunne også vælge at holde alt lokalt, fx med Llama eller en anden model, men det kræver lidt mere opsætning og ressourcer.
Koden er opdelt i flere filer, som du kan finde på GitHub.
Warning
Koden er hverken perfekt eller færdig. Den er tænkt som et eksempel på selve flowet og kan helt sikkert forbedres. Den er ikke optimeret til performance eller skalerbarhed — der er masser af muligheder for at gøre den bedre.
-
01_extract_txt_from_pdf.py
Indeholder koden til at konvertere en PDF til ren tekst. Den brugerpdfplumber
til at udtrække tekst fra PDF’en. Det er ikke perfekt, men fungerer fint på de fleste PDF’er. Gemmer den udtrukne tekst i en fil i samme mappe. -
02_split_txt_to_chunks.py
Opdeler teksten i mindre chunks på ca. 500 tegn med lidt overlap. Det gør det nemmere at håndtere og søge i. Brugerlangchain.text_splitter
og gemmer filerne i enchunks/
-mappe. -
03_create_embeddings.py
Laver embeddings af alle chunks medSentenceTransformer
og all-MiniLM-L6-v2 modellen fra Hugging Face, som genererer vektorerne. Gemmer dem også ichunks/
-mappen. -
04_local_search.py
Implementerer en lokal søgefunktion, der bruger de genererede embeddings til at finde relevante chunks ud fra brugerens spørgsmål. -
05_local_search_hybrid.py
En udvidelse af04_local_search.py
, som først filtrerer chunks ved at tjekke om søgeordet optræder direkte i teksten, og derefter laver en vektorsøgning på kun disse. Det giver hurtigere og ofte mere præcise resultater, fordi irrelevant tekst sorteres fra allerede inden similarity search. -
06_summarize_search_generate.py
Indeholder hele flowet, hvor brugeren kan stille spørgsmål, og systemet finder relevante chunks og genererer et svar. Brugerllm_utils.py
til at håndtere LLM-kald.
Har du lyst, så tag et kig på koden og leg med den. Det er en fremragende måde at lære på — og du kan nemt tilpasse den til dine egne behov.