229 lines
6.5 KiB
Markdown
229 lines
6.5 KiB
Markdown
# Les 12 — Lesopdracht
|
||
## Refactor jouw thema-app naar Tool Calling
|
||
|
||
**Vak:** AI-Assisted Development
|
||
**Opleiding:** NOVI Hogeschool Utrecht
|
||
**Wanneer:** Thuis, vóór volgende les
|
||
**Inleveren:** GitHub URL + screenshots van werkende multi-step chat
|
||
|
||
---
|
||
|
||
## Doel
|
||
|
||
Bouw voort op je **eigen thema-app** uit Les 11. Refactor de chat-route — weg met de hele dataset meesturen. Vervang door **tools** die AI zelf kan aanroepen.
|
||
|
||
Je oefent:
|
||
- Tools definiëren met description + inputSchema + execute
|
||
- `stopWhen: stepCountIs(N)` voor multi-step queries
|
||
- System prompts voor tool-gebruik aansturen
|
||
- Multi-step vragen testen
|
||
|
||
---
|
||
|
||
## Vereisten
|
||
|
||
- Werkende app uit Les 11 (Next.js + Supabase + chat met data)
|
||
- Eigen thema (geen Polderfest namaken)
|
||
- 100+ records in je Supabase
|
||
- OpenAI key in `.env.local`
|
||
|
||
> Heb je geen werkende Les 11 app? Eerst die afmaken. Deze opdracht bouwt erop voort.
|
||
|
||
---
|
||
|
||
## Wat moet er staan?
|
||
|
||
### Code
|
||
|
||
- [ ] **Chat-route gerefactord** — geen `.select("*")` aan begin van POST meer
|
||
- [ ] **Minstens 3 tools** gedefinieerd voor jouw dataset
|
||
- [ ] Eén tool met **enum parameters** (vaste keuze)
|
||
- [ ] Eén tool met **optional filters** (`.optional()` parameters)
|
||
- [ ] `stopWhen: stepCountIs(5)` in `streamText`
|
||
- [ ] System prompt aangepast: "gebruik tools, verzin niet"
|
||
|
||
### Werking
|
||
|
||
- [ ] Chat werkt nog (geen 500-errors)
|
||
- [ ] Minstens 3 vragen die elk **1 tool** triggeren
|
||
- [ ] Minstens 1 vraag die **2+ tools** triggert (multi-step)
|
||
- [ ] AI verzint niet meer — tool-results worden gebruikt
|
||
|
||
---
|
||
|
||
## Tools voor jouw thema — voorbeelden
|
||
|
||
### Restaurant-aggregator
|
||
|
||
| Tool | Parameters | Wat |
|
||
|------|-----------|-----|
|
||
| `searchRestaurants` | cuisine?, price_range?, neighborhood? | Filter restaurants |
|
||
| `getRestaurantById` | id | Detail + menu |
|
||
| `getCuisineStats` | (geen) | Verdeling per cuisine |
|
||
|
||
### Scriptie-archief
|
||
|
||
| Tool | Parameters | Wat |
|
||
|------|-----------|-----|
|
||
| `searchTheses` | year?, supervisor?, keyword? | Filter scripties |
|
||
| `getThesisAbstract` | id | Volledige samenvatting |
|
||
| `getYearStats` | (geen) | Telling per jaar |
|
||
|
||
### Museum-collectie
|
||
|
||
| Tool | Parameters | Wat |
|
||
|------|-----------|-----|
|
||
| `searchArtworks` | artist?, period?, medium? | Filter kunstwerken |
|
||
| `getArtworkDetails` | id | Detail + provenance |
|
||
| `getPeriodStats` | (geen) | Verdeling per stroming |
|
||
|
||
**Voor jouw thema — bedenk:**
|
||
- Wat filtert iemand vaak? → `searchX` met filter-parameters
|
||
- Wat is een unieke entiteit? → `getXById` of `getXByName`
|
||
- Welke aggregaties zijn interessant? → `getStats`
|
||
|
||
---
|
||
|
||
## Stappenplan
|
||
|
||
### Stap 1 — Backup huidige chat-route (2 min)
|
||
|
||
```bash
|
||
cp app/api/chat/route.ts app/api/chat/route.les11.ts.bak
|
||
```
|
||
|
||
Voor het geval de refactor faalt.
|
||
|
||
### Stap 2 — Refactor route.ts (20 min)
|
||
|
||
Open `app/api/chat/route.ts`. Verwijder:
|
||
- `const { data: bands } = await supabase...select("*")` aan begin
|
||
- De grote `context` string
|
||
- De grote system prompt met alle data
|
||
|
||
Voeg toe:
|
||
- `import { tool } from "ai"` en `import { z } from "zod"`
|
||
- Tool-definities boven je POST-functie
|
||
- `tools: { ... }` en `stopWhen: stepCountIs(5)` in `streamText` (importeer `stepCountIs` uit `"ai"`)
|
||
- Kortere system prompt — alleen rol + tool-tips
|
||
|
||
### Stap 3 — Eerste tool: searchX (15 min)
|
||
|
||
Hier het patroon — pas aan voor jouw thema:
|
||
|
||
```typescript
|
||
const searchItems = tool({
|
||
description:
|
||
"Zoek items in [thema-naam]. Filter op X, Y, of Z. " +
|
||
"Gebruik dit voor filtervragen.",
|
||
inputSchema: z.object({
|
||
category: z.enum(["A", "B", "C"]).optional(),
|
||
minRating: z.number().min(1).max(5).optional(),
|
||
keyword: z.string().optional().describe("Zoekterm in titel of beschrijving"),
|
||
}),
|
||
execute: async ({ category, minRating, keyword }) => {
|
||
let q = supabase.from("items").select("*");
|
||
if (category) q = q.eq("category", category);
|
||
if (minRating) q = q.gte("rating", minRating);
|
||
if (keyword) q = q.ilike("title", `%${keyword}%`);
|
||
const { data, error } = await q.limit(20);
|
||
if (error) return { error: error.message };
|
||
return { count: data.length, items: data };
|
||
},
|
||
});
|
||
```
|
||
|
||
### Stap 4 — Twee extra tools (15 min)
|
||
|
||
Voeg minstens 2 meer toe. Inspiratie boven.
|
||
|
||
### Stap 5 — System prompt aanpassen (5 min)
|
||
|
||
```typescript
|
||
const system = `Je bent een assistent voor [thema-naam].
|
||
Gebruik de beschikbare tools om vragen te beantwoorden.
|
||
|
||
Tips:
|
||
- Voor "welke X met Y?" → searchItems
|
||
- Voor "vertel me over X" → getItemById
|
||
- Voor "hoeveel" of "verdeling" → getStats
|
||
|
||
Verzin nooit data. Antwoord in het Nederlands.`;
|
||
```
|
||
|
||
### Stap 6 — Test multi-step (15 min)
|
||
|
||
In je chat, probeer:
|
||
|
||
1. **Single tool** (`searchItems` triggert):
|
||
```
|
||
Welke [items] hebben [filter]?
|
||
```
|
||
|
||
2. **Single tool** (`getItemById` triggert):
|
||
```
|
||
Vertel me alles over [specifieke item].
|
||
```
|
||
|
||
3. **Single tool** (`getStats` triggert):
|
||
```
|
||
Hoeveel [items] in totaal? En per [groep]?
|
||
```
|
||
|
||
4. **Multi-step** (2+ tools):
|
||
```
|
||
Geef me 3 [items] in categorie X, en vergelijk ze qua [eigenschap].
|
||
```
|
||
|
||
### Stap 7 — Screenshots maken (8 min)
|
||
|
||
Voor je inleveren bij Brightspace:
|
||
- 1 screenshot per single-tool vraag (3 stuks)
|
||
- 1 screenshot van multi-step vraag (waar je 2 tool-calls ziet — als je UI ze toont)
|
||
|
||
---
|
||
|
||
## Inleveren
|
||
|
||
1. **GitHub URL** in Brightspace
|
||
2. **4 screenshots** in je README (3× single + 1× multi-step)
|
||
3. **Eén alinea** in README: welke tools heb je gedefinieerd? Waarom deze?
|
||
|
||
---
|
||
|
||
## Veelvoorkomende problemen
|
||
|
||
| Probleem | Oplossing |
|
||
|----------|-----------|
|
||
| AI roept geen tools aan | Description verbeteren — wees specifieker |
|
||
| AI gebruikt 1 tool meerdere keren | System prompt — zeg "kies juiste tool" |
|
||
| AI returnt foutmelding "Tool args invalid" | Zod schema te strict — gebruik `.optional()` waar nodig |
|
||
| `Cannot find module 'zod'` | `npm install zod` |
|
||
| Tool execute crasht | Error throwen → niet doen, return `{ error: "..." }` |
|
||
| Multi-step werkt niet | `stopWhen: stepCountIs(5)` toegevoegd? Default is 1 stap. |
|
||
|
||
---
|
||
|
||
## Tijd-indicatie
|
||
|
||
| Stap | Tijd |
|
||
|------|------|
|
||
| Backup + refactor route | 22 min |
|
||
| 3 tools definiëren | 30 min |
|
||
| System prompt + multi-step | 20 min |
|
||
| Testen + screenshots | 23 min |
|
||
| **Totaal** | **~1,5 uur** |
|
||
|
||
Loop je vast? Vraag op Brightspace.
|
||
|
||
---
|
||
|
||
## Tips
|
||
|
||
- **Begin klein** — eerst één tool werkend. Dan twee. Dan drie. Niet alles tegelijk.
|
||
- **Schrijf descriptions zoals voor een collega** — "Doe X als gebruiker vraagt om Y."
|
||
- **Test direct na elke tool** — werkt 't? Goed. Werkt 't niet? Beschrijving aanpassen.
|
||
- **Gebruik enums** voor vaste keuzes — AI respecteert ze automatisch.
|
||
|
||
Succes! Volgende les zien we hoe ver dit kan met Agents.
|