405 lines
12 KiB
Markdown
405 lines
12 KiB
Markdown
# Les 12 — Tool Calling
|
|
## Slide Overzicht (Klas A — 3 uur fysiek, demo-driven)
|
|
|
|
**Lesvorm:** Tim demonstreert klassikaal. Studenten kijken. Zelf bouwen = thuis.
|
|
**Demo-app:** Polderfest 2027 (verder bouwen op Les 11)
|
|
**Vervolg op:** Les 11 — Vercel AI SDK + chat met data
|
|
**Aansluit op:** Les 13 — Agents + autonome multi-step workflows
|
|
|
|
---
|
|
|
|
## Slide 1: Title
|
|
### Les 12 — Tool Calling
|
|
|
|
**Visual:**
|
|
- Background: CREAM
|
|
- "Les 12" in BLUE
|
|
- "Tool Calling" in BLACK
|
|
- Subtitle: "Laat AI zelf kiezen welke functie aan te roepen"
|
|
|
|
---
|
|
|
|
## Slide 2: Terugblik
|
|
### Waar staan we?
|
|
|
|
**Vorige les:**
|
|
- Vercel AI SDK basics + 4 kern-functies
|
|
- Polderfest 2027 demo — 500 bands in Supabase
|
|
- Chat-route met `streamText` + `useChat`
|
|
- AI antwoordt op vragen over de data
|
|
|
|
**Het probleem dat we toen lieten zien:**
|
|
- Vandaag sturen we ALLE 500 bands mee als context bij elke vraag
|
|
- ~30.000 tokens per call — werkt voor 500, niet voor 50.000
|
|
|
|
**Vandaag lossen we dat op met Tool Calling.**
|
|
|
|
**Visual:** Pijltje van "alles meesturen" naar "AI kiest tools"
|
|
|
|
---
|
|
|
|
## Slide 3: Planning
|
|
### Vandaag — 180 minuten
|
|
|
|
| Onderwerp | Duur |
|
|
|-----------|------|
|
|
| Welkom + Terugblik + schaalprobleem recap | 10 min |
|
|
| Theorie: wat is Tool Calling? | 30 min |
|
|
| **Live Demo 1** — Eerste tool: searchBands | 20 min |
|
|
| **Live Demo 2** — Multi-step + meer tools | 20 min |
|
|
| **Pauze** | 15 min |
|
|
| **Live Demo 3** — Tool-calls in UI tonen | 25 min |
|
|
| **Live Demo 4** — Edge cases + error handling | 15 min |
|
|
| Waarom Tool Calling > context-all? | 5 min |
|
|
| Lesopdracht + Huiswerk uitleg | 20 min |
|
|
| Vragen + Afsluiting | 15 min |
|
|
|
|
**Format:** Demo-driven, jullie kijken mee.
|
|
|
|
---
|
|
|
|
## Slide 4: Wat is Tool Calling?
|
|
### AI besluit zelf welke functie te gebruiken
|
|
|
|
**Het idee:**
|
|
In plaats van **alle data** mee te sturen, geef je AI **tools** (functies). AI ziet een vraag, kiest welke tool relevant is, roept 'm aan met de juiste parameters, krijgt resultaat, antwoordt.
|
|
|
|
**Voorbeeld-flow:**
|
|
```
|
|
User: "Welke bands spelen vrijdag op de Main Stage?"
|
|
↓
|
|
AI: ik roep searchBands({ day: "Vrijdag", stage: "Main Stage" }) aan
|
|
↓
|
|
Supabase: 12 bands
|
|
↓
|
|
AI: "Op vrijdag op de Main Stage spelen: ..."
|
|
```
|
|
|
|
**Wat krijg je:**
|
|
- Schaalbaar (10 records of 10 miljoen — werkt hetzelfde)
|
|
- Real-time data (geen verouderde context)
|
|
- Type-safe (Zod schema voor parameters)
|
|
- Multi-step (AI kan meerdere tools combineren)
|
|
|
|
**Visual:** Schema diagram chat → tool → DB → AI → antwoord.
|
|
|
|
---
|
|
|
|
## Slide 5: Anatomie van een Tool
|
|
### description + inputSchema + execute
|
|
|
|
```typescript
|
|
import { tool } from "ai";
|
|
import { z } from "zod";
|
|
|
|
const searchBands = tool({
|
|
description: "Zoek bands op dag, stage, of genre",
|
|
inputSchema: z.object({
|
|
day: z.enum(["Vrijdag", "Zaterdag", "Zondag"]).optional(),
|
|
stage: z.string().optional(),
|
|
genre: z.string().optional(),
|
|
}),
|
|
execute: async ({ day, stage, genre }) => {
|
|
// Supabase query
|
|
const { data } = await supabase.from("bands").select("*")
|
|
.eq("day", day || undefined)
|
|
.eq("stage", stage || undefined);
|
|
return data;
|
|
},
|
|
});
|
|
```
|
|
|
|
**Drie verplichte delen:**
|
|
1. **`description`** — Wat doet de tool? AI leest dit om te beslissen.
|
|
2. **`inputSchema`** — Wat heeft de tool nodig? Zod schema = type-safe.
|
|
3. **`execute`** — Wat gebeurt er als de tool wordt aangeroepen?
|
|
|
|
**Belangrijk:** beschrijvingen zijn **kritiek**. AI kiest op basis van descriptions — vaag = verkeerde keuze.
|
|
|
|
---
|
|
|
|
## Slide 6: Multi-step met stopWhen
|
|
### Eén vraag = meerdere tool-calls
|
|
|
|
**Het concept:**
|
|
Met `stopWhen: stepCountIs(5)` geef je AI toestemming om tot 5 keer een tool aan te roepen voordat hij antwoordt.
|
|
|
|
**Voorbeeld:**
|
|
```
|
|
User: "Vergelijk de top headliner met de drukst geplande opener"
|
|
↓
|
|
AI: stap 1 — searchBands({ tier: "headliner" }) → 50 bands
|
|
↓
|
|
AI: stap 2 — searchBands({ tier: "opener" }) → 100 bands
|
|
↓
|
|
AI: stap 3 — verwerkt + vergelijkt
|
|
↓
|
|
AI: antwoordt met vergelijking
|
|
```
|
|
|
|
**In code:**
|
|
```typescript
|
|
const result = streamText({
|
|
model: openai("gpt-4o-mini"),
|
|
tools: { searchBands, getStats, getBandByName },
|
|
stopWhen: stepCountIs(5),
|
|
messages,
|
|
});
|
|
```
|
|
|
|
**Visual:** Flowchart met meerdere tool-blokjes.
|
|
|
|
---
|
|
|
|
## Slide 7: Vandaag — refactor Polderfest naar Tool Calling
|
|
### Wat gaan we bouwen?
|
|
|
|
**Stap voor stap:**
|
|
1. **Refactor** de chat-route van Les 11 — weg met alle bands meesturen
|
|
2. Eerste tool: `searchBands` met filter-parameters
|
|
3. Tweede en derde tool: `getStats`, `getBandByName`, `getScheduleByDay`
|
|
4. Multi-step in actie — vragen die 2-3 tools combineren
|
|
5. UI uitbreiden — tonen welke tools AI aanriep (transparantie)
|
|
6. Edge cases: ongeldige input, lege resultaten, errors
|
|
|
|
**Tools die we vandaag bouwen (6 stuks):**
|
|
|
|
| Tool | Wat | Tier |
|
|
|------|-----|------|
|
|
| `searchBands` | Filter op dag, stage, genre, tier | Read |
|
|
| `getBandByName` | Exact lookup | Read |
|
|
| `getStats` | Aggregate (count per groep) | Read |
|
|
| `getScheduleByDay` | Slot-overzicht per dag | Read |
|
|
| `addFavorite` | User favorite toevoegen | **Write** |
|
|
| `listFavorites` | User favorieten ophalen | Read |
|
|
|
|
**Visual:** Tools-lijst met read/write icons.
|
|
|
|
---
|
|
|
|
## Slide 8: LIVE DEMO 1 — Eerste tool: searchBands
|
|
### ~20 min
|
|
|
|
**Wat ik laat zien:**
|
|
1. Refactor `app/api/chat/route.ts` van Les 11 — weg met de hele context-string
|
|
2. Import `tool` van `ai`
|
|
3. Eerste tool definiëren: `searchBands` met description + inputSchema + execute
|
|
4. System prompt aanpassen: "Gebruik tools, verzin niet"
|
|
5. `tools: { searchBands }` + `stopWhen: stepCountIs(5)` toevoegen aan `streamText`
|
|
6. Browse naar `/chat`, vraag: "Welke bands spelen zaterdag op de Beach Stage?"
|
|
7. Zien: AI roept tool aan met juiste params → antwoordt
|
|
|
|
**Visual:** Side-by-side mock-up: oude chat-route vs nieuwe met tools.
|
|
|
|
---
|
|
|
|
## Slide 9: LIVE DEMO 2 — Meer tools + multi-step
|
|
### ~20 min
|
|
|
|
**Wat ik laat zien:**
|
|
1. Tweede tool: `getStats` — voor "hoeveel jazz acts?"
|
|
2. Derde tool: `getBandByName` — voor "vertel me over Lost Tigers"
|
|
3. Vierde tool: `getScheduleByDay` — voor "tijdschema vrijdag Main Stage"
|
|
4. System prompt verfijnen — wanneer welke tool
|
|
5. Vraag stellen die **meerdere tools** triggert: "Vergelijk twee genres qua headliners"
|
|
6. Tonen: `stopWhen` in actie — 2-3 tool-calls in één antwoord
|
|
|
|
**Visual:** Multi-step flow diagram.
|
|
|
|
---
|
|
|
|
## Slide 10: Pauze
|
|
### 15 minuten
|
|
|
|
---
|
|
|
|
## Slide 11: LIVE DEMO 3 — Tool-calls in UI tonen
|
|
### ~25 min
|
|
|
|
**Wat ik laat zien:**
|
|
1. `useChat` returnt `messages` met **parts** — text én tool-invocations
|
|
2. UI uitbreiden: parts loopen i.p.v. content
|
|
3. Tool-call rendering: "🔧 searchBands({ day: 'Vrijdag', stage: 'Main' })"
|
|
4. Tool-result rendering: collapsed by default, klik om uit te klappen
|
|
5. Streaming tool-calls visualiseren — typing-effect ook bij tool-naam
|
|
6. Bonus: loading-icoon tijdens tool-execute
|
|
|
|
**Waarom transparantie?**
|
|
- Studenten zien wat AI doet (debug-hulp)
|
|
- Vertrouwen — gebruiker ziet "ja, hij heeft echt de DB geraadpleegd"
|
|
- Voor productie: optioneel kunt verstoppen, voor demo onmisbaar
|
|
|
|
**Visual:** Mock-up van chat met tool-call chips.
|
|
|
|
---
|
|
|
|
## Slide 12: LIVE DEMO 4 — Edge cases + error handling
|
|
### ~15 min
|
|
|
|
**Wat ik laat zien:**
|
|
|
|
**Edge case 1: ongeldige input**
|
|
- Vraag: "Welke bands op Donderdag?"
|
|
- AI ziet: `day` parameter is enum — Donderdag bestaat niet
|
|
- Twee opties: AI weigert, of probeert ander filter
|
|
|
|
**Edge case 2: lege resultaat**
|
|
- Vraag: "Death metal bands?"
|
|
- Tool returnt lege array
|
|
- AI legt uit: "Geen death metal acts op Polderfest 2027"
|
|
|
|
**Edge case 3: database error**
|
|
- Wat als Supabase down is? Tool returnt `{ error: "..." }`
|
|
- AI moet dit netjes communiceren — niet hallucineren
|
|
|
|
**Edge case 4: write tool — addFavorite**
|
|
- Demo: AI voegt favoriet toe
|
|
- Confirmation tonen — "✓ toegevoegd aan favorieten"
|
|
- Belangrijk: AI mag write-tools niet zonder expliciete user-intent gebruiken
|
|
|
|
**Visual:** 4 edge-case scenarios + fixes.
|
|
|
|
---
|
|
|
|
## Slide 13: Waarom Tool Calling > context-all?
|
|
### De vergelijking
|
|
|
|
| Aspect | Les 11 (context-all) | Les 12 (tool calling) |
|
|
|--------|---------------------|----------------------|
|
|
| Tokens per call | ~30.000 (500 bands) | ~2.000 (tools + result) |
|
|
| Schaal | Tot ~1000 records | Tot duizenden |
|
|
| Live data | Snapshot bij start | Actueel per call |
|
|
| Write operaties | Niet mogelijk | Wel (addFavorite) |
|
|
| Multi-step | Beperkt | Native (`stopWhen`) |
|
|
| Cost | Hoger | Lager |
|
|
| Complexiteit | Lager | Iets hoger |
|
|
|
|
**Wanneer toch context-all?**
|
|
- Heel kleine dataset (<100 records)
|
|
- Snel prototype
|
|
- Geen schaal nodig
|
|
|
|
**Voor productie: bijna altijd Tool Calling.**
|
|
|
|
---
|
|
|
|
## Slide 14: Lesopdracht
|
|
### Bouw tools voor jouw eigen thema-app
|
|
|
|
**Voor thuis — bouw voort op je app uit Les 11:**
|
|
|
|
1. Refactor je chat-route — weg met de hele context-string
|
|
2. Definieer **minstens 3 tools** voor je eigen dataset
|
|
3. Voeg `stopWhen: stepCountIs(5)` toe aan je `streamText`
|
|
4. Pas system prompt aan: "gebruik tools, verzin niet"
|
|
5. Test 3 vragen die meerdere tools combineren
|
|
|
|
**Tools voorbeelden (afhankelijk van jouw thema):**
|
|
- `searchX(filter)` — read met filters
|
|
- `getXById(id)` — exact lookup
|
|
- `getStats(groupBy)` — aggregate
|
|
- Voor jouw thema-specifieke acties
|
|
|
|
**Eisen:**
|
|
- Werkende refactor (chat werkt nog)
|
|
- Min 3 tools waarvan minstens 1 met enum parameters
|
|
- Min 1 vraag die 2+ tools combineert (multi-step)
|
|
|
|
---
|
|
|
|
## Slide 15: Huiswerk
|
|
### Tools uitbreiden + UI visualisatie + write-tool
|
|
|
|
**Voor volgende week (Les 13):**
|
|
|
|
**Onderdeel A — Write-tool toevoegen**
|
|
- Maak een `user_X` tabel in Supabase (favorites, notes, votes, ...)
|
|
- Schrijf write-tool: `addX(userId, itemId)`
|
|
- Stel een vraag die deze tool triggert: "voeg X toe aan mijn lijst"
|
|
|
|
**Onderdeel B — Tool-calls in UI**
|
|
- Refactor je chat UI om tool-invocations te tonen
|
|
- Style: chip / badge / collapsed result
|
|
- Reden: transparantie + debug
|
|
|
|
**Onderdeel C — `TOOLS.md`**
|
|
Schrijf in repo-root:
|
|
- Welke tools heb je gedefinieerd? (lijst + descriptions)
|
|
- 3 vragen die 1 tool gebruiken
|
|
- 1 vraag die 2+ tools combineert (multi-step)
|
|
- 1 voorbeeld van een edge-case die AI goed afhandelde
|
|
|
|
**Bonus:** Loading indicator per tool-execute, tool-result formatten als kaartjes.
|
|
|
|
---
|
|
|
|
## Slide 16: Volgende les — Agents + autonomie
|
|
### Hoe ver kan een AI autonoom?
|
|
|
|
**Wat we vandaag deden:**
|
|
- AI roept tools aan in 1 ronde, maximaal 5 stappen
|
|
- Antwoord komt terug naar gebruiker
|
|
|
|
**Volgende les (Les 13):**
|
|
- **AI Agents** — langere autonome workflows
|
|
- `stopWhen: stepCountIs(20)` of meer — AI plant, voert uit, evalueert, herhaalt
|
|
- Tools die andere tools triggeren
|
|
- Stop-condities, retries, error recovery
|
|
- Voorbeeld: "Plan mijn volledige Polderfest weekend" — AI bekijkt alle dagen, maakt schema, voegt toe aan favorieten
|
|
|
|
**Daarna in deze leerlijn:**
|
|
- Les 14: RAG + embeddings (semantic search op grote datasets)
|
|
- Les 15-16: Testing + Deployment + Performance
|
|
- Les 17-18: Eindopdracht-werkdagen + Pitch
|
|
|
|
---
|
|
|
|
## Slide 17: Afsluiting
|
|
### Vragen?
|
|
|
|
**Vandaag gezien:**
|
|
- Schaalprobleem van context-all opgelost met Tool Calling
|
|
- Anatomie van een tool: description + inputSchema + execute
|
|
- `stopWhen` voor multi-step workflows
|
|
- Zes tools voor Polderfest gebouwd
|
|
- Tool-invocations in UI gevisualiseerd
|
|
- Edge cases + error handling
|
|
|
|
**Volgende les:** Agents + autonomie
|
|
|
|
**Vragen? Feedback?**
|
|
|
|
---
|
|
|
|
## Slide Summary
|
|
|
|
| # | Title | Type |
|
|
|---|-------|------|
|
|
| 1 | Title | Opening |
|
|
| 2 | Terugblik + schaalprobleem | Recap |
|
|
| 3 | Planning | 180-min |
|
|
| 4 | Wat is Tool Calling | Theorie |
|
|
| 5 | Anatomie van een tool | Theorie |
|
|
| 6 | Multi-step met stopWhen | Theorie |
|
|
| 7 | Vandaag bouwen we | Intro demo |
|
|
| 8 | **LIVE DEMO 1** — searchBands | Demo |
|
|
| 9 | **LIVE DEMO 2** — Multi-step + meer tools | Demo |
|
|
| 10 | Pauze | Break |
|
|
| 11 | **LIVE DEMO 3** — Tool-calls in UI | Demo |
|
|
| 12 | **LIVE DEMO 4** — Edge cases | Demo |
|
|
| 13 | Tool Calling vs context-all | Reflectie |
|
|
| 14 | Lesopdracht | Praktijk |
|
|
| 15 | Huiswerk | Praktijk |
|
|
| 16 | Volgende les: Agents | Preview |
|
|
| 17 | Afsluiting | Closing |
|
|
|
|
---
|
|
|
|
## Bronnen
|
|
|
|
- Vercel AI SDK Tools: https://ai-sdk.dev/docs/foundations/tools
|
|
- Multi-step: https://ai-sdk.dev/docs/foundations/agents
|
|
- Zod docs: https://zod.dev
|
|
- OpenAI Function Calling docs: https://platform.openai.com/docs/guides/function-calling
|
|
- Supabase JS query builder: https://supabase.com/docs/reference/javascript/select
|