12 KiB
Les 17: Vercel AI SDK, Tool Calling & Agents
Hoofdstuk
Deel 4: Advanced AI Features (Les 13-18)
Beschrijving
Bouw AI-powered features in je apps met de Vercel AI SDK. Leer niet alleen chat interfaces bouwen, maar ook hoe AI externe data kan ophalen via Tool Calling en autonome taken kan uitvoeren als Agent.
Te Behandelen
Groepsdiscussie (15 min)
Bespreek klassikaal de Cursor .cursorrules ervaringen uit Les 16 - welke regels zorgen voor betere AI output?
Waarom Vercel AI SDK?
Het probleem: Direct API calls naar OpenAI/Anthropic zijn complex:
- Streaming handmatig implementeren
- Error handling
- State management
- Tool calling implementeren
De oplossing: Vercel AI SDK
- Simpele React hooks (
useChat,useCompletion) - Built-in streaming
- Provider-agnostic (OpenAI, Anthropic, etc.)
- Tool calling out-of-the-box
- Agent capabilities met
maxSteps
Installatie & Setup
npm install ai @ai-sdk/openai zod
# zod is nodig voor tool parameter validatie
Environment variable:
# .env.local
OPENAI_API_KEY=sk-xxxxx
Deel 1: Basic Chat (Herhaling)
useChat Hook
'use client'
import { useChat } from 'ai/react'
export function ChatComponent() {
const { messages, input, handleInputChange, handleSubmit, isLoading } = useChat()
return (
<div className="flex flex-col h-screen">
<div className="flex-1 overflow-y-auto p-4">
{messages.map(m => (
<div key={m.id} className={m.role === 'user' ? 'text-right' : 'text-left'}>
<span className="inline-block p-2 rounded-lg bg-gray-100">
{m.content}
</span>
</div>
))}
</div>
<form onSubmit={handleSubmit} className="p-4 border-t">
<input
value={input}
onChange={handleInputChange}
placeholder="Type a message..."
className="w-full p-2 border rounded"
/>
</form>
</div>
)
}
Basic API Route
// app/api/chat/route.ts
import { openai } from '@ai-sdk/openai'
import { streamText } from 'ai'
export async function POST(req: Request) {
const { messages } = await req.json()
const result = streamText({
model: openai('gpt-4o-mini'),
system: 'You are a helpful assistant.',
messages,
})
return result.toDataStreamResponse()
}
Deel 2: Tool Calling - AI + Externe Data
Het probleem met basic chat:
- AI kent alleen zijn training data
- Geen toegang tot realtime informatie
- Kan geen acties uitvoeren
De oplossing: Tool Calling
- Definieer "tools" die AI kan aanroepen
- AI besluit zelf wanneer een tool nodig is
- Tool haalt data op → AI interpreteert resultaat
Voorbeeld: Cocktail Advisor met TheCocktailDB
// app/api/chat/route.ts
import { openai } from '@ai-sdk/openai'
import { streamText, tool } from 'ai'
import { z } from 'zod'
export async function POST(req: Request) {
const { messages } = await req.json()
const result = streamText({
model: openai('gpt-4o-mini'),
system: `Je bent een cocktail expert.
Gebruik de tools om cocktails te zoeken en recepten op te halen.
Geef persoonlijk advies op basis van de resultaten.`,
messages,
tools: {
// Tool 1: Zoek cocktails op ingrediënt
searchByIngredient: tool({
description: 'Zoek cocktails die een specifiek ingrediënt bevatten',
parameters: z.object({
ingredient: z.string().describe('Het ingrediënt om op te zoeken, bijv. "rum" of "vodka"')
}),
execute: async ({ ingredient }) => {
const res = await fetch(
`https://www.thecocktaildb.com/api/json/v1/1/filter.php?i=${ingredient}`
)
const data = await res.json()
return data.drinks?.slice(0, 5) || []
}
}),
// Tool 2: Haal cocktail details op
getCocktailDetails: tool({
description: 'Haal het volledige recept van een cocktail op',
parameters: z.object({
cocktailId: z.string().describe('Het ID van de cocktail')
}),
execute: async ({ cocktailId }) => {
const res = await fetch(
`https://www.thecocktaildb.com/api/json/v1/1/lookup.php?i=${cocktailId}`
)
const data = await res.json()
return data.drinks?.[0] || null
}
}),
// Tool 3: Zoek non-alcoholische opties
searchNonAlcoholic: tool({
description: 'Zoek non-alcoholische cocktails/mocktails',
parameters: z.object({}),
execute: async () => {
const res = await fetch(
`https://www.thecocktaildb.com/api/json/v1/1/filter.php?a=Non_Alcoholic`
)
const data = await res.json()
return data.drinks?.slice(0, 5) || []
}
})
}
})
return result.toDataStreamResponse()
}
Wat gebeurt er?
User: "Ik heb rum en limoen, wat kan ik maken?"
AI denkt: "Ik moet zoeken op rum"
→ Roept searchByIngredient({ ingredient: "rum" }) aan
→ Krijgt: [{ name: "Mojito", id: "11000" }, { name: "Daiquiri", id: "11006" }, ...]
AI denkt: "Mojito klinkt goed met limoen, laat me het recept ophalen"
→ Roept getCocktailDetails({ cocktailId: "11000" }) aan
→ Krijgt: { name: "Mojito", ingredients: [...], instructions: "..." }
AI antwoordt: "Met rum en limoen kun je een heerlijke Mojito maken!
Je hebt nog nodig: verse munt en suiker..."
Deel 3: Agents - Autonome Multi-Step AI
Van Tool Calling naar Agent:
- Tool calling = AI roept 1 tool aan, klaar
- Agent = AI blijft tools aanroepen totdat de taak af is
Het verschil is één parameter: maxSteps
const result = streamText({
model: openai('gpt-4o-mini'),
system: `Je bent een cocktail party planner.
Plan een compleet menu met alle details.`,
messages,
tools: { /* ... tools ... */ },
maxSteps: 8 // ← Agent mag 8 tool-calls doen
})
Voorbeeld: Party Planner Agent
// app/api/party-planner/route.ts
import { openai } from '@ai-sdk/openai'
import { streamText, tool } from 'ai'
import { z } from 'zod'
export async function POST(req: Request) {
const { messages } = await req.json()
const result = streamText({
model: openai('gpt-4o'), // Gebruik slimmer model voor agent taken
system: `Je bent een professionele cocktail party planner.
Wanneer iemand een feest wil plannen:
1. Zoek eerst cocktails die passen bij de wensen
2. Haal recepten op van de beste opties
3. Denk aan non-alcoholische alternatieven
4. Geef een compleet overzicht met ingrediënten
Wees proactief en denk mee.`,
messages,
tools: {
searchByIngredient: tool({
description: 'Zoek cocktails met een ingrediënt',
parameters: z.object({
ingredient: z.string()
}),
execute: async ({ ingredient }) => {
const res = await fetch(
`https://www.thecocktaildb.com/api/json/v1/1/filter.php?i=${ingredient}`
)
return res.json()
}
}),
getCocktailDetails: tool({
description: 'Haal recept details op',
parameters: z.object({
cocktailId: z.string()
}),
execute: async ({ cocktailId }) => {
const res = await fetch(
`https://www.thecocktaildb.com/api/json/v1/1/lookup.php?i=${cocktailId}`
)
return res.json()
}
}),
searchNonAlcoholic: tool({
description: 'Zoek mocktails',
parameters: z.object({}),
execute: async () => {
const res = await fetch(
`https://www.thecocktaildb.com/api/json/v1/1/filter.php?a=Non_Alcoholic`
)
return res.json()
}
}),
searchByCategory: tool({
description: 'Zoek cocktails per categorie (Cocktail, Shot, Beer, etc.)',
parameters: z.object({
category: z.string()
}),
execute: async ({ category }) => {
const res = await fetch(
`https://www.thecocktaildb.com/api/json/v1/1/filter.php?c=${category}`
)
return res.json()
}
})
},
maxSteps: 10 // Agent kan tot 10 tool calls doen
})
return result.toDataStreamResponse()
}
Agent in actie:
User: "Plan cocktails voor mijn verjaardagsfeest.
15 mensen, een paar drinken geen alcohol,
we houden van citrus smaken."
Agent stappen:
1. searchByIngredient("lemon") → 12 cocktails
2. searchByIngredient("lime") → 15 cocktails
3. searchByIngredient("orange") → 10 cocktails
4. searchNonAlcoholic() → 8 mocktails
5. getCocktailDetails("11000") → Mojito recept
6. getCocktailDetails("11007") → Margarita recept
7. getCocktailDetails("12162") → Virgin Piña Colada recept
8. getCocktailDetails("12316") → Lemonade recept
Output: Compleet party plan met:
- 3 alcoholische cocktails met citrus
- 2 mocktails voor niet-drinkers
- Gecombineerde ingrediëntenlijst
- Tips voor bereiding
Gratis APIs voor Projecten
| API | Data | URL | Auth |
|---|---|---|---|
| TheCocktailDB | 636 cocktails, recepten | thecocktaildb.com/api.php | Geen (key=1) |
| TheMealDB | 597 recepten, ingrediënten | themealdb.com/api.php | Geen (key=1) |
| Open Trivia DB | 4000+ quiz vragen | opentdb.com/api_config.php | Geen |
| REST Countries | Landen data | restcountries.com | Geen |
| Open Library | Boeken data | openlibrary.org/developers | Geen |
Best Practices
Tool Design:
// ✅ Goed: Specifieke, duidelijke tools
searchByIngredient: tool({
description: 'Zoek cocktails die een specifiek ingrediënt bevatten',
// ...
})
// ❌ Slecht: Vage tool
search: tool({
description: 'Zoek iets',
// ...
})
Agent System Prompts:
// ✅ Goed: Geef duidelijke instructies
system: `Je bent een cocktail expert.
Wanneer je een vraag krijgt:
1. Zoek eerst relevante cocktails
2. Haal details op van de beste matches
3. Geef persoonlijk advies
Wees proactief en denk mee met de gebruiker.`
// ❌ Slecht: Te vaag
system: `Je bent een assistent.`
Error Handling in Tools:
execute: async ({ ingredient }) => {
try {
const res = await fetch(`...`)
if (!res.ok) {
return { error: 'Kon geen cocktails vinden' }
}
return res.json()
} catch (error) {
return { error: 'API niet beschikbaar' }
}
}
Tools
- Vercel AI SDK (
aipackage) - Zod (parameter validatie)
- Next.js API Routes
- Externe APIs (TheCocktailDB, TheMealDB, etc.)
Lesopdracht (2 uur)
Bouw een AI Agent met Externe Data
Deel 1: Setup (15 min)
npm install ai @ai-sdk/openai zod- Voeg
OPENAI_API_KEYtoe aan.env.local - Kies je API: TheCocktailDB of TheMealDB
Deel 2: Basic Tool Calling (45 min)
- Maak
/api/chat/route.ts - Implementeer 2 tools:
- Zoek op ingrediënt
- Haal details op
- Test: "Wat kan ik maken met [ingrediënt]?"
Deel 3: Agent met maxSteps (45 min)
- Voeg
maxSteps: 5toe - Voeg een 3e tool toe (bijv. zoek per categorie)
- Verbeter je system prompt voor agent gedrag
- Test: "Help me een menu plannen voor..."
Deel 4: Frontend (15 min)
- Bouw chat UI met
useChat - Voeg loading indicator toe
- Test de complete flow
Deliverable
- Werkende agent met minimaal 3 tools
- Chat interface
- Screenshot van agent die meerdere tools aanroept
Huiswerk (2 uur)
Bouw AI Feature voor Eindproject
Deel 1: Agent Design (30 min)
Plan je agent voor de eindopdracht:
- Welke externe API gebruik je?
- Welke tools heeft je agent nodig? (minimaal 3)
- Wat is de typische flow?
Documenteer in docs/AI-DECISIONS.md
Deel 2: Implementatie (1 uur)
Bouw de agent voor je eindproject:
- Minimaal 3 tools
maxStepsvan minimaal 3- Goede error handling
- Relevante system prompt
Deel 3: Integratie (30 min)
Combineer met Supabase:
- Sla user preferences op
- Geef preferences mee als context aan agent
- Sla conversation history op
Deliverable
- Werkende agent in eindproject
docs/AI-DECISIONS.mdmet agent design- Minimaal 5 prompts in
PROMPT-LOG.md
Leerdoelen
Na deze les kan de student:
- Vercel AI SDK installeren en configureren
- Tools definiëren met Zod parameters
- Tool Calling implementeren voor externe API integratie
- Agents bouwen met
maxStepsvoor autonome taken - De juiste aanpak kiezen (basic chat vs tool calling vs agent)
- Error handling implementeren in tools
- Gratis externe APIs integreren in AI features