Snilld

Når keyword-søgning svigter Sådan bygger du kontekstfølsom søgning med embeddings i Python

Brugere spørger i naturligt sprog, ikke i eksakte stavelser. Her er en praktisk vej til semantisk søgning med embeddings, metadata og RAG i Python — hvad der virker i drift, hvor det går galt, og hvordan du måler fremskridt uden gætværk.

23. maj 2026 Peter Munkholm

Vi kender det: man ved, at svaret findes, men søgefeltet hjælper ikke. En deltager sagde på en workshop i Aarhus for nylig: “Hvor står der, at jeg kan nulstille licensen uden at miste historik?” Hun skrev det tre gange i intranettet. Intet. Først da hun tastede “reset license retain history” med præcis produktterminologi dukkede en PDF op. Det føles ikke som brugerfejl; som regel er det søgningen, der halter.

Machine Learning Mastery beskriver samme sammenbrud: keyword-søgning bryder, når en forespørgsel ikke matcher dokumentets ordlyd. Ikke nogen nyhed, men effekten er reel i support og onboarding. Flere synonymer i indekset hjælper kun delvist.

Hvorfor keyword-match fejler

Literal match leder efter bogstavelige sammenfald. Hurtigt og deterministisk, men blindt for betydning. Når en bruger skriver noget, der ikke står ordret i dokumentet, falder kvaliteten. Brugere spørger i intentioner, ikke i eksakte stavelser. Parafraser, ufuldstændige beskrivelser og nært beslægtede fagudtryk bliver væk.

Konsekvensen er flere tilbagespørgsmål i support og skjulte videnslommer i Confluence og SharePoint. Når søgningen halter, spørger folk kollegaer i stedet. Tidsdyrt og skrøbeligt.

Tight editorial close-up of anonymized archive spines and colored metadata tabs, indigo mood with cyan rim lighting and subtle data-pulse overlay.

Hvad embeddings faktisk ændrer

Embeddings repræsenterer tekst som talrækker, der fanger betydning. En forespørgsel som “nulstil licens uden at slette data” bliver matematisk tæt på afsnit om “reset license while preserving history”. Intuitionen: ting, der ligner hinanden i betydning, ligger tæt i et vektorrum.

Det løser ikke alt, men forskellen er mærkbar. Kombinationen af embeddings, vektorindeks og evt. reranking leverer mere brugbare svar i virkelige søgninger. Elastic fremhæver, at semantisk søgning i produktion vinder på relevans og skalerbarhed, når den kobles til driftsværktøjer og enterprise-kontrol. Adgangsstyring og kontekstvindue skal dog sidde i skabet. Ellers glider svaret.

RAG i praksis

Standardmønsteret er enkelt, også ifølge VentureBeat: split dokumenter i bidder, lav embeddings for hver bid, læg dem i en vektor-database, og hent top-k mest lignende bidder ved forespørgsel. LLM’en bruger de bidder som kontekst til et svar. Det virker ofte for produkt-FAQ, kontraktbilag og driftsvejledninger.

Snilld-erfaring: vi har kørt den pipeline i miljøer fra små support-CMS’er til korpora i millionklassen. Når chunking og metadata sidder rigtigt, falder støjen, svartider bliver forudsigelige, og svar trækker på godkendte, ajourførte kilder. Praktisk gevinst, ikke teori.

Top-k er en flaskehals

Den ubehagelige sandhed: mange assistenter fejler ikke på grund af modellens ræsonnement, men fordi retrieval leverer et for snævert udsnit. VentureBeat fremhæver, at top-k-begrænsningen ofte spænder ben for agentiske workflows. Hvis de rigtige brikker ikke kommer med, kan selv en stærk model ikke regne den ud.

Banner

Snilld-erfaring: vi så et top-5 snit overse en kritisk changelog. Agenten begyndte at gætte. Det koster i både drift og tillid. “Hæv k” hjælper ikke altid — det øger latency og mudrer konteksten. Bedre er adaptiv retrieval, skarpe metadatafiltre og ved behov en sekundær, målrettet søgning.

Mid-frame editorial photo at a municipal service counter: hands exchanging an anonymized info ticket, indigo-cyan mood and a faint data-pulse overlay suggesting retrieval flow.

DCI kort fortalt

Direkte korpusinteraktion lader en agent søge i råtekst med klassiske værktøjer, så bevis ikke ryger ud i første vektorscore. VentureBeat beskriver det som særligt nyttigt ved eksakte strenge, versionsnumre og filstier. Vi vurderer det primært som en forskningsnær teknik lige nu. Vil I prøve det, så gør det sikkert: afgræns korpus, kør faste målinger på precision og latency, og sammenlign direkte mod jeres RAG-baseline.

