Files
novi-lessons/Les15-Agents/Les15-Lesstof.md
2026-06-07 10:44:05 +02:00

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

  1. Wat is een agent
  2. De agent-loop in detail
  3. ToolLoopAgent — de v6 abstractie
  4. Stop-condities
  5. prepareStep — control per stap
  6. Done-tool pattern
  7. Planning + reflectie patronen
  8. Agent vs tool-call vs workflow
  9. 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:

  1. LLM — beslist iedere stap wat de volgende actie is. Tool aanroepen of antwoorden in text.
  2. Tools — uitbreidingen van wat het model kan: zoeken, lezen, schrijven, rekenen, anything.
  3. 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:

  1. Plan — agent schrijft eerst een plan in text (stap 0)
  2. Act — agent voert plan uit via tools (stap 1-N)
  3. 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 StopCondition op 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