Chat RAG (Retrieval Augmented Generation) - Deep Dive
Inhoudsopgave
- Overzicht
- De Volledige Flow
- Vector Embeddings: De Basis
- Semantic Search: Hoe Vindt Chat Relevante Informatie?
- RAG in Actie: Van Vraag tot Antwoord
- Objecten vs Files: Het Verschil
- Frontend Weergave
- Voorbeeld: 'De kleur van Mokkum is...'
Overzicht
OpenRegister's Chat functionaliteit gebruikt RAG (Retrieval Augmented Generation) om contextuele, nauwkeurige antwoorden te geven op basis van jouw eigen data - zowel objecten als documenten.
Wat is RAG?
RAG combineert twee krachtige technieken:
- Retrieval: Zoek relevante informatie in jouw data via semantic search
- Generation: Gebruik een LLM om een natuurlijk antwoord te genereren met die context
Dit voorkomt 'hallucination' (verzonnen antwoorden) en houdt de AI gegrond in jouw echte data.
De Volledige Flow
Vector Embeddings: De Basis
Wat is een Embedding?
Een embedding is een numerieke representatie van tekst in een hoog-dimensionale ruimte:
// Input tekst
$text = 'De kleur van Mokkum is blauw';
// LLM embedding model (Fireworks: thenlper/gte-base)
$embedding = [
0.234,
-0.123,
0.567,
0.891,
// ... 768 dimensies totaal
];
Waarom embeddings?
- Teksten met vergelijkbare betekenis hebben vergelijkbare vectors
- We kunnen wiskundig berekenen hoe 'dichtbij' twee teksten zijn
- Taal-onafhankelijk: 'color' en 'kleur' zijn dichtbij elkaar
Hoe Worden Embeddings Opgeslagen?
Database tabel: oc_openregister_vectors
CREATE TABLE `oc_openregister_vectors` (
`id` bigint PRIMARY KEY AUTO_INCREMENT,
`entity_type` varchar(50), -- 'object' of 'file'
`entity_id` varchar(255), -- UUID van object/file
`chunk_index` int, -- Voor files: welk stuk (0 voor objects)
`total_chunks` int, -- Voor files: totaal aantal stukken
`chunk_text` longtext, -- De originele tekst (voor debugging/display)
`embedding` longblob, -- Serialized PHP array met vector
`embedding_model` varchar(100), -- 'thenlper/gte-base'
`embedding_dimensions` int, -- 768
`metadata` longtext, -- Extra JSON metadata
`created_at` datetime,
KEY `entity_idx` (`entity_type`,`entity_id`)
);
Voorbeeld rij:
id: 123
entity_type: 'object'
entity_id: 'a1b2c3d4-...'
chunk_index: 0
chunk_text: 'Object met naam Mokkum en kleur blauw'
embedding: (serialized array van 768 floats)
embedding_model: 'thenlper/gte-base'
embedding_dimensions: 768
Semantic Search
Hoe Werkt Semantic Search?
Code: VectorEmbeddingService::semanticSearch()
Stap voor stap:
1. Query Embedding Genereren
// User vraagt: 'Wat is de kleur van Mokkum?'
$queryEmbeddingData = $this->generateEmbedding($query, $provider);
$queryEmbedding = $queryEmbeddingData['embedding'];
// Result: [0.123, -0.456, 0.789, ...] (768 dims)
2. Alle Vectors Ophalen uit Database
$vectors = $this->fetchVectors($filters);
// Returns: All object + file vectors from oc_openregister_vectors
3. Cosine Similarity Berekenen
Cosine Similarity meet de hoek tussen twee vectors:
- 1.0 = identiek (0° hoek)
- 0.0 = geen relatie (90° hoek)
- -1.0 = tegengesteld (180° hoek)
private function cosineSimilarity(array $vector1, array $vector2): float
{
$dotProduct = 0.0;
$magnitude1 = 0.0;
$magnitude2 = 0.0;
// Loop through all 768 dimensions
for ($i = 0; $i < 768; $i++) {
$dotProduct += $vector1[$i] * $vector2[$i];
$magnitude1 += $vector1[$i] ** 2;
$magnitude2 += $vector2[$i] ** 2;
}
$magnitude1 = sqrt($magnitude1);
$magnitude2 = sqrt($magnitude2);
return $dotProduct / ($magnitude1 * $magnitude2);
}
Voor elk vector in de database:
foreach ($vectors as $vector) {
$storedEmbedding = unserialize($vector['embedding']);
$similarity = $this->cosineSimilarity($queryEmbedding, $storedEmbedding);
$results[] = [
'vector_id' => $vector['id'],
'entity_type' => $vector['entity_type'], // 'object' of 'file'
'entity_id' => $vector['entity_id'],
'similarity' => $similarity, // 0.0 - 1.0
'chunk_text' => $vector['chunk_text'],
// ...
];
}