// Diskussionsunderlag, Viva AI-team

Embedding-modeller är rätt verktyg för rätt jobb. Här är ett konkret case.

Lokal bge-m3 ersätter Claude-estimering i scoring-fasen av AI Relevansstrategi-skillen. Inte för att bge-m3 är "smartare", utan för att den löser det problem skillen ställer (vector similarity) i en uppgift där LLM:er är fel verktyg.

Vad mäter scoring-fasen?

AI Relevansstrategi (Vector Strategy) bygger på en enkel premiss: AI-sökmotorer (ChatGPT, Perplexity, Gemini) hämtar content via vector retrieval. När en användare frågar något konverterar systemet frågan till en vektor och hämtar de chunks vars vektorer ligger närmast.

Vår scoring-fas mäter exakt det: hur nära varje textstycke ligger ett målsökord i den semantiska rymden. Resultatet är ett cosine similarity score per chunk.

Tidigare gjorde vi den mätningen med Claude. Men cosine similarity är inte en reasoning-uppgift. Det är en geometrisk operation. LLM:er är inte byggda för det. Embedding-modeller är.

Cosine similarity, kort förklarat

Embedding-modellen omvandlar text till en lång rad siffror, en vektor i hög-dimensionell rymd. Bge-m3 jobbar i 1024 dimensioner. Två texter med liknande betydelse hamnar nära varandra i den rymden. Olika texter hamnar längre ifrån.

θ Target: "skinnsoffa" Chunk: "Fördelar med skinnsoffor..." Liten vinkel θ = hög similarity. Cosine av vinkeln blir score.
cosine(A, B) = (A · B) / (‖A‖ × ‖B‖)
Resultatet ligger mellan -1 och 1. Normaliseras till 0 till 1 i vår scoring.

Det här är vad RAG-system gör. Det är vad ChatGPT gör när den hämtar fakta från sin context. Det är vad Perplexity gör för citationer. Vi mäter alltså med samma metod som AI-sökmotorerna själva använder, vilket är hela poängen med Vector Strategy som metod.

bge-m3 från BAAI, kör lokalt via Ollama

Bge-m3 är en open source embedding-modell från Beijing Academy of AI. Den är multilingual (100+ språk inkl. svenska), tränad med kontrastiv inlärning, och topprankad på MTEB-benchmarken för flerspråkig retrieval.

ModellBAAI/bge-m3
Storlek567M parametrar, 1.2 GB på disk, ~600 MB RAM under körning
Dimensionalitet1024 dimensioner per vektor
Kontextfönster8192 tokens (≈ 6000 ord) per chunk
Språkstöd100+ språk, designad multilingual från grunden
LicensMIT (öppen, kommersiell användning OK)
KörningLokalt via Ollama på M1 Pro, sub-sekund per scoring

Hur det integrerades i skillen

Skillens scoring-fas (score_chunks.py) byggdes om med en engine-abstraktion. Tre läges-fallback i ordning:

# Default: lokal Ollama med bge-m3
python score_chunks.py --target "skinnsoffa" --input extract.md --output scores.json

# Auto-fallback om Ollama saknas men OPENAI_API_KEY finns
python score_chunks.py --engine openai ...

# Sista fallback: Claude-estimering inline (icke-deterministisk)

Tröskelvärdena för svag/medel/stark är engine-specifika eftersom olika embedding-modeller har olika absolutskala. Kalibrerade på 60 chunks över 5 cases:

ollama:bge-m3svag < 0.76, stark ≥ 0.81 (smal distribution, [0.66, 0.84])
openai:text-embedding-3-smallsvag < 0.65, stark ≥ 0.75 (legacy, ada-002-kalibrerat)
ai-estimeradsvag < 0.65, stark ≥ 0.75 (Claude använder hela 0 till 1-skalan)

Tre tal som flyttar matten