Metadata er styretøjet

Embeddings er motoren, metadata er styretøjet. Felter som dokumenttype, produktversion, gyldighedsperiode, forfatterteam, miljø (prod, test), adgangsniveau og sprog løfter relevansen. Elastic taler for rerankers og enterprise-kontroller, herunder at favorisere nyere dokumenter uden at smadre relevans. Det matcher, hvad vi ser i drift.

Uden metadata sammenligner systemet en prisannonce fra 2019 med en sikkerhedspolitik fra i går. Med de rigtige felter kan du holde søgningen inden for hegnspæle, der giver mening. Snilld-erfaring: “kategori=faq” på tusinder af dokumenter ødelagde signalet. En oprydning gav markant bedre top-3 dagen efter.

Prototypeflow i Python

En minimal vej fra ingest til svar kan se sådan ud:

# 1) Ingest
paths = crawl("./docs")
raw_docs = [load(p) for p in paths]

# 2) Chunk (flad liste af bidder)
chunks = []
for d in raw_docs:
    chunks.extend(chunk(d, size=800, overlap=120))

# 3) Embed
texts = [c.text for c in chunks]
vecs = embed_model.encode(texts)

# 4) Index
vecdb = VectorDB()
vecdb.upsert(
    ids=[c.id for c in chunks],
    vectors=vecs,
    meta=[c.meta for c in chunks]
)

# 5) Query
q = "nulstil licens uden at slette data"
qv = embed_model.encode([q])[0]
cands = vecdb.search(qv, k=8, filter={"product": "X", "env": "prod"})

# 6) LLM-QA
answer = llm.answer(question=q, context=join(cands))

Koden er skitse, men flowet er reproducerbart og let at bygge videre på.

Når keyword-søgning svigter Sådan bygger du kontekstfølsom søgning med embeddings i Python - billede 3

Trinvis plan der holder i hverdagen

Start med at kortlægge indhold og søgelog. Hvad leder folk efter, hvad finder de faktisk, hvor falder de fra. Vælg 1–2 dokumenttyper med høj værdi, fx SLA-tillæg eller installationsguides. Sørg for adgangsgrænser og PII-tjek, før noget forlader kilden.

Byg en lille prototype i Python med en vektor-DB. Indlæs et udsnit, chunk, embed, og sæt en simpel API op. Test top-k, to–tre embeddingsmodeller og hårde metadatafiltre. Snilld-erfaring: typiske fejlkilder i løbet af to uger er forkert chunk-size, for flade metadatafelter eller latency, der stikker af.

Kobl LLM-QA på og etabler et feedbackloop. Gem spørgsmål, kilder og brugervurderinger. Brug det til at justere chunking, filtre og evt. reranking. Ugentlige målinger vinder over kvartalsvise mavefornemmelser.

Designvalg og tradeoffs

Chunk-size: for små bidder mister kontekst; for store bliver alt “næsten relevant”. Vi starter ofte omkring 600–1.200 tokens med overlap og justerer efter dokumenttype. Manualer tåler større bidder end juridiske bilag. Heuristik, ikke lov.

Embeddingsmodel: høj dimension kan give nuance, men koster lager og søgetid. Lav dimension er billigere og hurtigere, men kan tabe fagsprog. Ingen model passer alle. Vælg på data, ikke på rygte.

Vektor-DB: hostet giver hurtig start; selvhostet giver kontrol, men kræver drift. Latency og opdateringsvindue afgør, om indhold føles friskt. Mål p95-svartid, indeksstørrelse og tid til konsistens efter ingest.

Sådan benchmarker du jeres valg

