# 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) => (
{m.parts?.map((part, i) => { if (part.type === "text") { return
{part.text}
; } // In AI SDK v6 zijn tool-parts genaamd `tool-` if (part.type?.startsWith("tool-")) { const toolName = part.type.replace("tool-", ""); return (
🔧 {toolName}({JSON.stringify(part.input)}) {part.state === "output-available" && (
Toon resultaat
{JSON.stringify(part.output, null, 2)}
)}
); } return null; })}
))} ``` ### 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-` (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!