0
tokens per scoring
Tidigare 40k till 130k via Claude
0,000
avvikelse mellan körningar
Tidigare 0,06 till 0,23 max abs diff
20×
snabbare per scoring
Tidigare 7 till 15 s, nu under 1 s
Förut

Claude-estimering

  • LLM-resonemang för varje chunk
  • Drift mellan körningar (samma chunk, olika score)
  • API-tokens per analys
  • Kräver internet
Nu

Lokal bge-m3

  • Vector cosine, deterministisk algoritm
  • Bit-identisk output över obegränsat antal körningar
  • Noll API-kostnad i mätfasen
  • Funkar offline efter första pull

Embedding-modellen mäter. Claude tolkar.

Det här är inte en ersättning av Claude. Det är att flytta Claude till rätt nivå i pipeline. Claude är fortfarande oumbärlig för Fas 5 till 9 (gap-analys mot konkurrenter, drivande faktorer, rekommendationer per chunk, mönsteranalys, fan-out). Den jobbar bara inte längre med grov-mätningen.

bge-m3 (Fas 4)

Vector mätinstrument. Producerar siffror. Bit-identisk varje körning. Inga tokens. 1024-dim cosine similarity per chunk mot target.

Claude (Fas 5 till 9)

Tolkning. Tar siffrorna och chunk-texten som input. Avgör vad som är false positives, gör konkurrentbenchmark, skriver rekommendationer, identifierar sitewide-mönster.

Konkret exempel: Mitsubishi Hero-sidan har en chunk "Fyll i formuläret för offert" (kontaktformulär). Bge-m3 scorar den 0.74 (term-överlapp finns). Claude i Fas 6 tittar på chunken och ser att det är ett formulär, inte content, och flaggar det som false positive. Båda gör sin del. Ingen ersätter den andra.

Determinism gör iterativ förbättring möjlig

Det här är den underskattade vinsten. När scoring är deterministisk kan vi för första gången köra samma textsnutt mot samma target om och om igen och faktiskt mäta om en omskrivning blev bättre eller sämre. Tidigare gick det inte. Variationen mellan körningar var större än effekten av textförändringen.

v1
Original
"Vi säljer skinnsoffor med kvalitet och stil för hemmet."
0.71
v2
Lägg till material-variation och underhåll
"Skinnsoffor i fullnarvsläder och anilinläder, slittåliga och åldras vackert med rätt vård."
0.78
v3
Lägg till modeller och användningsscenarier
"Skinnsoffor i hörnmodell, divan eller 3-sits, för vardagsrum, kontor eller TV-rum. Slittåliga, lätta att rengöra."
0.83

Tre versioner mot samma target ("skinnsoffa"). Tre olika scores. Ingen tvekan om att v3 mäter bättre. Vi kan stå för siffrorna inför kund. Tidigare hade vi behövt köra varje version 5 gånger och räkna medelvärde för att vara säkra.

Det här är ett första steg, inte en färdig produkt

Skillen kör nu lokalt på min M1. Det är beviset att lokal embedding-modell fungerar i produktion. Frågan är hur vi paketerar och skalar det.

// Fråga 1

Kan vi köra bge-m3 (eller motsvarande) på server-nivå så hela byrån har tillgång? Cloudflare Workers AI har bge-m3 som beta. En central Ollama-instans på vår infrastruktur är ett annat alternativ. Containerized via Docker?

// Fråga 2

Hur passar det här in i Viviane? Om Viviane redan har en LLM-backend kan en embedding-endpoint vara en naturlig komplettering. Då blir scoring tillgängligt som en intern API för andra workflows, inte bara AI Relevansstrategi.

// Fråga 3

Vilka andra användningsfall har vi för embedding-modeller? Klustring av kund-content, semantisk sök i content-bibliotek, dedupe-detection i FAQ-batchar, fan-out av sub-intentioner i Vector Strategy Fas 9. Det här är ett mätinstrument vi inte hade tidigare.

// Fråga 4

