fix: update lessons
This commit is contained in:
322
Les13-Agents/Les13-Huiswerk.md
Normal file
322
Les13-Agents/Les13-Huiswerk.md
Normal file
@@ -0,0 +1,322 @@
|
||||
# Les 13 — Huiswerk
|
||||
## Agent uitbreiden + prepareStep + custom stop + AGENT.md
|
||||
|
||||
**Vak:** AI-Assisted Development
|
||||
**Opleiding:** NOVI Hogeschool Utrecht
|
||||
**Deadline:** Voor Les 14 — 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!
|
||||
Reference in New Issue
Block a user