Vezetői összefoglaló
A szemantikus keresés az AI-alapú üzleti alkalmazások kritikus infrastrukturális rétege. Míg a hagyományos kulcsszó-keresés pontos egyezéseket keres, a szemantikus keresés a jelentést érti meg — lehetővé téve, hogy egy AI asszisztens valóban releváns kontextussal válaszoljon az üzleti kérdésekre.
Ez a whitepaper egy valós, produkciós multi-tenant SaaS rendszer architektúráját mutatja be, amely:
- 9 különböző adattípust kezel (email, naptár, CRM, számlázás)
- pgvector-t használ dedikált vektor-adatbázis helyett
- Knowledge graph-fal gazdagítja a vektorkeresést
- Greedy token budget rendszerrel optimalizálja az LLM kontextust
- BullMQ aszinkron pipeline-nal kezeli a nagy volumenű embedding-generálást
A whitepaper 15 fejezeten keresztül végigvezeti az olvasót az embedding-modellek kiválasztásától a produkciós monitoringig.
1. A probléma — amikor a keresés nem érti, amit keresünk
A szemantikus szakadék
Képzeld el: egy szépségszalon AI asszisztensét megkérdezik: „Mikor volt utoljára Kiss Anna?"
Kulcsszó-keresés eredménye: Semmi. A szó, hogy „utoljára" nem szerepel egyetlen naptárbejegyzésben sem.
Szemantikus keresés eredménye: Megtalálja Kiss Anna utolsó naptárbejegyzését (2026-03-15, hajvágás + festés), mert megérti, hogy az „utoljára" = legutóbbi időpont.
De ez még nem elég. Mi van, ha a kérdés: „Mikor volt utoljára Kiss Anna, és mit csináltunk?"
Ehhez kell a tudásgráf is: a naptáresemény mellé betölti a kliens-profilt (VIP, allergiás bizonyos festékekre) és az előző alkalommal írt jegyzetet.
Keresési megközelítés Mit talál? A kérdés megválaszolható?
─────────────────────────────────────────────────────────────────────────────────
Kulcsszó (LIKE, tsvector) Pontos szó-egyezés ❌ Nem
Vektoros (embedding) Szemantikailag hasonló ⚠️ Részben
Vektor + gráf Hasonló + kapcsolódó ✅ Igen, kontextussal
Ez a whitepaper azt mutatja be, hogyan jutottunk el a kulcsszó-keresésből a vektor + knowledge graph architektúráig — és milyen döntéseket kellett meghoznunk az úton.
2. Embedding modellek — a szemantikus keresés motorja
2.1 Mi az embedding?
Az embedding egy szöveg (jelen esetben üzleti adat: email, naptáresemény, ügyfélprofil) átalakítása egy numerikus vektorrá — jellemzően 256-3072 dimenziós térben. Az ilyen vektorok között cosine similarity-vel mérhetjük a szemantikai hasonlóságot.
2.2 A fő választási szempontok
2.3 A mi választásunk: OpenAI text-embedding-3-small (1536d)
Miért?
- Multilingvális: A magyar üzleti szövegeket (email, CRM jegyzet) jól kezeli, anélkül, hogy külön magyar modellt kellene üzemeltetni
- API egyszerűség: Már használjuk az OpenAI API-t az LLM-hez — egyetlen vendor, egyetlen API kulcs
- Költséghatékony: $0.02/1M token — egy 10.000 node-os knowledge graph teljes embedding-je ~$0.50
- Dimenzió: Az 1536 jó egyensúly a pontosság és a tárolási/keresési költség között
2.4 Magyar nyelvi sajátosságok
A magyar agglutináló nyelv — a „futottam", „futottál", „futottunk" mind más token, de szemantikailag közel vannak. Az OpenAI modellek BPE tokenizáció-ja ezt részben kezeli, de a nagyon ritka összetett szavak (pl. „munkavállalói-érdekképviseleti-tanácsadó") fragmentálódhatnak.
Gyakorlati tapasztalat: A text-embedding-3-small a magyar üzleti szövegekre (email, CRM, naptár) meglepően jól működik — a cosine similarity értékek konzisztensek a szemantikai hasonlósággal. A ritka szakkifejezéseknél (pl. specifikus kozmetikai eljárások) a contextualized embedding (ld. 14.2 fejezet) segíthet.
2.5 Döntési fa a modellválasztáshoz
Kell-e az adat az EU-n belül maradjon?
├── Igen → Lokális modell (bge-m3, E5-mistral) vagy EU-régió API
│ ├── Van GPU infrastruktúra? → Lokális
│ └── Nincs → EU-régió OpenAI / Cohere
└── Nem → API-alapú
├── Költségérzékeny? → OpenAI text-embedding-3-small ($0.02)
├── Maximális pontosság? → Cohere embed-v4 vagy E5-mistral
└── Már van OpenAI integráció? → text-embedding-3-small (egyszerűség)
3. Vektortárolás — pgvector vs. dedikált vektor-adatbázisok
3.1 A döntés: PostgreSQL + pgvector
A döntő érv: A knowledge graph node-ok és élek ugyanabban az adatbázisban vannak, mint a vektorok. Egyetlen SQL query-vel tudunk vektoros keresést + gráf-bejárást + tenant-szűrést csinálni — nincs hálózati latency két rendszer között.
3.2 A knowledge_nodes tábla
CREATE TABLE knowledge_nodes (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
provider_id UUID NOT NULL REFERENCES providers(id),
type VARCHAR(50) NOT NULL, -- 'email', 'calendar_event', 'client', stb.
source VARCHAR(50), -- 'gmail', 'google_calendar', 'crm', stb.
external_id VARCHAR(255), -- eredeti rendszerbeli ID
label VARCHAR(500), -- emberi olvasható cím
content TEXT, -- a teljes szöveges tartalom
properties JSONB DEFAULT '{}', -- típus-specifikus metaadatok
embedding vector(1536), -- OpenAI text-embedding-3-small
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
CONSTRAINT unique_external UNIQUE (provider_id, source, external_id)
);
-- Vektoros keresési index
CREATE INDEX idx_knowledge_nodes_embedding
ON knowledge_nodes USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100);
-- Tenant-szűrés index
CREATE INDEX idx_knowledge_nodes_provider
ON knowledge_nodes (provider_id);
3.3 Indexelés: IVFFlat vs. HNSW
A mi választásunk: IVFFlat (lists=100). Egy multi-tenant SaaS-ben a tenant-ek jellemzően 100-10.000 node-ot tartanak — ezen a méretkategórián az IVFFlat bőven elegendő, és az index rebuild a BullMQ pipeline-ba integrálható.
4. Aszinkron embedding pipeline — BullMQ architektúra
4.1 Miért aszinkron?
Az embedding-generálás nem szinkron: egy email-szinkronizáció 500 emailt hoz, mindegyikhez API hívás kell. Ha szinkron lenne, egy Gmail sync 500 × 200ms = 100 másodpercig tartana — elfogadhatatlan.
4.2 A pipeline architektúra
Gmail/Calendar Connector
│
▼
EventWorker (concurrency: 5)
│ Node létrehozás/frissítés
│ Él-kezelés (BELONGS_TO, EMAILED, stb.)
▼
EmbeddingQueue.add({ nodeId, content })
│
▼
EmbeddingWorker (concurrency: 3, rate: 50/min)
│ OpenAI API hívás
│ Vektor mentés → knowledge_nodes.embedding
▼
Keresésre kész ✓
4.3 A szöveg-előkészítés
Az embedding minősége a bemeneti szöveg minőségétől függ. A pipeline:
-
Tartalom összeállítás típus szerint:
- Email:
Tárgy: ${subject}\nFeladó: ${from}\n${body} - Naptár:
${summary} - ${start} - ${location}\n${description} - Kliens:
${name} - ${email} - ${notes}
- Email:
-
Tisztítás: HTML tag-ek eltávolítása, whitespace normalizálás
-
Truncate: Max 8000 karakter (a text-embedding-3-small 8191 token limites, de a karakter/token arány ~4:1 magyar szövegnél)
function prepareTextForEmbedding(node) {
let text = '';
switch (node.type) {
case 'email':
text = `Email tárgy: ${node.label}\n${node.content}`;
break;
case 'calendar_event':
text = `Esemény: ${node.label}\n${node.properties?.location || ''}\n${node.content}`;
break;
case 'client':
text = `Ügyfél: ${node.label}\n${node.content}`;
break;
default:
text = `${node.label}\n${node.content}`;
}
return text.replace(/\s+/g, ' ').trim().substring(0, 8000);
}
4.4 Rate limiting és hibakezelés
Az OpenAI embedding API rate limit-je (Tier 3): ~5000 RPM. De a biztonságos operálás érdekében 50 job/percre korlátozzuk:
const embeddingWorker = new Worker('embedding-queue', processEmbedding, {
connection: redis,
concurrency: 3,
limiter: {
max: 50,
duration: 60000 // 50 job/perc
}
});
429 (rate limit) kezelés: Ha az OpenAI 429-et ad, a worker 1 órás szünetet tart (az OpenAI rate limit reset window-ja alapján), majd folytatja. Ez agresszívabb, mint egy exponential backoff, de megbízhatóbb — a 429 után az exponential backoff gyakran „oszcillál".
Hibaizoláció: Egy sikertelen embedding nem blokkolja a többi node feldolgozását. A BullMQ attempts: 3 + backoff: exponential konfiguráció automatikusan újrapróbálja, és ha 3 próba után sem sikerül, a node embedding = NULL marad, ami azt jelenti, hogy a keresés nem találja — de a rendszer működik.
5. Chunking stratégiák — vagy inkább: miért NEM chunkolunk?
5.1 A chunking mítosza
A legtöbb RAG tutorial a „chunking"-ot tanítja elsőként: vágd fel a dokumentumokat 500-1000 tokenes darabokra, mindegyiket embedding-eld külön. Ez dokumentum-alapú rendszerekre kiváló (pl. egy 200 oldalas kézikönyv feldolgozása).
De az üzleti adatoknál más a helyzet:
- Egy email átlagosan 200-500 token — természetes egység, nem kell vágni
- Egy naptáresemény 50-150 token
- Egy ügyfélprofil 100-300 token
- Egy CRM jegyzet 50-200 token
Ezek az adatok „természetes chunk-ok" — ha felvágjuk őket, elveszítjük a kontextust.
5.2 Az entitás-alapú megközelítés
A mi rendszerünk entitás-alapú, nem dokumentum-alapú:
Dokumentum-alapú: Entitás-alapú:
───────────────── ─────────────────
PDF → 50 chunk → 50 vektor Email → 1 node + élek
Nincs kapcsolat a chunk-ok között Naptár → 1 node + élek
Sok redundancia Kliens → 1 node + élek
Természetes gráf-struktúra
Minden üzleti entitás (email, esemény, ügyfél, számla) egy node a knowledge graph-ban, egy embedding-gel. A kontextust nem a chunk-ok átfedése adja, hanem a gráf élek — egy email node össze van kötve a küldővel (client), a szálával (thread), és a benne említett eseményekkel.
5.3 Kivétel: hosszú szövegek
Ha mégis van hosszú szöveg (pl. egy 10.000 szavas termékkatalógus), három strategy közül választhatunk:
- Fixed-size chunking: 500 tokenes darabok, 50 token átfedéssel. Egyszerű, de kontextus-veszteség.
- Recursive character text splitting: Bekezdésenként, majd mondatonként vág. Jobb kontextus-megőrzés.
- Semantic chunking: Embedding-alapú hasonlósággal dönt, hol vágjon. Legjobb minőség, de drágább.
A mi rendszerünkben a hosszú szövegek ritkák (az üzleti adatok 95%-a < 1000 token), ezért az egyszerű truncate (8000 karakter) elegendő.
6. Keresési finomhangolás — cosine similarity, threshold, top-K
6.1 A cosine similarity röviden
Két vektor (A, B) cosine hasonlósága: cos(θ) = (A · B) / (||A|| × ||B||). Értéke -1 és 1 között lehet; 1 = tökéletesen hasonló, 0 = ortogonális (nincs kapcsolat).
A gyakorlatban az embedding modellek 0.3-0.9 tartományban adnak értékeket — a 0.60 feletti érték jellemzően „releváns"-t jelent.
6.2 A threshold paradoxon
Kézenfekvőnek tűnik: állítsuk magasra a threshold-ot (pl. 0.80), és csak a nagyon releváns találatokat adjuk vissza. De:
A 0.60 az optimális threshold a text-embedding-3-small modellel, üzleti adatokon tesztelve. Ez modell-specifikus — más modellel más érték lehet optimális.
6.3 Top-K: miért 8?
A top-K (hány találatot kérünk) a token budget-tel összefügg:
- Token budget: 3000 token (~12.000 karakter)
- Átlagos node tartalom: 300-400 karakter (75-100 token)
- Formázási overhead: ~20 token/node (Markdown fejlécek, szeparátorok)
- Gráf-szomszédok: A top-3 találat szomszédai is bekerülnek
→ 8 direkt találat + ~5 gráf-szomszéd = ~13 node × ~100 token = ~1300 token, ami jól belefér a 3000-es budget-be, marad hely a formázásnak és a „biztonsági tartaléknak".
6.4 Multi-tenant keresési izoláció
A keresés mindig tenant-szűrt:
SELECT id, label, content, type, source, properties,
1 - (embedding <=> $1::vector) AS similarity
FROM knowledge_nodes
WHERE provider_id = $2
AND embedding IS NOT NULL
ORDER BY embedding <=> $1::vector
LIMIT $3;
A provider_id = $2 feltétel biztosítja, hogy egy tenant soha nem lát másik tenant adatait — még véletlenül sem. Ez nem csak GDPR compliance, hanem üzleti kritikus: egy szépségszalon ne lássa egy másik szalon ügyfél-adatait.
7. Kontextus-gazdagítás — a gráf ereje
7.1 A probléma: izolált vektortalálatok
A vektoros keresés izolált node-okat ad vissza. De az üzleti kérdések kontextust igényelnek:
- „Mikor jön legközelebb Kiss Anna?" → Kell a naptáresemény + az ügyfélprofil (pl. allergia-információk)
- „Mi volt a szombati email lényege?" → Kell az email + a teljes szál (thread) + az érintett ügyfél
- „Mennyi bevétel volt márciusban?" → Kell a számlák + az érintett ügyfélrek
7.2 Az 1-hop szomszédok betöltése
A megoldás: a top vektortalálatok gráf-szomszédjait is betöltjük. Az 1-hop = a direkt szomszédok (1 él távolságra lévő node-ok).
Vektor-találat: event_calendar_77 (sim: 0.82)
│
├── BOOKED ──▶ client_15 „Kiss Anna" (VIP, allergia: X festék)
├── BELONGS_TO ──▶ calendar_google_main
└── MENTIONS ──▶ note_23 „Múltkor szólni kell a balayage-ról"
A client_15 és note_23 nem szerepeltek a vektoros keresés top-K találatai között (más az embedding-jük), de kritikus kontextust adnak a válaszhoz.
7.3 A relevancia-öröklés (decay factor)
A szomszédok relevancia-pontszámát a szülő similarity-jéből öröklik, egy decay faktorral csökkentve:
neighbor_score = parent_similarity × decay_factor
A rendszerünkben a decay_factor = 0.8:
- Ha a szülő similarity = 0.82 → a szomszéd score = 0.82 × 0.8 = 0.656
- Ez még a threshold (0.60) felett van → bekerül az eredménybe
- Egy 2-hop szomszéd: 0.82 × 0.8 × 0.8 = 0.525 → threshold alatt → nem kerül be
A decay factor automatikusan szabályozza a gráf-bejárás mélységét: minél távolabbi egy szomszéd, annál alacsonyabb a score-ja, és természetes módon kiesik.
7.4 Miért működik ez a megközelítés?
A relevancia-öröklés három fontos tulajdonsággal bír:
- A direkt találatok mindig előrébb vannak a rangsorban
- A szomszédok nem „szorítják ki" a direkt találatokat a token budget-ből
- Többszörösen hivatkozott entitások (pl. Kiss Anna client node két email-ből is elérhető) magasabb relevancia-pontszámot kapnak
7.5 A rekurzív CTE gráf-bejárás
A szomszédok lekérdezése hatékony SQL-lel történik, rekurzív CTE-vel:
WITH RECURSIVE related AS (
-- Kiindulás: a start node
SELECT kn.id, kn.type, kn.label, kn.content, kn.properties,
0 AS depth, ARRAY[kn.id] AS path
FROM knowledge_nodes kn
WHERE kn.id = $1
UNION ALL
-- Rekurzió: mindkét irányú élek mentén
SELECT kn2.id, kn2.type, kn2.label, kn2.content, kn2.properties,
r.depth + 1, r.path || kn2.id
FROM related r
JOIN knowledge_edges ke ON ke.from_node_id = r.id OR ke.to_node_id = r.id
JOIN knowledge_nodes kn2 ON kn2.id = CASE
WHEN ke.from_node_id = r.id THEN ke.to_node_id
ELSE ke.from_node_id
END
WHERE r.depth < $2 -- max mélység: 1 (vagy 2 speciális esetben)
AND NOT kn2.id = ANY(r.path) -- ciklus-megelőzés
)
SELECT DISTINCT ON (id) * FROM related WHERE depth > 0;
Ciklus-megelőzés: A path tömb tartalmazza az eddig érintett node-okat. Ha egy node már szerepel a path-ban, a rekurzió nem folytatódik azon az ágon. Ez megakadályozza a végtelen ciklusokat kölcsönös hivatkozásoknál (pl. email → thread → email).
7.6 Konfigurálható paraméterek
Miért 1 hop és nem 2? A 2-hop szomszédok exponenciálisan növelik az eredményhalmazt (5 szomszéd × 5 = 25 second-hop node), és a relevancia a decay² = 0.64-re csökken — ami már a threshold (0.60) közelében van. Ezért az 1-hop az optimális egyensúly a kontextus-gazdagság és a költséghatékonyság között.
8. A teljes RAG pipeline — 5 lépésben
8.1 Architekturális áttekintés
Felhasználói kérdés
│
▼
┌─────────────────────────────────────────────────────────┐
│ retrieveRAGContext(providerId, message) │
│ │
│ 1. Guard: message.length >= 3? │
│ └─ Nem → return emptyResult() │
│ │
│ 2. Vector Search │
│ generateEmbedding(message) │
│ → searchByEmbedding(embedding, providerId, topK=8) │
│ → filter: similarity > 0.60 │
│ │
│ 3. Graph Enrichment │
│ Top-3 talált → getNeighbors() per node │
│ → szomszédok decay = 0.8 │
│ → max 5 szomszéd per node │
│ │
│ 4. Dedup + Rank + Token Budget │
│ → deduplikálás ID alapján (magasabb score marad) │
│ → rendezés similarity desc │
│ → greedy packing: 3000 token budget │
│ │
│ 5. Format + Inject │
│ → Markdown kontextus típus szerinti csoportosítással │
│ → Source objektumok a frontend-nek │
│ → System message-ként az LLM-nek │
└─────────────────────────────────────────────────────────┘
│
▼
LLM válaszgenerálás a kontextus alapján
8.2 Lépés 1: Guard — bemeneti validáció
if (!message || message.trim().length < RAG_CONFIG.minQueryLength) {
return emptyResult();
}
Miért kell? Egy 1-2 karakteres üzenet (pl. „hi", „ok") nem hordoz szemantikus tartalmat — az embedding-je „átlagos", ami irreleváns találatokat adna. A 3 karakteres minimum kiszűri ezeket.
8.3 Lépés 2: Vektoros keresés
A felhasználói üzenetet ugyanazzal a modellel embedding-eljük, mint az adatokat (text-embedding-3-small). Ez kritikus: ha a query embedding és a tárolt embedding más modellből jön, a cosine similarity értelmetlen.
A keresés pgvector cosine distance operátorral történik, provider_id tenant-szűréssel.
8.4 Lépés 3: Gráfgazdagítás
A top-3 vektortalálathoz (nem mind a 8-hoz — teljesítmény-tudatos) betöltjük az 1-hop szomszédokat. A szomszédok a szülő similarity-jét × 0.8 decay-jel kapják.
8.5 Lépés 4: Deduplikáció, rangsorolás, token budget
Az összes node (vektor + gráf) egyetlen listába kerül:
- Deduplikáció: Ha egy node többször jelenik meg (pl. egy ügyfél két email szomszédja is), a magasabb score marad
- Rangsorolás: Csökkenő similarity szerint
- Token budget packing: Greedy algoritmus — a rangsort követve addig adagoljuk a node-okat, amíg a 3000 tokenes budget (karakter/4 heurisztikával becsülve) elfogy. Minden node max 400 karakter tartalmat kap.
let usedTokens = 0;
const selected = [];
for (const node of rankedNodes) {
const content = (node.content || '').substring(0, RAG_CONFIG.maxContentLength);
const estimatedTokens = Math.ceil(content.length / 4) + 20; // +20 a formázásnak
if (usedTokens + estimatedTokens > RAG_CONFIG.maxContextTokens) break;
usedTokens += estimatedTokens;
selected.push({ ...node, content });
}
8.6 Lépés 5: Markdown formázás és injektálás
A kiválasztott node-ok típus szerint csoportosított Markdown-ná alakulnak:
📧 **Emailek:**
- **Időpont változtatás** (2026-03-10)
Kedves Salon! Szeretném módosítani a pénteki időpontomat...
_Forrás: Gmail_
📅 **Naptár események:**
- **Hajvágás + festés** (2026-03-15 14:00)
Kiss Anna - 90 perc
_Forrás: Google Calendar_
👤 **Ügyfelek:**
- **Kiss Anna**
Tel: +36-30-123-4567, VIP ügyfél, allergia: bizonyos festékek
_Forrás: CRM_
Ez a Markdown system message-ként kerül az LLM kontextusába — elkülönítve a fő system prompt-tól:
LLM üzenet-tömb:
[0] system → Fő system prompt (személyiség, szabályok, elérhető tool-ok)
[1] system → RAG kontextus (a fenti Markdown)
[2-N] user/assistant → Korábbi beszélgetés (max 50 üzenet)
[N+1] user → Aktuális kérdés
8.7 Forrás-attribúció a frontend-nek
A RAG pipeline nem csak az LLM-nek ad kontextust, hanem a frontend-nek is visszaküldi a forrás-referenciákat:
const sources = selectedNodes.map(node => ({
type: node.type,
label: node.label,
snippet: node.content?.substring(0, 150),
source: SOURCE_LABELS[node.source],
icon: SOURCE_ICONS[node.source],
similarity: node.similarity,
nodeId: node.id
}));
Ez lehetővé teszi, hogy a frontend „Források" szekciót jelenítsen meg a válasz alatt — az LLM válasza mellett látszik, milyen adatokra alapozta a választ.
9. Hybrid keresés — a következő lépés
9.1 A szemantikus keresés korlátjai
A tisztán vektoros keresés nem tökéletes:
9.2 Hybrid: vektor + BM25
A megoldás: kombináljuk a szemantikus keresést kulcsszó-alapú kereséssel (BM25 vagy PostgreSQL full-text search):
Felhasználói kérdés
│
├─── Semantic Search (pgvector cosine) → top-K lista + score
│
└─── Keyword Search (tsvector/BM25) → top-K lista + score
│
▼
Reciprocal Rank Fusion (RRF)
│
▼
Összesített, rangsorolt eredmény
Reciprocal Rank Fusion (RRF) képlet:
RRF(d) = Σ 1 / (k + rank_r(d)) ahol k tipikusan 60
Az RRF előnye, hogy nincs szükség a score-ok normalizálására — a rangsorolás pozíciója számít, nem az abszolút érték.
9.3 PostgreSQL-natív implementáció
A pgvector + pg_trgm + tsvector kombináció lehetővé teszi a hybrid keresést egyetlen adatbázisban:
-- Full-text search index (ha még nincs)
CREATE INDEX idx_knowledge_nodes_fts
ON knowledge_nodes USING gin (to_tsvector('hungarian', label || ' ' || content));
-- Hybrid query: vektor + full-text, RRF
WITH vector_results AS (
SELECT id, label, content,
ROW_NUMBER() OVER (ORDER BY embedding <=> $1::vector) AS v_rank
FROM knowledge_nodes
WHERE provider_id = $2 AND embedding IS NOT NULL
LIMIT 20
),
text_results AS (
SELECT id, label, content,
ROW_NUMBER() OVER (ORDER BY ts_rank_cd(
to_tsvector('hungarian', label || ' ' || content),
plainto_tsquery('hungarian', $3)
) DESC) AS t_rank
FROM knowledge_nodes
WHERE provider_id = $2
AND to_tsvector('hungarian', label || ' ' || content)
@@ plainto_tsquery('hungarian', $3)
LIMIT 20
)
SELECT COALESCE(v.id, t.id) AS id,
COALESCE(v.label, t.label) AS label,
1.0 / (60 + COALESCE(v.v_rank, 1000))
+ 1.0 / (60 + COALESCE(t.t_rank, 1000)) AS rrf_score
FROM vector_results v
FULL OUTER JOIN text_results t ON v.id = t.id
ORDER BY rrf_score DESC
LIMIT 8;
9.4 Mikor érdemes hybrid-re váltani?
10. Re-ranking — a minőség utolsó mérföldje
10.1 A probléma
A bi-encoder (embedding modell) gyors és hatékony, de a hasonlóságot külön-külön számolt vektorok összehasonlításával méri. Nem „olvassa" egyszerre a query-t és a dokumentumot.
A cross-encoder együtt olvassa a query-t és a dokumentumot — ezért pontosabb, de lassabb:
Bi-encoder (embedding):
Query → [vektor_q] Document → [vektor_d] cosine(q, d) → score
Sebesség: ~1000 doc/sec Pontosság: ★★★☆☆
Cross-encoder (re-ranker):
[Query + Document] → score
Sebesség: ~50 doc/sec Pontosság: ★★★★★
10.2 A kétfázisú keresés
A megoldás: a bi-encoder (pgvector) szűr (top-K), a cross-encoder rangsorol:
Kérdés → pgvector cosine (1ms, 50K dokumentumból top-20)
│
▼
Cross-encoder re-rank (200ms, 20 dokumentumon)
│
▼
Top-8 valóban releváns találat
10.3 Elérhető re-ranker megoldások
10.4 Mikor érdemes re-ranker-t használni?
11. Evaluation framework — honnan tudod, hogy jól működik?
11.1 A RAG kiértékelés problémája
A szemantikus keresés és a RAG pipeline kiértékelése nehezebb, mint egy hagyományos keresőé, mert:
- Nincs egyértelmű „helyes válasz" — a relevancia szubjektív
- A válasz minősége a keresés + az LLM együttes teljesítménye
- A kiértékelés drága (emberi annotálás vagy LLM-alapú scoring)
11.2 RAGAS metrikák
A RAGAS framework az iparági standard a RAG kiértékelésre:
11.3 Gyakorlati kiértékelési módszer
50-100 valós kérdéssel készíts golden dataset-et:
{
"question": "Mikor volt utoljára Kiss Anna?",
"expected_context": ["event_77", "client_15"],
"expected_answer_contains": ["2026-03-15", "hajvágás"],
"category": "appointment_lookup"
}
Automatizált kiértékelési ciklus:
- Futtasd a 50 kérdést a RAG pipeline-on
- Mérd: Context Precision, Context Recall, Faithfulness
- Variáld a paramétereket (threshold: 0.55/0.60/0.65, topK: 5/8/12)
- Vizualizáld: precision-recall görbék a különböző konfigurációkra
- Válaszd a legjobb egyensúlyt
11.4 A/B tesztelés produkciókban
Ha van elegendő forgalom, az A/B teszt az igazság:
A „back-question arány" (hányszor kérdez vissza a felhasználó, mert nem kapott jó választ) az egyik legjobb proxy-metrika a RAG minőségére.
12. Knowledge Graph + RAG: a GraphRAG megközelítés
12.1 Mit ad hozzá a gráf a RAG-hoz?
A hagyományos RAG „lapos" — dokumentumokat keres. A GraphRAG struktúrát ad:
12.2 A mi GraphRAG implementációnk
A rendszerünk 9 node-típust és 8 éltípust kezel:
Node típusok:
Éltípusok (kétirányú bejárással):
EMAILED — email küldés/fogadás kapcsolat
BOOKED — foglalás kapcsolat (client → appointment)
PAID — fizetés kapcsolat (client → invoice)
MENTIONS — hivatkozás (bármely node → bármely node)
TAGGED — címkézés
ASSIGNED — hozzárendelés (task → user)
BELONGS_TO — csoportosítás (email → thread, deal → client)
SENT_TO — célzott küldés
12.3 A gráf előnyei valós kérdéseknél
Kérdés: „Mennyit költött Kiss Anna az elmúlt 3 hónapban?"
Hagyományos RAG eredménye:
→ Talál egy emailt, amiben szó van fizetésről
→ LLM becslést ad
GraphRAG eredmény:
1. Vektor keresés → deal_15 „Kiss Anna csomag" (sim: 0.75)
2. Gráf enrichment:
deal_15 ──BELONGS_TO──▶ client_15 „Kiss Anna"
client_15 ──PAID──▶ invoice_23 „45.000 Ft 2026-02"
client_15 ──PAID──▶ invoice_31 „38.000 Ft 2026-01"
client_15 ──BOOKED──▶ appointment_44 „2026-03-15"
3. LLM pontos választ ad: „Kiss Anna az elmúlt 3 hónapban
83.000 Ft-ot költött"
13. Produkciós üzemeltetés — monitoring, drift, re-indexelés
13.1 Mit kell monitorozni?
13.2 Embedding drift
A modellek változnak. Ha az OpenAI frissíti a text-embedding-3-small-t (eddig nem tették, de a korábbi text-embedding-ada-002-t deprecated-nek jelölték), a régi és új vektorok inkompatibilisek. Ez a „drift" — a keresési minőség fokozatosan romlik.
Megelőzés:
- Modellverzió logolás: Tárold, melyik modellverzióval generáltad az embeddinget
- Teljes re-embedding képesség: Legyen script, ami az összes node-ot újra embedding-eli
- Canary tesztek: A golden dataset-et rendszeresen futtasd — ha a precision csökken, gyanakodj drift-re
13.3 Re-indexelés stratégia
Mikor kell re-indexelni?
Zero-downtime re-indexelés:
-- 1. Új indexet építünk CONCURRENTLY (nem zárolja a táblát)
CREATE INDEX CONCURRENTLY idx_knowledge_nodes_embedding_new
ON knowledge_nodes USING hnsw (embedding vector_cosine_ops)
WITH (m = 16, ef_construction = 200);
-- 2. Régi index törlése
DROP INDEX idx_knowledge_nodes_embedding;
-- 3. Átnevezés
ALTER INDEX idx_knowledge_nodes_embedding_new
RENAME TO idx_knowledge_nodes_embedding;
13.4 A resilience mátrix
A teljes pipeline-ban minden komponens fail-safe:
Az elv: Az AI asszisztens mindig válaszol. Ha nincs kontextus, tudásgráf nélkül válaszol. Ha nincs tool, nélkülük. A degradáció fokozatos, sosem totális.
14. Fine-tuning embeddings — mikor érdemes?
14.1 A fine-tuning ígérete és valósága
Az embedding modell fine-tuning-ja a domain-specifikus szókincsre hangolhatja a modellt. De:
14.2 Alternatíva: prompt-szintű embedding javítás
Mielőtt fine-tune-olnál, próbáld a bemeneti szöveg javítását:
// Ahelyett, hogy:
generateEmbedding(email.body)
// Kontextualizáld:
generateEmbedding(`Email tárgy: ${email.subject}\nFeladó: ${email.from}\n${email.body}`)
Ez a „contextualized embedding" meglepően sokat javíthat — a modell többet tud a szöveg kontextusáról, anélkül hogy fine-tune-olnánk.
14.3 Mikor érdemes fine-tune-olni?
15. Összefoglalás és döntési mátrix
15.1 Az architektúra rétegei
┌──────────────────────────────────────────────────────────────┐
│ Felhasználói kérdés │
├──────────────────────────────────────────────────────────────┤
│ RAG Pipeline │
│ ┌─────────────┐ ┌──────────────┐ ┌─────────────────────┐ │
│ │ Vector Search│→ │ Graph Enrich │→ │ Dedup + Token Pack │ │
│ │ (pgvector) │ │ (1-hop CTE) │ │ (3000 token budget) │ │
│ └─────────────┘ └──────────────┘ └─────────────────────┘ │
├──────────────────────────────────────────────────────────────┤
│ Knowledge Graph (PostgreSQL) │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ KnowledgeNode │──│ KnowledgeEdge │ │
│ │ (9 típus, 1536d) │ │ (8 éltípus) │ │
│ └──────────────────┘ └──────────────────┘ │
├──────────────────────────────────────────────────────────────┤
│ Embedding Pipeline (BullMQ) │
│ ┌─────────────┐ ┌──────────────┐ ┌─────────────────────┐ │
│ │ Event Worker │→ │ Embedding Q │→ │ OpenAI API │ │
│ │ (conc: 5) │ │ (50/min) │ │ (text-emb-3-small) │ │
│ └─────────────┘ └──────────────┘ └─────────────────────┘ │
├──────────────────────────────────────────────────────────────┤
│ Connectors │
│ Gmail │ Google Calendar │ CRM │ Számlázz.hu │ Billingo │
└──────────────────────────────────────────────────────────────┘
15.2 CTO döntési checklist
15.3 A legfontosabb tanulságok
-
Kezdj egyszerűen: pgvector + text-embedding-3-small + cosine search. Ez 30 perc alatt működik, és a legtöbb KKV use case-re elegendő.
-
Ne chunkold, ami természetes egység: Emailek, események, ügyféladatok egyben jobbak. Az entitás-alapú knowledge graph megoldja a granularitás kérdését.
-
A gráf-gazdagítás az igazi differenciátor: A „Ki?" „Mikor?" „Mennyit?" típusú kérdésekre a vektoros keresés önmagában gyenge — a szomszédok betöltése drámai minőségjavulást hoz.
-
A resilience nem opcionális: Produkciós rendszerben a RAG pipeline nem blokkolhatja az AI választ. Ha bármi elromlik, graceful degradation: kevesebb kontextussal válaszolunk, de válaszolunk.
-
Mérj 50 kérdéssel, mielőtt bármit változtatsz: A threshold, top-K, token budget mind finomhangolható — de adatvezérelt döntéssel, nem megérzéssel.
Szeretné implementálni a szemantikus keresést a saját rendszerében? Az Atlosz Interactive csapata produkciós tapasztalattal rendelkezik pgvector, knowledge graph és RAG pipeline architektúrában. Vegye fel velünk a kapcsolatot egy ingyenes technikai konzultációért.