När är embedding-modell rätt verktyg och när är LLM rätt? Heuristik: om uppgiften är geometrisk likhet, klustring, ranking eller retrieval, är det embedding. Om uppgiften är reasoning, omformulering, analys eller generering, är det LLM. Båda har sin plats. Använd inte LLM som embedding-modell.

// Fråga 5

Hur paketerar vi det här som intern tjänst? Ett wrapper-API över Ollama? En pre-byggd Docker-image som varje teammedlem kan köra lokalt? En nodpool på server-sidan med queue? Det är inte tekniskt svårt, det är en distributionsfråga.

Hur vi vet att det funkar

Bytet är inte gjort på känsla. Vi körde en benchmark på riktig kunddata för att jämföra metoderna mot varandra på det som spelar roll i en SEO-leverans: rankar de chunkarna likadant, ger de samma score varje körning, hur dyr är varje metod.

Setup

5 test-caseCDON Apple AirPods, ILVA soffor, Ekeby skinnsoffa, Takteam antialgbehandling, Mitsubishi Hero. Olika branscher, olika sökord-typ.
60 chunksAlla extrakt-text-stycken över 25 ord från sidorna i de 5 casen.
5 metoderbge-m3 (lokal), embeddinggemma (lokal mindre), Claude Opus, Claude Sonnet, Claude Haiku.
2 körningar per metodFör att mäta variation. Totalt 50 mätserier.

Fynd 1: Bara bge-m3 ger samma resultat varje gång

Vi körde varje metod två gånger med exakt samma input. Mätte hur mycket scorearna skilde sig mellan körning 1 och 2. För en kund som vill jämföra rapporter över tid är det här hela skillnaden mellan "vi mäter samma sak" och "siffrorna driver".

bge-m3 (lokal)
0.000
Identisk varje körning
Claude Opus
0.10
Drift på upp till 0.10 per chunk
Claude Sonnet
0.12
Drift på upp till 0.12 per chunk
Claude Haiku
0.23
Drift på upp till 0.23 per chunk

Stapelns längd = hur mycket samma chunk kan ändra score mellan körning 1 och 2. Lime stapel = ingen drift. Orange = drift.

Fynd 2: bge-m3 hittar samma svaga chunks som Claude Opus

Vector Strategy-rapporten lyfter fram chunks som är för svaga och behöver skrivas om. Frågan: hittar bge-m3 samma svaga chunks som Claude Opus skulle ha pekat ut? Svar: ja, ungefär 7 av 10.

bge-m3 vs Opus
69%
Samma svaga chunks identifierade
Sonnet vs Opus
79%
Samma svaga chunks identifierade
Haiku vs Opus
69%
Samma svaga chunks identifierade
embeddinggemma vs Opus
39%
Hittar fel chunks. Drop.

bge-m3 ligger i nivå med Haiku på att hitta de chunks som behöver fixas. Det är den primära jobbet i Vector Strategy.

Fynd 3: Tokenkostnaden faller dramatiskt

Token-budget per analys, summerat över alla 5 cases och 2 körningar (totalt 10 mätningar per metod). Detta är vad en analys faktiskt kostar i Claude-tokens när scoring-fasen är inräknad.

bge-m3 (lokal)
0
Ingen API-kostnad. Allt körs lokalt.
Claude Sonnet
407 320
Tokens för 10 scoring-körningar
Claude Opus
616 840
Tokens för 10 scoring-körningar
Claude Haiku
1 327 265
Mer tokens än Opus, oväntat

Haiku är inte billigare i praktiken eftersom den lägger till resonemang som Opus skippar. Bge-m3 kör helt utanför token-ekonomin.

Sammanfattat: bge-m3 är gratis, blir samma varje gång, hittar 7 av 10 svaga chunks som Opus skulle ha hittat. Det räcker för det jobb Vector Strategy faktiskt gör i en SEO-leverans (peka ut vad som behöver skrivas om). Nyans-tolkning sker i Fas 5 till 9 med Claude som fortfarande är på plats.