Lav et lille, fast sæt på 20–30 queries, halvt naturligt sprog, halvt fagsprog. Mål:

  • Relevans pr. query vurderet af domæneeksperter på en 0–3 skala
  • Recall@k og MRR@k for k i {3, 5, 10}
  • p95-latency for retrieval og samlet svar

    Kør A\/B med keyword vs. semantik side om side i to uger. Skift kun én variabel ad gangen: først chunk-size, så model, så k. Brug stratificeret sampling af dokumenttyper, så testen ikke forvanskes af en dominerende kilde. Gem resultaterne og gentag testen ved større ændringer.

    Banner

    Drift er det hårde arbejde

    Overvågning: se på recall@k, klikadfærd og p95-latency pr. indeks og pr. query-type. Spikes fredage kl. 15 har altid en årsag. Snilld-erfaring: interne dashboards fangede spikes i embedding-generation og fejlede skriveoperationer, før brugerne gjorde det. Pointen: hold øje, før det gør ondt.

    Vedligehold: når vokabular ændrer sig (ny produktlinje, ny lovtekst), falder kvaliteten. Planlæg kvartalsvist sanity-check og månedligt mini-benchmark på faste queries. Metadata kræver disciplin. Uden den bliver alt “andet”. Backup og gendannelse for vektor-DB er obligatorisk. Test restore af både index og embeddings, ikke kun kildedokumenter.

    Sikkerhed, compliance og PII

    Embeddings kan indeholde persondata, hvis du sender rå tekst ind. Kør PII-screening og maskér før embedding. Adgangskontrol skal ned i retrieval-laget, ikke kun i UI. Log, hvem der søger hvad, og hvilke kilder der blev vist. Gem versionshistorik for embeddingsmodel og indeks.

    Operationelt sletteflow ved kildesletning — eksempel i pseudokode:

    def delete_source_embeddings(source_id):
        ids = vecdb.find_ids(filter={"source_id": source_id})
        for bid in ids:
            vecdb.delete(bid)
        cache.invalidate(keys=ids)
        audit.log(action="delete", source_id=source_id, count=len(ids))
    
    # Ved opdatering
    new_chunks = chunk(load(source_path), size=800, overlap=120)
    new_vecs = embed_model.encode([c.text for c in new_chunks])
    vecdb.upsert(ids=[c.id for c in new_chunks], vectors=new_vecs, meta=[c.meta for c in new_chunks])
    

    Og en kort tjekliste på GDPR-principper: dataminimering ved ingest, formålsbegrænsning dokumenteret i databehandleraftaler, og verificerbar sletning i både kilde og vektorindeks via audit-query.

    Hvordan måler man, om det virker

    Definér KPI’er før pilot: andel korrekte svar (domæneeksperter), gennemsnitlig tid til svar, p95-latency, andel forespørgsler uden fundne kilder og intern brugertilfredshed. Dæk kendte smertepunkter i jeres queries. Halvdelen naturligt sprog, halvdelen fagsprog.

    Har I allerede kørt A\/B som beskrevet, så følg op med fejlklassificering: var problemet retrieval, ranking eller LLM-sammenfatning. Ret i den rækkefølge, ikke omvendt. Det sparer tid og undgår at over-tune modellen for et data-problem.

    Hvad vi har set i praksis

    Snilld-erfaring: I en prototype på et support-CMS faldt andelen af “ingen resultater” markant, men første version gav forkerte top-5-snippets, fordi chunk-size var 2.000 tokens og metadata manglede produktversion. Vi skar chunk-size ned, tilføjede version og miljø — og præcisionen i top-3 steg efter to dages iteration. Ikke magi. Bare håndværk.

    Snilld-erfaring: I en case med kvalitetsmanualer var embeddingsmodellen solid, men uden gyldighedsperiode kom forældede procedurer ind som kilder. Vi indlæste publiceringsdato og filtrerede på “seneste revision”. Det hjalp straks. To små felter, stor forskel.

    Hvornår man skal vente

    Er jeres indhold ekstremt struktureret, fåtalligt og med stærke nøglefelter, kan klassisk søgning med facetter være nok. Skifter indhold så hurtigt, at metadata ikke kan følge med, bliver semantik et dyrt plaster. Og uden ejerskab til kilder og et reelt feedbackloop mangler brændstoffet. Start med governance og kildekvalitet.

    Hvorfor det haster

    Skiftet fra nøgleord til betydning er ikke pynt. Pointen fra Machine Learning Mastery om keyword-svigt er stadig lakmustesten: hvis brugere spørger i naturligt sprog, skal søgningen fange betydningen. VentureBeats gennemgang af top-k og DCI minder om, at retrieval-design ofte er flaskehalsen. Elastic peger på, at værktøjerne til drift og skalering er på plads.

    Vores anbefaling i Snilld: byg småt, målrettet og målbart. Derfra kan man skrue op.

    Service-note fra Snilld

    Vi tilbyder workshop, datarens, embeddings-pipeline, vektor-DB-opsætning, LLM-integration og løbende drift. I skal eje benchmarkene og tallene. Man opdager først forskellen, når man sidder med det i hænderne.

Kilder

    Gør brugeroplevelsen bedre.
    Hvilket firma arbejder du for?