Files
novi-lessons/Les12-Tool-Calling/Les12-Huiswerk.md
2026-05-21 08:52:47 +02:00

261 lines
7.9 KiB
Markdown

# Les 12 — Huiswerk
## Write-tool + UI visualisatie + reflectie
**Vak:** AI-Assisted Development
**Opleiding:** NOVI Hogeschool Utrecht
**Deadline:** Voor de volgende les (Les 13 — Agents)
**Inleveren:** GitHub repo + `TOOLS.md` in root
---
## Doel
Bouwt voort op de **lesopdracht** (jouw thema-app met 3 read-tools). Hier voeg je een **write-tool** toe, visualiseert tool-calls in je UI, en documenteert in `TOOLS.md`.
> Niet klaar met de lesopdracht? Eerst die afmaken — het huiswerk heeft de 3 read-tools als startpunt nodig.
---
## Onderdeel A — Write-tool toevoegen (verplicht)
Voeg een tool toe waarmee de gebruiker iets **kan opslaan** in je database.
### Stappen
1. **Nieuwe tabel in Supabase** — voor user-acties. Voorbeelden:
```sql
create table user_favorites (
id bigserial primary key,
user_email text not null,
item_id bigint not null references items(id) on delete cascade,
created_at timestamp default now(),
unique(user_email, item_id)
);
```
Andere ideeën — afhankelijk van thema:
- `user_notes` — eigen aantekeningen per item
- `user_votes` — like/dislike per item
- `user_watchlist` — bewaar voor later
- `user_visited` — track wat al gezien is
2. **Write-tool schrijven**
```typescript
const addToFavorites = tool({
description:
"Voeg een item toe aan de favorieten van de gebruiker. " +
"Alleen gebruiken als de gebruiker expliciet vraagt om iets toe te voegen.",
inputSchema: z.object({
userEmail: z.string().email(),
itemName: z.string(),
}),
execute: async ({ userEmail, itemName }) => {
const { data: item } = await supabase
.from("items").select("id").ilike("name", itemName).single();
if (!item) return { error: `'${itemName}' niet gevonden.` };
const { error } = await supabase
.from("user_favorites")
.insert({ user_email: userEmail, item_id: item.id });
if (error) return { error: error.message };
return { success: true, itemName };
},
});
```
3. **Lees-tool er bij** (om de favorieten weer terug te halen):
```typescript
const listFavorites = tool({
description: "Geef de favorieten van de gebruiker.",
inputSchema: z.object({ userEmail: z.string().email() }),
execute: async ({ userEmail }) => {
const { data, error } = await supabase
.from("user_favorites")
.select("items(name, ...)")
.eq("user_email", userEmail);
if (error) return { error: error.message };
return data?.map((r) => r.items) ?? [];
},
});
```
4. **Test in chat**:
- "Voeg X toe aan mijn favorieten, mijn email is test@test.nl"
- "Wat staat er nu in mijn favorieten?"
### Eisen
- [ ] Nieuwe tabel in Supabase (zelfde regels: RLS aan, policies voor demo)
- [ ] Write-tool werkt — favorieten worden opgeslagen
- [ ] List-tool werkt — favorieten worden teruggehaald
- [ ] System prompt aangepast — "alleen toevoegen op expliciete request"
---
## Onderdeel B — Tool-calls in UI tonen (verplicht)
Refactor je chat-UI om **tool-invocations zichtbaar te maken**.
### Hoe
`useChat` returnt `messages` met **parts** — een array van delen. Tekst-parts én tool-invocation-parts.
```tsx
{messages.map((m) => (
<div key={m.id}>
{m.parts?.map((part, i) => {
if (part.type === "text") {
return <div key={i}>{part.text}</div>;
}
// In AI SDK v6 zijn tool-parts genaamd `tool-<toolName>`
if (part.type?.startsWith("tool-")) {
const toolName = part.type.replace("tool-", "");
return (
<div key={i} className="bg-yellow-50 border border-yellow-300 p-2 rounded text-sm">
🔧 <strong>{toolName}</strong>({JSON.stringify(part.input)})
{part.state === "output-available" && (
<details className="mt-1">
<summary>Toon resultaat</summary>
<pre>{JSON.stringify(part.output, null, 2)}</pre>
</details>
)}
</div>
);
}
return null;
})}
</div>
))}
```
### Eisen
- [ ] Tool-invocations zichtbaar als chip / badge / box
- [ ] Tool-naam + args getoond
- [ ] Resultaat (collapsed) zichtbaar bij open-klikken
- [ ] Multi-step vragen tonen meerdere chips
---
## Onderdeel C — `TOOLS.md` documentatie (verplicht)
Schrijf in je repo-root een markdown-bestand met de volgende secties.
### Sectie 1 — Mijn tools
Tabel met al je tools:
| Tool | Wat doet 't | Read / Write |
|------|-------------|--------------|
| searchItems | Filter op X, Y, Z | Read |
| getItemById | Detail van één item | Read |
| getStats | Verdeling per groep | Read |
| addToFavorites | User favoriet opslaan | **Write** |
| listFavorites | User favorieten ophalen | Read |
### Sectie 2 — Voorbeeld-vragen
**3 vragen die 1 tool gebruiken:**
1. *Vraag*: "..."
*Tool die triggered*: `searchItems({ ... })`
*Antwoord (samenvatting)*: ...
2. (idem)
3. (idem)
**1 vraag die 2+ tools combineert (multi-step):**
*Vraag*: "..."
*Tools die triggeren in volgorde*:
1. `searchItems(...)` — krijgt 5 items
2. `getItemById(...)` — detail van item #3
*Antwoord*: ...
### Sectie 3 — Eén edge-case die AI goed afhandelde
*Wat gebeurde er*: ...
*Welke tool was 't*: ...
*Hoe handelde AI 't af*: ...
Voorbeelden van edge cases:
- Ongeldige enum-value
- Lege resultaten
- Database error
- Vraag waar geen tool voor bestaat
### Vorm
- Max 500 woorden totaal
- Concrete voorbeelden
- Mag wat informeel
---
## Bonus (optioneel)
- **Loading indicator** per tool-execute (spinner naast tool-naam tijdens running)
- **Mooie cards** voor tool-results in plaats van JSON-dump
- **Confirmation UI** voor write-tools (bevestig vóór insert)
- **Tool failure recovery** — als tool faalt, AI probeert andere tool
---
## Inleveren
1. **GitHub repo URL** in Brightspace
2. **`TOOLS.md`** in repo-root
3. **Schema-update** in `schema.sql` (met nieuwe tabel)
4. **Updated `app/api/chat/route.ts`** met write-tool
5. **Updated `app/chat/page.tsx`** met tool-call rendering
---
## Beoordeling
| Criterium | Punten |
|-----------|--------|
| A — Write-tool werkt + list-tool werkt | 3 |
| B — Tool-calls in UI zichtbaar | 2 |
| C — TOOLS.md aanwezig met 3 secties | 3 |
| Chat werkt end-to-end (geen broken pages) | 1 |
| Documentatie sectie 3 (edge case) is concreet | 1 |
| **Totaal** | **10** |
Voldoende = 6+. Bonus telt mee bij twijfelgevallen.
---
## Tijd-indicatie
| Onderdeel | Tijd |
|-----------|------|
| A — Write-tool (tabel + tool + test) | 40 min |
| B — UI tool-call rendering | 30 min |
| C — TOOLS.md schrijven | 20 min |
| **Totaal** | **~1,5 uur** |
---
## Veelvoorkomende valkuilen
- **Write-tool zonder permissies** — werkt voor demo, maar in productie crash je als anon-user iets probeert te schrijven. Voor nu: RLS-policies open zetten.
- **Confusion AI tussen read en write** — sterke description: "alleen gebruiken als gebruiker expliciet vraagt om..."
- **UI part-types niet ondersteund** — check welke versie AI SDK je hebt. In v6 zijn tool-parts genaamd `tool-<toolName>` (niet meer `tool-invocation`). Oudere versies hadden `toolInvocations` array.
- **`details` collapsed in dev maar uitgeklapt in prod** — Tailwind / browser-default. Test in productie.
- **`useChat` returnt parts undefined** — check je AI SDK versie, run `npm update ai`
---
## Tips
- **Schrijf TOOLS.md gaandeweg** — niet aan eind. Sla goede voorbeelden + screenshots op zodra ze werken.
- **Test write-tool eerst los** — voordat je 'm via chat triggert, run de execute-functie handmatig. Sneller debuggen.
- **UI parts is nieuw** — als je oude tutorial volgt, kan API anders zijn. Check `ai-sdk.dev/docs` voor de actuele syntax.
- **Edge case 'goed' afhandelen** is subjectief — schrijf in TOOLS.md wat **jouw** definitie van 'goed' was.
Volgende les: Agents. Met je tools werkend gaan we dan zien hoe AI 30+ tool-calls in één request kan plannen. Tot dan!