14 KiB
Les 15 — Lesstof
Agents — LLMs die tools gebruiken in een loop
Vak: AI-Assisted Development Opleiding: NOVI Hogeschool Utrecht Vorige les: Les 12 — Tool Calling Volgende les: Les 16 — MCP servers
Inhoud
- Wat is een agent
- De agent-loop in detail
- ToolLoopAgent — de v6 abstractie
- Stop-condities
- prepareStep — control per stap
- Done-tool pattern
- Planning + reflectie patronen
- Agent vs tool-call vs workflow
- Productie-overwegingen
1. Wat is een agent
De definitie van Vercel AI SDK is kort en goed bruikbaar:
Agents are LLMs that use tools in a loop to accomplish tasks.
Drie ingrediënten:
- LLM — beslist iedere stap wat de volgende actie is. Tool aanroepen of antwoorden in text.
- Tools — uitbreidingen van wat het model kan: zoeken, lezen, schrijven, rekenen, anything.
- Loop — de runtime die LLM en tools afwisselt. Beheert berichten (context) en stop-condities.
Het verschil met Les 12 zit niet in of je een loop hebt, maar in hoe lang die loop draait en hoeveel autonomie het model krijgt. In Les 12 deden we maximaal 5 stappen — kort en gecontroleerd. In Les 15 gaan we naar 20-50 stappen, en het model bepaalt zelf welke tools het in welke volgorde gebruikt.
Voorbeelden van agent-taken
- Research agent — gegeven een vraag, zoek op het web, lees relevante pagina's, schrijf een rapport
- Coding agent — gegeven een bugreport, lees code, draai tests, voer fix door
- Planner — gegeven beschikbaarheid + voorkeuren, plan een vakantie
- Customer support triage — gegeven een ticket, classify, gather context, draft response
In al deze gevallen is de volgorde van stappen niet vooraf bekend. Daarom heeft een agent meer ruimte nodig dan een simpele tool-call.
2. De agent-loop in detail
Wat gebeurt er per iteratie?
┌────────────────────────────────────────┐
│ STAP N: │
│ │
│ 1. Stuur huidige messages naar LLM │
│ 2. LLM antwoordt met: │
│ a) text-only? → loop stopt │
│ b) tool-call(s)? → ga door │
│ 3. Execute(s) de tools (parallel) │
│ 4. Voeg tool-result(s) toe aan msgs │
│ 5. Check stopWhen-conditie │
│ 6. Indien niet gestopt → STAP N+1 │
└────────────────────────────────────────┘
De loop stopt in vier gevallen:
- Natural finish — model genereert text in plaats van tool-call
- stopWhen voldaan — bijv.
stepCountIs(20) - Tool zonder execute aangeroepen — done-pattern
- Tool needs approval — agent vraagt user-confirmation (advanced)
Default:
stepCountIs(20). Dit is een veiligheidsgrens om runaway loops te voorkomen.
3. ToolLoopAgent — de v6 abstractie
In AI SDK v3-v5 schreef je de loop vaak zelf met streamText + stopWhen. In v6 is er een dedicated class: ToolLoopAgent.
Het basis-pattern
import { ToolLoopAgent, tool, stepCountIs } from "ai";
import { z } from "zod";
const researchAgent = new ToolLoopAgent({
model: "openai/gpt-4o",
system: "Je bent een research-assistent. Plan je werk, zoek, lees, vat samen.",
tools: {
webSearch,
readPage,
saveReport,
},
stopWhen: stepCountIs(30),
});
const result = await researchAgent.generate({
prompt: "Schrijf een rapport over AI in de Nederlandse bouwsector",
});
console.log(result.text); // finale antwoord
console.log(result.steps); // array van alle stappen
console.log(result.steps[0].toolCalls); // welke tools werden aangeroepen
console.log(result.steps[0].toolResults); // resultaten daarvan
Waarom ToolLoopAgent?
- Minder boilerplate — geen handmatige while-loop, geen message-array management
- Herbruikbaar — definieer agent 1x, gebruik in API-routes / scripts / queues
- Single source of config — system prompt, tools, model, stop-conditie op één plek
streaming vs generate
// Eenmalig antwoord
const result = await agent.generate({ prompt });
// Voor chat-UI: streaming
const result = agent.stream({ prompt });
return result.toUIMessageStreamResponse(); // streamt steps naar client
4. Stop-condities
De stopWhen parameter bepaalt wanneer de agent klaar is. Vier opties:
4.1 stepCountIs(N) — maximum aantal stappen
stopWhen: stepCountIs(50) // stop na 50 stappen
Bruikbaar als veiligheidsgrens. Default is stepCountIs(20).
4.2 hasToolCall(toolName) — stop bij specifieke tool
stopWhen: hasToolCall("saveReport")
Stop zodra een bepaalde tool is aangeroepen. Handig voor "doe het werk en sla op, dan klaar".
4.3 isLoopFinished() — onbeperkt
stopWhen: isLoopFinished() // GEEN limit — agent stopt alleen natuurlijk
Waarschuwing: zonder limiet kan agent oneindig doorgaan. Alleen gebruiken met andere safeguards (cost limit, timeout).
4.4 Combineren — array van condities
stopWhen: [
stepCountIs(50), // max 50 stappen, of
hasToolCall("submit"), // submit aangeroepen, of
]
Stop zodra één conditie waar is.
4.5 Custom stop-condition
import { StopCondition, ToolSet } from "ai";
const tools = { webSearch, readPage, saveReport } satisfies ToolSet;
const budgetExceeded: StopCondition<typeof tools> = ({ steps }) => {
const tokens = steps.reduce(
(sum, s) => sum + (s.usage?.totalTokens ?? 0),
0
);
return tokens > 50_000; // stop bij 50k tokens
};
new ToolLoopAgent({
tools,
stopWhen: [stepCountIs(30), budgetExceeded],
});
De callback krijgt alle steps tot nu toe. Je kunt op alles stoppen wat je kunt afleiden uit de step-historie.
5. prepareStep — control per stap
prepareStep is een async callback die VOOR elke stap draait. Je kunt dynamisch het model, de tools, de messages of de toolChoice aanpassen.
5.1 Signature
prepareStep: async ({
model, // huidig model
stepNumber, // 0-indexed
steps, // alle vorige steps
messages, // berichten die naar model gaan
}) => {
// return iets — of {} voor "geen wijziging"
return {
model?: ...,
messages?: ...,
activeTools?: [...],
toolChoice?: ...,
};
}
5.2 Dynamic model selection
Begin goedkoop, switch naar duur model voor complex reasoning:
prepareStep: async ({ stepNumber, messages }) => {
if (stepNumber > 2 && messages.length > 10) {
return { model: "anthropic/claude-sonnet-4.5" };
}
return {};
}
5.3 Tool-filtering per fase
prepareStep: async ({ stepNumber }) => {
if (stepNumber <= 3) {
return { activeTools: ["webSearch"], toolChoice: "required" };
}
if (stepNumber <= 6) {
return { activeTools: ["readPage"] };
}
return { activeTools: ["saveReport", "done"], toolChoice: "required" };
}
Forceer een research → read → save flow zonder dat agent kan 'cheaten'.
5.4 Context-trimming
Lange agents genereren veel berichten. Token-budget bewaken:
prepareStep: async ({ messages }) => {
if (messages.length > 20) {
return {
messages: [
messages[0], // system
...messages.slice(-10),// laatste 10
],
};
}
return {};
}
6. Done-tool pattern
Soms wil je dat agent ALTIJD via een tool stopt — bijvoorbeeld omdat je het uiteindelijke antwoord gestructureerd wilt.
const tools = {
webSearch,
readPage,
saveReport,
done: tool({
description: "Roep aan wanneer onderzoek klaar is en rapport is opgeslagen.",
inputSchema: z.object({
finalReportId: z.number(),
summary: z.string(),
}),
// GEEN execute — dit signaleert: stop de loop
}),
};
const agent = new ToolLoopAgent({
model: "openai/gpt-4o",
tools,
toolChoice: "required", // model MOET altijd een tool aanroepen
});
const result = await agent.generate({ prompt });
// Het finale antwoord zit in de done-tool call:
const doneCall = result.staticToolCalls[0];
if (doneCall?.toolName === "done") {
console.log(doneCall.input.summary);
}
Wanneer dit pattern gebruiken:
- Je wilt een gestructureerd eindantwoord (geen vrije text)
- Je wilt voorkomen dat agent tussendoor stopt zonder iets op te slaan
- Combineren met
toolChoice: "required"om text-generatie uit te sluiten
7. Planning + reflectie patronen
7.1 Plan-Act-Reflect (ReAct)
Een populair pattern uit de literatuur:
- Plan — agent schrijft eerst een plan in text (stap 0)
- Act — agent voert plan uit via tools (stap 1-N)
- Reflect — agent kijkt terug, vat samen, evalueert (laatste stap)
In de praktijk komt dit door simpelweg in de system prompt te zetten:
1. Begin met een plan in 3-5 bullets.
2. Voer het plan uit met tools.
3. Aan het eind: schrijf een korte reflectie — wat ging goed, wat zou anders?
Het model doet dit dan vanzelf binnen de loop — geen aparte code nodig.
7.2 Sub-agents
Een agent kan een andere agent aanroepen als tool:
const summarizer = new ToolLoopAgent({
model: "openai/gpt-4o-mini",
system: "Vat één webpagina samen in 5 bullets.",
tools: { readPage },
stopWhen: stepCountIs(3),
});
const summarizePageTool = tool({
description: "Vat een URL samen in bullets",
inputSchema: z.object({ url: z.string().url() }),
execute: async ({ url }) => {
const result = await summarizer.generate({
prompt: `Vat samen: ${url}`,
});
return { summary: result.text };
},
});
const researchAgent = new ToolLoopAgent({
model: "openai/gpt-4o",
tools: { webSearch, summarizePage: summarizePageTool, saveReport },
});
Voordelen:
- Token-budget: sub-agent ziet kleinere context
- Specialisatie: sub-agent kan kleiner/goedkoper model gebruiken
- Modulair: sub-agent te testen los van hoofdagent
7.3 Self-correction
Geef de agent een tool om eigen tussenresultaat te valideren:
const validate = tool({
description: "Check of een rapport feitelijk juist is. Returnt issues.",
inputSchema: z.object({ reportText: z.string() }),
execute: async ({ reportText }) => {
// bv. tweede LLM-call als fact-checker
return { issues: [...] };
},
});
Combineer met system prompt: "Als validate issues teruggeeft, herschrijf en check opnieuw."
8. Agent vs tool-call vs workflow
Niet elke taak hoort bij een agent. Drie niveaus:
| Niveau | Wanneer | Voorbeeld |
|---|---|---|
| Plain tool-call | 1 tool nodig, 1 antwoord | "Wat is het weer in Utrecht?" |
| Multi-step (Les 12) | 2-5 tools, vraag heeft 1 doel | "Hoeveel jazz acts vs rock?" |
| Agent (Les 15) | 10-50 stappen, open-ended | "Onderzoek X en schrijf rapport" |
| Workflow (code) | Reproduceerbaar, audit-trail | Order intake pipeline |
Wanneer GEEN agent
- Determinisme nodig — finance, juridisch, medische data
- Latency — agent gebruikt al snel 30s+
- Voorspelbare kosten — agent met 50 stappen kan duur uitlopen
- Eenvoudige flow — een if-statement is genoeg
Wanneer WEL agent
- Open-ended — research, planning, debugging
- Volgorde onbekend — model bepaalt zelf
- Lange ketens — meerdere bronnen, meerdere stappen
- Latency + kosten acceptabel — async use cases vooral
Quote uit AI SDK docs:
Agents are flexible and powerful, but non-deterministic. When you need reliable, repeatable outcomes with explicit control flow, use core functions with structured workflow patterns.
9. Productie-overwegingen
9.1 Kosten bewaken
Eén onbedoelde loop kan tientallen euro's kosten. Altijd:
stepCountIs(N)als safety cap- Custom
StopConditionop tokens of dollar-amount - Logging per step (welke tool, hoeveel tokens)
const costCap: StopCondition<typeof tools> = ({ steps }) => {
const usd = steps.reduce((s, step) =>
s + estimateCost(step.usage), 0);
return usd > 0.50;
};
9.2 Latency
Een 20-step agent op gpt-4o duurt 30-60 seconden. Voor user-facing:
- Stream steps naar client (
agent.stream()+toUIMessageStreamResponse()) - Toon progress (welke stap, welke tool)
- Loading state met geschatte tijd
9.3 Observability
Log per step:
- Step number
- Tool calls + input
- Tool result (truncated)
- Token usage
- Latency
Dit is essentieel voor debugging — agents zijn niet-deterministisch, dus een specifieke run is vaak niet exact reproduceerbaar.
9.4 Veiligheid
- Write-tools (DB inserts, mails, API calls) altijd met explicit user-intent
- Confirmation UI voor irreversible acties
- Read-only sandbox voor development (Supabase RLS policies open)
- Production: tighter RLS + write-tools authenticated
9.5 Testing
Agents zijn lastig te testen — non-determinisme. Strategieën:
- Mock LLM — test je tools los met fake LLM
- Snapshot tests — log volledige step-trace, vergelijk met goedgekeurde versie
- Eval suites — set van vragen + verwachte tool-sequences
Bronnen
- AI SDK Agents — Overview: https://ai-sdk.dev/docs/agents/overview
- AI SDK Agents — Loop Control: https://ai-sdk.dev/docs/agents/loop-control
- AI SDK Agents — Building Agents: https://ai-sdk.dev/docs/agents/building-agents
- AI SDK Agents — Subagents: https://ai-sdk.dev/docs/agents/subagents
- AI SDK Workflow patterns: https://ai-sdk.dev/docs/agents/workflows
- Tavily Web Search API: https://tavily.com/
- Anthropic — Building effective agents: https://www.anthropic.com/research/building-effective-agents
- ReAct paper: https://arxiv.org/abs/2210.03629