261 lines
7.9 KiB
Markdown
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!
|