RAG, vector databases, OpenAI API's — de technologie bestaat. Maar hoe bouw je dit daadwerkelijk in je webshop? Een technische deep-dive met concrete kosten en implementatiekeuzes.
LLM's integreren in je webshop — RAG, embeddings en API's
De meeste artikelen over AI in e-commerce beschrijven wat mogelijk is. Dit artikel beschrijft hoe je het bouwt — met concrete architectuurbeslissingen, werkende code-voorbeelden, kostencalculaties en de valkuilen die je onderweg tegenkomt.
Dit is een technische deep-dive. Als je liever begint met de businesscase, lees dan eerst ons artikel over AI-gedreven semantic search voor webshops.
Wat een LLM-integratie inhoudt
Een Large Language Model (LLM) is een AI-model getraind op grote hoeveelheden tekst dat zowel tekst begrijpt als genereert. GPT-4, Claude 3, Gemini — dit zijn allemaal LLM's beschikbaar via API.
Integreren in je webshop betekent dat je het model aanroept via een HTTP-request, je eigen data meegeeft als context en het resultaat gebruikt in je applicatie. De drie hoofdtoepassingen voor webshops:
- Generatieve toepassingen — productbeschrijvingen schrijven, marketing-emails opstellen, FAQ-antwoorden genereren op basis van je kennisbasis
- Begripstoepassingen — zoekopdrachten begrijpen, klantvragen categoriseren, sentiment analyseren op reviews
- Conversatietoepassingen — chatbots, productadviseurs, configuratiewizards die door opties leiden
De meest interessante combinatie voor webshops — en ook de meest complexe om te bouwen — is een LLM gekoppeld aan je eigen productdata via RAG.
RAG — Retrieval-Augmented Generation uitgelegd
Een LLM weet veel, maar niet wat er in jouw catalogus staat. GPT-4 weet niet dat jij momenteel een blauwe winterjas in maat M hebt staan voor €129, dat hij 600 gram weegt en waterafstotend is.
De naïeve aanpak is je hele catalogus in de prompt stoppen. Dat werkt niet. Een prompt met 50.000 producten past niet in een context window, kost een fortuin en maakt het model traag en onnauwkeurig. Hoe groter de context, hoe meer het model de neiging heeft relevante informatie te negeren.
RAG is de architectuuroplossing. Het principe is eenvoudig maar de implementatie vereist aandacht.
De RAG-cyclus in vier stappen:- Indexeer — Zet je productdata om naar vector embeddings en sla op in een vector database (eenmalig, daarna incrementeel bij updates)
- Retrieve — Wanneer een gebruiker een vraag stelt, zoek de meest relevante producten via vector similarity search
- Augment — Voeg die relevante producten als context toe aan de LLM-prompt
- Generate — Het LLM genereert een antwoord op basis van die specifieke, actuele context
Het resultaat: een LLM dat accuraat antwoord geeft op basis van jouw catalogus, zonder alles in de prompt te stoppen en zonder dat het model dingen verzint die niet in je assortiment staan.
Technische architectuur
INDEXERINGSPIJPLIJN (eenmalig + incremental updates):
Productdata (Magento/Shopify)
↓
Tekst constructie (naam + beschrijving + attributes)
↓
Embedding model (OpenAI text-embedding-3-small)
↓
Vector database (pgvector / Pinecone)
QUERY-PIJPLIJN (per gebruikersinteractie):
Gebruikersquery
↓
Embedding model → query vector
↓
Vector similarity search → top-K producten
↓
Prompt builder → query + productcontext
↓
LLM API (Claude / GPT-4) → antwoord
↓
Response parser → geformatteerde output
↓
Gebruiker
Code — indexeringsservice in Laravel
<?php
namespace App\Services\AI;
use App\Models\Product;
use Illuminate\Support\Facades\DB;
use OpenAI\Laravel\Facades\OpenAI;
class ProductEmbeddingService
{
/**
* Genereer en sla embedding op voor één product.
*/
public function indexProduct(Product $product): void
{
$text = $this->buildProductText($product);
$embedding = $this->generateEmbedding($text);
// Opslaan in pgvector (PostgreSQL extensie)
DB::statement(
'INSERT INTO product_embeddings (product_id, embedding, indexed_at)
VALUES (?, ?::vector, NOW())
ON CONFLICT (product_id)
DO UPDATE SET embedding = EXCLUDED.embedding, indexed_at = NOW()',
[$product->id, '[' . implode(',', $embedding) . ']']
);
}
/**
* Verwerk een batch producten efficiënt via de Batch API.
*/
public function indexBatch(array $productIds): void
{
$products = Product::whereIn('id', $productIds)
->with(['category', 'attributes'])
->get();
// OpenAI ondersteunt batch embedding — efficiënter en goedkoper
$texts = $products->map(fn($p) => $this->buildProductText($p))->toArray();
$response = OpenAI::embeddings()->create([
'model' => 'text-embedding-3-small',
'input' => $texts,
]);
foreach ($response->embeddings as $index => $embeddingData) {
$product = $products[$index];
$embedding = '[' . implode(',', $embeddingData->embedding) . ']';
DB::statement(
'INSERT INTO product_embeddings (product_id, embedding, indexed_at)
VALUES (?, ?::vector, NOW())
ON CONFLICT (product_id)
DO UPDATE SET embedding = EXCLUDED.embedding, indexed_at = NOW()',
[$product->id, $embedding]
);
}
}
/**
* Bouw een rijke tekstrepresentatie van het product voor betere embeddings.
*/
private function buildProductText(Product $product): string
{
$parts = [
'Productnaam: ' . $product->name,
'Categorie: ' . ($product->category->name ?? ''),
'Beschrijving: ' . strip_tags($product->description ?? ''),
];
// Voeg attributes toe als sleutel-waarde paren
foreach ($product->attributes as $attribute) {
$parts[] = $attribute->label . ': ' . $attribute->value;
}
return implode("\n", array_filter($parts));
}
private function generateEmbedding(string $text): array
{
$response = OpenAI::embeddings()->create([
'model' => 'text-embedding-3-small',
'input' => $text,
]);
return $response->embeddings[0]->embedding;
}
}
Code — retrieval en RAG query service
<?php
namespace App\Services\AI;
use App\Models\Product;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use OpenAI\Laravel\Facades\OpenAI;
class ProductAdvisorService
{
public function __construct(
private ProductEmbeddingService $embeddingService
) {}
/**
* Beantwoord een klant-vraag op basis van de catalogus via RAG.
*/
public function answer(string $userQuestion, int $contextProducts = 5): string
{
// Stap 1: relevante producten ophalen
$relevantProducts = $this->retrieveRelevantProducts($userQuestion, $contextProducts);
if ($relevantProducts->isEmpty()) {
return 'Ik kon geen passende producten vinden voor jouw vraag. '
. 'Kun je je zoekopdracht anders formuleren?';
}
// Stap 2: productcontext bouwen
$productContext = $this->buildProductContext($relevantProducts);
// Stap 3: antwoord genereren via Anthropic Claude
return $this->generateAnswer($userQuestion, $productContext);
}
/**
* Vector similarity search via pgvector.
*/
private function retrieveRelevantProducts(string $query, int $limit): Collection
{
// Zet query om naar vector
$queryEmbedding = OpenAI::embeddings()->create([
'model' => 'text-embedding-3-small',
'input' => $query,
])->embeddings[0]->embedding;
$vectorStr = '[' . implode(',', $queryEmbedding) . ']';
// Cosine distance search — haal alleen voorradige producten op
$results = DB::select("
SELECT p.id, 1 - (pe.embedding <=> ?::vector) as similarity
FROM product_embeddings pe
JOIN products p ON p.id = pe.product_id
WHERE p.is_active = true
AND p.qty > 0
AND (1 - (pe.embedding <=> ?::vector)) > 0.6
ORDER BY pe.embedding <=> ?::vector
LIMIT ?
", [$vectorStr, $vectorStr, $vectorStr, $limit]);
$productIds = collect($results)->pluck('id');
return Product::whereIn('id', $productIds)
->with(['category'])
->get()
->sortBy(fn($p) => array_search($p->id, $productIds->toArray()));
}
private function buildProductContext(Collection $products): string
{
return $products->map(fn($product) => implode("\n", [
'---',
'Product: ' . $product->name,
'Prijs: €' . number_format($product->price, 2, ',', '.'),
'Categorie: ' . ($product->category->name ?? 'Onbekend'),
'Beschrijving: ' . substr(strip_tags($product->description ?? ''), 0, 300),
'Op voorraad: Ja',
'URL: /product/' . $product->url_key,
]))->join("\n");
}
private function generateAnswer(string $question, string $context): string
{
// Anthropic Claude via HTTP client
$response = \Illuminate\Support\Facades\Http::withHeaders([
'x-api-key' => config('services.anthropic.api_key'),
'anthropic-version' => '2023-06-01',
'content-type' => 'application/json',
])->post('https://api.anthropic.com/v1/messages', [
'model' => 'claude-3-haiku-20240307',
'max_tokens' => 600,
'system' => 'Je bent een behulpzame productadviseur. '
. 'Beantwoord vragen uitsluitend op basis van de meegeleverde producten. '
. 'Als geen enkel product past bij de vraag, zeg dat eerlijk. '
. 'Verzin geen producten of specificaties die niet in de context staan. '
. 'Schrijf in het Nederlands. Wees concreet en bondig.',
'messages' => [
[
'role' => 'user',
'content' => "BESCHIKBARE PRODUCTEN:\n{$context}\n\nKLANT VRAAG:\n{$question}",
],
],
]);
return $response->json('content.0.text') ?? 'Er is een fout opgetreden. Probeer het opnieuw.';
}
}
Model keuze en kostenvergelijking
De keuze voor een LLM-provider heeft grote impact op kosten, snelheid en kwaliteit.
OpenAI
GPT-4o (flagship):- Input: $2,50 per 1M tokens | Output: $10,00 per 1M tokens
- Beste voor: complexe redenering, code generatie, meertalige content
- Latency: 1-3 seconden
- Input: $0,15 per 1M tokens | Output: $0,60 per 1M tokens
- Beste voor: hoog volume, eenvoudigere taken, real-time toepassingen
- Latency: <1 seconde
Anthropic (Claude)
Claude 3.5 Sonnet:- Input: $3,00 per 1M tokens | Output: $15,00 per 1M tokens
- Beste voor: lange context, complexe instructie-opvolging
- Sterk in: weigeren van hallucinated content bij beperkende prompts
- Input: $0,25 per 1M tokens | Output: $1,25 per 1M tokens
- Beste voor: real-time chatbot interacties, hoog volume, lage latency
- Latency: <0,5 seconde
Kostencalculatie — productadviseur in productie
Aanname: 10.000 gesprekken per maand, gemiddeld 4 berichten per gesprek, gemiddeld 1.000 tokens per bericht (inclusief productcontext die ~600 tokens is).
10.000 gesprekken × 4 berichten × 1.000 tokens = 40.000.000 tokens/maand
Met Claude 3 Haiku (60% input, 40% output):
Input: 24M tokens × $0,25/1M = $6,00
Output: 16M tokens × $1,25/1M = $20,00
Totaal: $26,00/maand
Met GPT-4o mini:
Input: 24M tokens × $0,15/1M = $3,60
Output: 16M tokens × $0,60/1M = $9,60
Totaal: $13,20/maand
Met GPT-4o (voor complexe use cases):
Input: 24M tokens × $2,50/1M = $60,00
Output: 16M tokens × $10,00/1M = $160,00
Totaal: $220,00/maand
Voor productadviseur en chatbot-toepassingen is Claude 3 Haiku of GPT-4o mini de juiste keuze. Gebruik grotere modellen alleen waar de kwaliteitswinst de kostenstijging rechtvaardigt.
Embedding kosten
OpenAI text-embedding-3-small: $0,02 per 1M tokens
Catalogus van 50.000 producten (gemiddeld 250 woorden per product):
50.000 × 250 woorden × 1,33 tokens/woord ≈ 16.625.000 tokens
Kosten volledige herindexering: $0,33
Dagelijkse updates (500 gewijzigde producten):
500 × 250 × 1,33 ≈ 166.250 tokens/dag
Dagelijkse kosten: $0,003
Jaarlijkse kosten voor updates: $1,10
Embeddings zijn vrijwel gratis. De kosten zitten in LLM inference.
Infrastructuur — wat je nodig hebt in productie
Vector database keuze
pgvector (PostgreSQL extensie)- Kosten: gratis, self-hosted
- Wanneer: je gebruikt al PostgreSQL, catalogus tot ~1M producten
- Voordeel: geen extra service, transactionele consistentie met productdata
- Nadeel: minder geoptimaliseerd voor puur vector-werk dan dedicated oplossingen
- Kosten: $70/maand starter, schaalt met volume
- Wanneer: hoog query-volume, meerdere indexes, geen PostgreSQL
- Voordeel: managed, goed geconfigureerd voor vector-werk
- Nadeel: externe dependency, kosten schalen
- Kosten: gratis self-hosted, managed variant beschikbaar
- Wanneer: hybride search (vector + keyword) belangrijk is
- Voordeel: ingebouwde hybride search, rijke filtering
- Nadeel: complexere setup, meer onderhoud
Voor de meeste Magento- en Shopify-integraties via Laravel is pgvector de pragmatische keuze. Eén service minder, geen vendor lock-in, goede performance tot honderdduizenden producten.
Caching strategie
LLM-responses cachen bespaart 30-50% op API-kosten bij herhalende vragen.
// Cache populaire vragen — 4 uur geldig
$cacheKey = 'product_advisor_' . md5($userQuestion);
return Cache::remember($cacheKey, 14400, function () use ($userQuestion) {
return $this->productAdvisorService->answer($userQuestion);
});
Wees voorzichtig met caching bij tijdsgevoelige informatie: voorraad en prijs mogen niet gecached worden als die real-time moeten zijn.
Rate limiting
Zonder rate limiting ben je kwetsbaar voor kostenoverschrijdingen. Stel limieten in per gebruiker en per IP.
// Laravel throttle middleware voor de AI-endpoints
Route::post('/api/product-advisor', [ProductAdvisorController::class, 'answer'])
->middleware('throttle:20,1'); // 20 requests per minuut per IP
Monitoring
Log elke LLM-aanroep. Minimaal: prompt hash (niet de volledige prompt vanwege privacy), response tijdsduur, token count, kosten en of er een escalatie-trigger was.
Zonder monitoring heb je geen inzicht in kwaliteitsproblemen, misbruik of onverwacht hoge kosten.
Architectuur — Laravel als AI-sidecar
Voor Magento en Shopify bouwen wij de AI-laag als aparte Laravel-service. Dit geeft:
- Scheiding van concerns — Magento blijft Magento. De AI-logica is los en testbaar
- Onafhankelijke schaling — de AI-service kan horizontaal schalen zonder dat Magento dat doet
- Herbruikbaarheid — dezelfde AI-service kan meerdere frontends bedienen
- Technologische flexibiliteit — andere LLM-provider proberen vereist aanpassing in één service
De communicatie verloopt via REST API of GraphQL. Magento of Shopify roept de Laravel AI-service aan, die op zijn beurt de vector database en LLM-API aanspreekt.
Valkuilen die je wilt vermijden
Hallucinaties in productcontextLLM's verzinnen antwoorden als ze geen goede context hebben. Beperk het model expliciet tot de meegeleverde context: "Antwoord alleen op basis van de bovenstaande producten. Als geen enkel product past, zeg dat expliciet." Test dit actief door te vragen naar producten die je niet verkoopt.
Stale embeddingsAls je productdata wijzigt maar de embeddings niet update, krijg je verouderde of verkeerde zoekresultaten. Implementeer een event-driven update via Magento/Shopify webhooks of product-save hooks.
Te grote contextMeer context is niet altijd beter. Stuur nooit meer dan 8-10 producten als context. Met meer producten neemt de kans toe dat het model relevante informatie negeert. Filter scherp in de retrieval-stap.
Ontbrekende similarity-drempelZonder een minimum similarity score stuur je ook producten als context die nauwelijks relevant zijn. Hanteer een drempel van 0,55-0,65 cosine similarity. Producten onder die drempel zijn ruis.
Geen fallbackWat gebeurt er als de OpenAI API down is? Als je vector database te traag reageert? Bouw altijd een fallback: terugvallen op reguliere zoekresultaten of een melding dat de AI-assistent tijdelijk niet beschikbaar is.
Wanneer beginnen
De technologie is toegankelijk. Een werkend prototype van een RAG-gebaseerde productadviseur bouw je in een dag. Een productierijpe implementatie met monitoring, caching, rate limiting, CI/CD en foutafhandeling kost 4-8 weken.
De businesscase is er voor webshops met meer dan 1.000 producten en een van de volgende situaties:
- Klanten bellen of mailen veel over "welk product past bij mijn situatie?"
- De zoekfunctie levert regelmatig nul of irrelevante resultaten
- Productbeschrijvingen schrijven kost te veel tijd
Heb je een concrete use case? Wij bouwen de AI-integratie als onderdeel van je Magento- of Shopify-architectuur, met Laravel als AI-laag. Neem contact op voor een technisch gesprek.
Dit artikel maakt deel uit van onze AI-serie. Lees ook: AI-chatbots voor klantenservice en semantic search voor webshops. Code-voorbeelden zijn vereenvoudigd voor leesbaarheid; productie-implementaties vereisen aanvullende foutafhandeling, security en logging.

Geschreven door Ruthger Idema
15+ jaar ervaring in e-commerce development. Gespecialiseerd in Magento, Shopify en Laravel maatwerk.
Meer over ons team →