Files
novi-lessons/Les15-RAG-Embeddings/Les15-Slide-Overzicht.md
2026-06-07 13:35:02 +02:00

479 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Les 15 — RAG + Embeddings
## Slide Overzicht (Klas A — 3 uur fysiek, demo-driven)
**Lesvorm:** Tim demonstreert klassikaal. Studenten kijken. Zelf bouwen = huiswerk.
**Demo-app:** Nieuwe PDF Q&A app from scratch
**Vervolg op:** Les 14 — Agents
**Aansluit op:** Les 16 — MCP servers
---
## Slide 1: Title
### Les 15 — RAG + Embeddings
**Visual:**
- Background: CREAM
- "Les 15" in BLUE
- "RAG + Embeddings" in BLACK
- Subtitle: "AI laten antwoorden op basis van jouw eigen documenten"
---
## Slide 2: Terugblik
### Waar staan we?
**Vorige lessen:**
- Les 12: Tool Calling — AI kiest welke functie
- Les 13: Agents — autonoom 20-50 stappen
- Les 15: Externe APIs + Vercel deploy
**Het probleem dat we nu oplossen:**
AI weet veel, maar weet NIET wat in jouw documenten staat:
- Interne kennisbank
- Klantcontracten
- Productdocumentatie
- 200-pagina PDF die vandaag binnenkwam
**Twee opties:**
- ❌ Alle 200 pagina's elke vraag meesturen — niet schaalbaar
-**RAG** — alleen de relevante stukjes ophalen per vraag
**Visual:** Pijl van "alle docs context" naar "RAG → top 5 chunks".
---
## Slide 3: Planning
### Vandaag — 180 minuten
| Onderwerp | Duur |
|-----------|------|
| Welkom + Terugblik | 10 min |
| Theorie: Wat is een embedding? | 20 min |
| Theorie: RAG pipeline (index + query) | 15 min |
| Theorie: pgvector + chunking | 15 min |
| **Live Demo 1** — pgvector setup + embed pipeline | 25 min |
| **Live Demo 2** — PDF upload + index | 20 min |
| **Pauze** | 15 min |
| **Live Demo 3** — Query pipeline + AI antwoord | 20 min |
| **Live Demo 4** — RAG-tool in een agent | 15 min |
| Wanneer RAG wel/niet | 10 min |
| Lesopdracht + Huiswerk | 10 min |
| Vragen + Afsluiting | 5 min |
---
## Slide 4: Wat is een embedding?
### Tekst als een punt in de ruimte
**Het idee:**
- Stop een stuk tekst in een embedding-model
- Krijg een **vector** terug — array van ~1536 nummers
- Vergelijkbare betekenissen = vergelijkbare vectors
```
"De kat zit op de mat" → [0.12, -0.45, 0.88, ...]
"Een poes ligt op het tapijt" → [0.14, -0.41, 0.85, ...]
"Voetbal in Nederland" → [-0.73, 0.21, -0.32, ...]
```
Eerste twee zijn **dichtbij in vector-space**. Derde is ver weg. Niet omdat woorden overlappen — omdat **betekenis** vergelijkbaar is.
**Welk model:**
- OpenAI `text-embedding-3-small` — 1536 dim, snel + goedkoop
- OpenAI `text-embedding-3-large` — 3072 dim, beter maar duurder
- Open-source: `nomic-embed-text`, `mxbai-embed-large`
**Visual:** 2D plot met 3 punten — twee dichtbij, één ver weg.
---
## Slide 5: Vector similarity
### Hoe meet je 'dichtbij'?
**Drie metrics:**
| Metric | Wat | Wanneer |
|--------|-----|---------|
| **Cosine similarity** | Hoek tussen vectors (1 tot 1) | Default — werkt op semantic search |
| **Dot product** | Sum van producten | Sneller — als vectors genormaliseerd zijn |
| **Euclidean distance** | Afstand in ruimte | Zelden voor text — meer voor images |
**Cosine similarity = 1** → exact gelijke betekenis
**Cosine similarity = 0** → ongerelateerd
**Cosine similarity = -1** → tegengesteld
```sql
-- In pgvector
SELECT * FROM chunks
ORDER BY embedding <=> '[0.12, -0.45, ...]'::vector
LIMIT 5;
```
`<=>` is cosine distance. Sorteren op `<=>` = dichtstbijzijnde eerst.
**Visual:** Drie vectors met hoeken — kleine hoek = similar.
---
## Slide 6: RAG pipeline
### Index time vs query time
**Index time (eenmalig of bij upload):**
```
PDF / docs
Parse + chunk (500 tokens per chunk)
Embed elke chunk
Store: chunk_text + embedding in pgvector
```
**Query time (elke vraag):**
```
User vraag
Embed vraag
Similarity search → top 5 chunks
Geef chunks als context aan LLM
LLM antwoordt op basis van context
```
**Belangrijkste inzicht:** AI ziet nooit de hele DB. Alleen 5 relevante chunks per vraag.
**Visual:** Twee parallelle flow-diagrammen — index vs query.
---
## Slide 7: pgvector
### Postgres met vector-support
**Wat is het:**
- Postgres extension — voegt `vector` datatype toe
- Cosine, euclidean, dot product operators
- Schaalbaar tot ~1M+ vectors per tabel
- Ingebouwd in Supabase — één click activeren
**Schema voorbeeld:**
```sql
create extension if not exists vector;
create table chunks (
id bigserial primary key,
source text not null,
page int,
content text not null,
embedding vector(1536),
created_at timestamp default now()
);
create index on chunks
using hnsw (embedding vector_cosine_ops);
```
`hnsw` index = snelle approximate nearest neighbor search. Voor production met >10k rows.
---
## Slide 8: Chunking strategieën
### Hoe knip je een document op?
**Drie aanpakken:**
| Strategie | Hoe | Wanneer |
|-----------|-----|---------|
| **Fixed size** | 500 tokens per chunk, 50 overlap | Default, simpelste |
| **Recursive** | Splits op paragraaf → zin → woord | Behoudt structuur |
| **Semantic** | Embed zinnen, group similar | Beste kwaliteit, duurder |
**Wat is een goede chunk:**
- ~200-500 tokens (~1000-2500 chars)
- Genoeg context maar niet te groot
- **Overlap** (10-15%) zodat info aan grenzen niet verdwijnt
**Te kleine chunks** → fragmentatie, AI mist context
**Te grote chunks** → ruis, irrelevant info verdunt het antwoord
**Visual:** Document opgeknipt in chunks met overlap.
---
## Slide 9: Wat we vandaag bouwen
### PDF Q&A from scratch
**Doel:** App waar je een PDF uploadt en daarna vragen kunt stellen over de inhoud.
**Stack:**
- Next.js 16 + TypeScript + Tailwind
- Supabase + pgvector
- AI SDK `embedMany` + `generateText`
- OpenAI `text-embedding-3-small`
- `unpdf` voor PDF parsing
**Twee endpoints:**
| Route | Wat | Wanneer |
|-------|-----|---------|
| `POST /api/index` | PDF upload → chunks → embeddings | Eén keer per document |
| `POST /api/ask` | Vraag → embed → search → answer | Per vraag |
**Bonus:** RAG-tool in een ToolLoopAgent — combineer met Les 13.
**Visual:** Index + query pipelines uitgelegd in 1 diagram.
---
## Slide 10: LIVE DEMO 1 — pgvector + embed pipeline
### ~25 min
**Wat ik laat zien:**
1. `pnpm create next-app pdf-qa` + `pnpm add ai @ai-sdk/openai @supabase/supabase-js unpdf`
2. **Supabase:** activeer `pgvector` extension (één SQL command)
3. Schema: `chunks` tabel met `vector(1536)` kolom + HNSW index
4. `lib/embeddings.ts`:
```typescript
import { embed, embedMany } from "ai";
import { openai } from "@ai-sdk/openai";
const model = openai.textEmbeddingModel("text-embedding-3-small");
export async function embedText(text: string) {
const { embedding } = await embed({ model, value: text });
return embedding;
}
```
5. Test: `console.log` een embedding — array van 1536 nummers tussen -1 en 1
**Visual:** Console-log met vector + Supabase tabel.
---
## Slide 11: LIVE DEMO 2 — PDF upload + index
### ~20 min
**Wat ik laat zien:**
1. `app/api/index/route.ts` — POST endpoint accepteert file upload
2. `unpdf`: extract text per page
3. **Chunking**: split text in ~500 char chunks met 50 char overlap
4. `embedMany`: batch embed alle chunks (sneller dan loop)
5. Insert in Supabase met `embedding` als vector
```typescript
const chunks = chunkText(fullText, 500, 50);
const { embeddings } = await embedMany({ model, values: chunks });
await supabase.from("chunks").insert(
chunks.map((c, i) => ({
source: filename,
content: c,
embedding: embeddings[i],
}))
);
```
6. Demo: upload Polderfest 2027 line-up PDF — 30 chunks in DB
7. Check Supabase tabel — chunks zichtbaar, embedding kolom gevuld
---
## Slide 12: Pauze
### 15 minuten
---
## Slide 13: LIVE DEMO 3 — Query pipeline
### ~20 min
**Wat ik laat zien:**
1. `app/api/ask/route.ts` — POST endpoint accepteert vraag
2. Embed vraag (zelfde model als index!)
3. Supabase RPC of raw SQL: cosine similarity search
```typescript
const { embedding } = await embed({ model, value: question });
const { data: chunks } = await supabase.rpc("match_chunks", {
query_embedding: embedding,
match_count: 5,
});
const context = chunks.map((c) => c.content).join("\n\n");
const { text } = await generateText({
model: openai("gpt-4o-mini"),
prompt: `Beantwoord op basis van deze context:\n\n${context}\n\nVraag: ${question}`,
});
```
4. Supabase function `match_chunks` definiëren (SQL):
```sql
create function match_chunks(query_embedding vector(1536), match_count int)
returns table (content text, similarity float)
language sql as $$
select content, 1 - (embedding <=> query_embedding) as similarity
from chunks
order by embedding <=> query_embedding
limit match_count;
$$;
```
5. Test: vraag "Wie zijn de headliners?" → AI geeft accuraat antwoord op basis van PDF
---
## Slide 14: LIVE DEMO 4 — RAG-tool in een agent
### ~15 min
**Wat ik laat zien:**
Combineer Les 13 (Agents) + Les 15 (RAG):
```typescript
import { ToolLoopAgent, tool, stepCountIs } from "ai";
const ragSearch = tool({
description: "Zoek in geüploade documenten",
inputSchema: z.object({ query: z.string() }),
execute: async ({ query }) => {
const { embedding } = await embed({ model, value: query });
const { data } = await supabase.rpc("match_chunks", {
query_embedding: embedding,
match_count: 5,
});
return data;
},
});
const docAgent = new ToolLoopAgent({
model: openai("gpt-4o-mini"),
system: "Je beantwoordt vragen door eerst in documenten te zoeken.",
tools: { ragSearch },
stopWhen: stepCountIs(10),
});
```
**Demo vraag:** "Vergelijk de jazz en rock-headliners op Polderfest" — agent doet 2-3 RAG searches, vergelijkt, antwoordt.
**Krachtig:** RAG + Agent = AI met geheugen + redenering.
---
## Slide 15: Wanneer RAG wel/niet?
### Niet alles is RAG
**Wanneer WEL RAG:**
- Veel documenten (>50 pagina's totaal)
- Documenten veranderen (klantcontracten, kennisbank)
- Semantic search nodig (niet alleen keyword)
- Privacy: data blijft in jouw DB
**Wanneer NIET RAG:**
- Klein document (<10 pagina's) — gewoon meesturen
- Exacte data (prijzen, IDs) — gebruik tool-calls / DB query
- Structured data (tabellen, records) — SQL is beter
- 1 keer per dag — overhead niet de moeite
**Alternatieven:**
| Probleem | Beter dan RAG |
|----------|---------------|
| 5-pagina manual | Volledige tekst in system prompt |
| 10k product records | SQL tool-calls (Les 12) |
| Live data | API tool-call |
| Code-base navigatie | Tree-sitter / grep, geen embeddings |
**Hybrid is vaak best:** semantic search + keyword filter.
---
## Slide 16: Lesopdracht + Huiswerk
### PDF Q&A app deployen
**Lesopdracht (in-class, 30 min):**
- PDF Q&A app opzetten (volg demo)
- pgvector in Supabase actief
- 1 PDF indexed
- 3 vragen werkend
**Huiswerk (voor Les 16):**
- **A:** Eigen PDF (interesse, studie, hobby) — minimaal 20 pagina's
- **B:** UI met chat-interface (`useChat` van Les 12)
- **C:** RAG-tool in een ToolLoopAgent
- **D:** `RAG.md` met:
- Beschrijving van je PDF + chunks count
- 5 vragen + antwoorden + welke chunks werden opgehaald
- 1 vraag waar RAG het **fout** had — analyse waarom
- Eén chunking-strategie geprobeerd + resultaat
**Bonus:**
- Hybrid search (semantic + keyword via Postgres full-text)
- Re-ranking met `cohere-rerank` of LLM
- Streaming antwoord met source-citations
---
## Slide 17: Volgende les + Afsluiting
### Vragen?
**Vandaag gezien:**
- Embeddings — tekst als vectors in semantic space
- Cosine similarity voor 'dichtbij'
- RAG pipeline — index time vs query time
- pgvector in Supabase met HNSW index
- Chunking strategieën
- RAG-tool in een agent
**Volgende les (Les 16): MCP — Model Context Protocol**
- Agent = LLM in een loop met tools
- ToolLoopAgent — system, tools, stopWhen, prepareStep
- 4 stop-condities: stepCountIs, hasToolCall, isLoopFinished, custom
- Combo: RAG-tool in een agent (combo Les 15 + 15)
**Daarna in deze leerlijn:**
- Les 16: MCP — eigen Model Context Protocol server bouwen
- Les 17: Externe APIs in diepte (OAuth, webhooks, paid APIs)
- Les 18: Supabase Auth + RLS — multi-user apps
**Vragen? Feedback?**
---
## Slide Summary
| # | Title | Type |
|---|-------|------|
| 1 | Title | Opening |
| 2 | Terugblik | Recap |
| 3 | Planning | 180-min |
| 4 | Wat is een embedding | Theorie |
| 5 | Vector similarity | Theorie |
| 6 | RAG pipeline | Theorie |
| 7 | pgvector | Theorie |
| 8 | Chunking strategieën | Theorie |
| 9 | Wat we bouwen | Intro demo |
| 10 | **LIVE DEMO 1** — pgvector + embed | Demo |
| 11 | **LIVE DEMO 2** — PDF upload + index | Demo |
| 12 | Pauze | Break |
| 13 | **LIVE DEMO 3** — Query pipeline | Demo |
| 14 | **LIVE DEMO 4** — RAG-tool in agent | Demo |
| 15 | Wanneer RAG wel/niet | Reflectie |
| 16 | Lesopdracht + Huiswerk | Praktijk |
| 17 | Afsluiting + Les 16 preview | Closing |
---
## Bronnen
- **AI SDK Embeddings:** https://ai-sdk.dev/docs/ai-sdk-core/embeddings
- **OpenAI Embeddings:** https://platform.openai.com/docs/guides/embeddings
- **pgvector:** https://github.com/pgvector/pgvector
- **Supabase pgvector:** https://supabase.com/docs/guides/database/extensions/pgvector
- **unpdf:** https://github.com/unjs/unpdf
- **Anthropic — contextual retrieval:** https://www.anthropic.com/news/contextual-retrieval
- **LlamaIndex RAG concepts:** https://docs.llamaindex.ai/en/stable/getting_started/concepts/