# Les 14 — Huiswerk
## Agent uitbreiden + prepareStep + custom stop + AGENT.md

**Vak:** AI-Assisted Development
**Opleiding:** NOVI Hogeschool Utrecht
**Deadline:** Voor Les 15 — RAG + embeddings
**Inleveren:** GitHub repo + `AGENT.md` in root

---

## Doel

Bouwt voort op de **lesopdracht** (research-agent met 3 tools + `stepCountIs(10)`). In dit huiswerk maak je de agent slimmer: extra tools, dynamic step-control, een eigen stop-conditie, en documenteer je het geheel.

> Niet klaar met de lesopdracht? Eerst die afmaken — het huiswerk heeft die als startpunt nodig.

---

## Onderdeel A — Extra tool (verplicht)

Voeg een 4e tool toe: **`listReports`** om opgeslagen rapporten op te halen.

```typescript
export const listReports = tool({
  description:
    "Toon eerder opgeslagen onderzoeksrapporten. " +
    "Gebruik dit als de gebruiker vraagt naar bestaand onderzoek " +
    "voordat je nieuw onderzoek doet.",
  inputSchema: z.object({
    limit: z.number().int().min(1).max(20).default(5),
  }),
  execute: async ({ limit }) => {
    const { data, error } = await supabase
      .from("research_reports")
      .select("id, query, created_at")
      .order("created_at", { ascending: false })
      .limit(limit);
    if (error) return { error: error.message };
    return data;
  },
});
```

Voeg toe aan `agent.tools` en test:

> "Heb ik eerder iets over AI in de bouw onderzocht? Zo nee, doe het nu."

Verwacht: agent roept eerst `listReports`, dan eventueel `webSearch` → `readPage` → `saveReport`.

### Eisen

- [ ] `listReports` werkt
- [ ] System prompt aangepast — agent weet wanneer te checken
- [ ] Minstens 1 test-query gedraaid die `listReports` triggert

---

## Onderdeel B — prepareStep toevoegen (verplicht)

Voeg een `prepareStep` callback toe aan je `ToolLoopAgent`. Kies **minimaal één** use case:

### Optie B1 — Dynamic model

Begin met `gpt-4o-mini`, switch naar betere model bij complexe stappen.

```typescript
import { openai } from "@ai-sdk/openai";

new ToolLoopAgent({
  model: openai("gpt-4o-mini"),
  tools: { webSearch, readPage, saveReport, listReports },
  prepareStep: async ({ stepNumber, messages }) => {
    if (stepNumber > 4 || messages.length > 12) {
      return { model: openai("gpt-4o") };
    }
    return {};
  },
});
```

### Optie B2 — Fase-gebaseerde tools

```typescript
prepareStep: async ({ stepNumber }) => {
  if (stepNumber <= 2) {
    return { activeTools: ["listReports", "webSearch"] };
  }
  if (stepNumber <= 5) {
    return { activeTools: ["readPage"] };
  }
  return { activeTools: ["saveReport"], toolChoice: "required" };
}
```

### Optie B3 — Context-trimming

```typescript
prepareStep: async ({ messages }) => {
  if (messages.length > 15) {
    return {
      messages: [messages[0], ...messages.slice(-10)],
    };
  }
  return {};
}
```

### Eisen

- [ ] Minimaal 1 `prepareStep` use-case geïmplementeerd
- [ ] Effect zichtbaar in console-logs (welk model / welke tools per stap)
- [ ] In `AGENT.md` uitleggen welke optie gekozen + waarom

---

## Onderdeel C — Custom stop-conditie (verplicht)

Schrijf een eigen `StopCondition`. Twee voorbeelden — kies één of bedenk eigen:

### Optie C1 — Token-budget

```typescript
import type { StopCondition } from "ai";

const tokenBudget: StopCondition<typeof tools> = ({ steps }) => {
  const total = steps.reduce(
    (sum, s) => sum + (s.usage?.totalTokens ?? 0),
    0
  );
  return total > 30_000;
};
```

### Optie C2 — Genoeg sources verzameld

```typescript
const enoughSources: StopCondition<typeof tools> = ({ steps }) => {
  const readPages = steps.flatMap((s) =>
    s.toolCalls?.filter((tc) => tc.toolName === "readPage") ?? []
  );
  return readPages.length >= 5; // 5 pagina's gelezen = genoeg
};
```

Combineren met andere condities:

```typescript
stopWhen: [
  stepCountIs(30),
  tokenBudget,
  hasToolCall("saveReport"),
]
```

### Eisen

- [ ] Custom `StopCondition` werkt
- [ ] Gecombineerd met `stepCountIs(N)` voor veiligheid
- [ ] In `AGENT.md` uitleggen wat de conditie doet

---

## Onderdeel D — `AGENT.md` documentatie (verplicht)

Schrijf in repo-root.

### Sectie 1 — Tools

| Tool | Wat doet 't | Read / Write |
|------|-------------|--------------|
| webSearch | Tavily query → 5 resultaten | Read |
| readPage | Tavily extract → fulltext | Read |
| saveReport | Insert in research_reports | Write |
| listReports | Bestaande rapporten ophalen | Read |
| (eigen tool?) | ... | ... |

### Sectie 2 — Stop-strategie

- `stepCountIs(N)` — safety cap, N = ...
- Custom: ... (uitleg)
- Combinatie waarom: ...

### Sectie 3 — prepareStep keuze

- Welke optie gekozen (B1/B2/B3)
- Waarom
- Wat zie je in de console-logs verschillen tussen "met" en "zonder" prepareStep

### Sectie 4 — Voorbeeld-run

Pak een echte query, draai 'm, en log alle steps. Plak in `AGENT.md`:

```
Query: "Wat zijn de belangrijkste AI-trends in 2026?"

Step 0: webSearch({ query: "AI trends 2026" })
  → 5 resultaten
Step 1: readPage({ url: "..." })
  → 4500 char tekst
Step 2: readPage({ url: "..." })
Step 3: webSearch({ query: "AI regulation EU 2026" })
Step 4: readPage({ url: "..." })
Step 5: saveReport({ ... })
  → reportId: 42

Finale rapport: (200-300 woorden)
```

### Sectie 5 — Eén observatie

Iets wat opviel:
- Onverwacht goed gedrag?
- Loop die te lang duurde?
- Tool die niet werd gekozen?
- Effect van `prepareStep` zichtbaar?

### Vorm

- Max 700 woorden totaal
- Concrete logs, geen abstracte tekst
- Mag wat informeel

---

## Bonus (optioneel)

### Bonus 1 — UI met live steps

`app/page.tsx` — een form + lijst die elke step toont terwijl agent draait.

Gebruik `agent.stream()` ipv `agent.generate()`:

```typescript
const result = researchAgent.stream({ prompt: query });
return result.toUIMessageStreamResponse();
```

Client: tonen welke tool aangeroepen wordt + resultaat collapsed.

### Bonus 2 — Sub-agent voor samenvatten

Maak een 2e `ToolLoopAgent` (kleiner model) die per URL samenvat. Hoofdagent gebruikt deze als tool:

```typescript
const summarizer = new ToolLoopAgent({
  model: openai("gpt-4o-mini"),
  system: "Vat één pagina samen in 5 bullets.",
  tools: { readPage },
  stopWhen: stepCountIs(3),
});

const summarizePage = tool({
  description: "Vat een URL samen",
  inputSchema: z.object({ url: z.string().url() }),
  execute: async ({ url }) => {
    const r = await summarizer.generate({ prompt: `Samenvat: ${url}` });
    return { summary: r.text };
  },
});
```

### Bonus 3 — Done-tool pattern

Vervang `hasToolCall("saveReport")` door een expliciete `done`-tool zonder execute, met `toolChoice: "required"`. Documenteer in `AGENT.md` wat het verschil was.

---

## Inleveren

1. **GitHub repo URL** in Brightspace
2. **`AGENT.md`** in repo-root
3. **Updated `lib/tools.ts`** met `listReports` (+ optionele extra tool)
4. **Updated `lib/agent.ts`** met `prepareStep` + custom stop
5. **Schema-update** in `schema.sql` (mocht je nieuwe tabel hebben)

---

## Beoordeling

| Criterium | Punten |
|-----------|--------|
| A — listReports werkt + agent gebruikt 'm correct | 2 |
| B — prepareStep geïmplementeerd + zichtbaar effect | 2 |
| C — Custom StopCondition werkt | 2 |
| D — AGENT.md compleet met 5 secties | 3 |
| Agent draait end-to-end (geen broken runs) | 1 |
| **Totaal** | **10** |

Voldoende = 6+. Bonus telt mee bij twijfelgevallen.

---

## Tijd-indicatie

| Onderdeel | Tijd |
|-----------|------|
| A — listReports + test | 20 min |
| B — prepareStep | 30 min |
| C — Custom StopCondition + tests | 30 min |
| D — AGENT.md schrijven met logs | 30 min |
| **Totaal** | **~2 uur** |

---

## Veelvoorkomende valkuilen

- **`prepareStep` returnt niks** → je moet `{}` returnen voor "geen wijziging", niet `undefined`
- **Custom stop returnt nooit true** → log `steps.length` en je conditie-waarde per stap
- **Agent stopt te vroeg** → check of je `stopWhen` array niet te restrictief is
- **Sub-agent recursie** → een sub-agent gebruikt zelf weer een sub-agent? Stack-overflow risico — limit op depth
- **Token-cost out of control** → log per stap usage en zet kostencap

---

## Tips

- **Log per stap** — `console.log({ step, toolCalls, usage })` is de beste debug-tool
- **Test isolated** — test je `StopCondition` met fake steps eerst
- **Houd `AGENT.md` bij tijdens werken** — niet aan eind
- **Eindrapport kwaliteit** — als agent rapport een 4 is van 10, ligt het meestal aan system prompt — niet aan tools

Volgende les: RAG + embeddings. Combo met agents: een RAG-tool in een agent. Tot dan!
