# Les 15 — Huiswerk
## Eigen PDF + simpele chat-UI + RAG.md

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

---

## Doel

Bouwt voort op de **lesopdracht** (werkende PDF Q&A app met 1 PDF). In dit huiswerk:

- Index een echte PDF van minimaal 20 pagina's
- Maak een simpele upload + chat-UI
- Documenteer 5 vragen + 1 fail-case in `RAG.md`

---

## Onderdeel A — Echte PDF + UI (verplicht)

### Stap 1 — Kies een PDF

Kies een PDF van minimaal 20 pagina's, iets dat jou interesseert:
- Studieboek-hoofdstuk
- Open-source whitepaper (LLaMA, RAG-papers)
- Bedrijfsjaarverslag
- Wikipedia-artikel als PDF geprint
- Game manual

Moet text-based zijn (geen scan).

### Stap 2 — Upload-UI

`app/page.tsx` — file upload + lijst van geïndexeerde docs:

```tsx
"use client";
import { useState } from "react";

export default function Page() {
  const [file, setFile] = useState<File | null>(null);
  const [indexing, setIndexing] = useState(false);
  const [result, setResult] = useState<string>("");

  async function handleUpload() {
    if (!file) return;
    setIndexing(true);
    const fd = new FormData();
    fd.append("file", file);
    const res = await fetch("/api/index", { method: "POST", body: fd });
    const data = await res.json();
    setResult(`${data.chunks} chunks geïndexeerd`);
    setIndexing(false);
  }

  return (
    <main className="max-w-2xl mx-auto p-8">
      <h1 className="text-3xl font-bold mb-6">PDF Q&A</h1>
      <input type="file" accept=".pdf"
        onChange={(e) => setFile(e.target.files?.[0] ?? null)} />
      <button onClick={handleUpload} disabled={indexing}
        className="ml-3 bg-blue-600 text-white px-4 py-2 rounded">
        {indexing ? "..." : "Index"}
      </button>
      {result && <p className="mt-4 text-green-600">{result}</p>}

      <ChatBox />
    </main>
  );
}
```

### Stap 3 — Chat-interface (simpel)

Geen useChat / streaming nodig — gewoon een form + lijst van vragen+antwoorden:

```tsx
"use client";
import { useState } from "react";

export function ChatBox() {
  const [q, setQ] = useState("");
  const [history, setHistory] = useState<{q: string; a: string}[]>([]);
  const [loading, setLoading] = useState(false);

  async function ask() {
    if (!q.trim()) return;
    setLoading(true);
    const res = await fetch("/api/ask", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ question: q }),
    });
    const data = await res.json();
    setHistory((h) => [...h, { q, a: data.answer }]);
    setQ("");
    setLoading(false);
  }

  return (
    <div className="mt-8">
      <div className="flex gap-2 mb-4">
        <input value={q} onChange={(e) => setQ(e.target.value)}
          placeholder="Stel een vraag..."
          className="flex-1 border p-2 rounded" />
        <button onClick={ask} disabled={loading}
          className="bg-blue-600 text-white px-4 py-2 rounded">
          {loading ? "..." : "Vraag"}
        </button>
      </div>
      <ul className="space-y-3">
        {history.map((item, i) => (
          <li key={i} className="border rounded p-3">
            <p className="font-semibold">Q: {item.q}</p>
            <p className="text-gray-700 mt-1">{item.a}</p>
          </li>
        ))}
      </ul>
    </div>
  );
}
```

### Eisen

- [ ] Upload werkt — file input + index endpoint call
- [ ] Geïndexeerde docs zichtbaar (chunks count)
- [ ] Chat-interface werkt (vraag/antwoord)
- [ ] Antwoorden zijn duidelijk gebaseerd op PDF-inhoud

---

## Onderdeel B — `RAG.md` documentatie (verplicht)

Schrijf in repo-root.

### Sectie 1 — Document info

- Bestandsnaam + onderwerp
- Aantal pagina's
- Aantal chunks na indexing
- Chunk-size + overlap die je gebruikt hebt

### Sectie 2 — 5 succesvolle vragen

Voor elk:

```markdown
**Vraag 1:** "Wat zijn de drie hoofdkenmerken van X?"

**Antwoord (samenvatting):** ...

**Top chunks (top 2-3):**
- Source page 12 — "X heeft drie kenmerken..."
- Source page 14 — "Het derde kenmerk..."
```

### Sectie 3 — 1 fail-case

Een vraag waar RAG het **niet goed deed**:

```markdown
**Vraag:** "Wat staat er over Y?"

**Antwoord (incorrect):** "Y wordt niet besproken in het document."

**Wat ging mis:** Y wordt wel besproken op pagina 8, maar onder een ander
woord (Z). Embedding van "Y" was te ver van embedding van paragraaf over "Z".

**Wat zou helpen:** Synoniem-expansion in de query, of hybrid search met keyword.
```

### Sectie 4 — Eén observatie

Iets wat opviel:
- Welke vragen werken het best?
- Hoe lang duurt index van een PDF van X pagina's?
- Verschil tussen vage en specifieke vragen?

### Vorm

- Max 500 woorden totaal
- Concrete cijfers + voorbeelden
- Mag wat informeel

---

## Bonus (optioneel)

### Bonus 1 — Streaming antwoorden

Gebruik `streamText` in plaats van `generateText` voor word-by-word output.

### Bonus 2 — Source-citations in UI

Toon naast elk antwoord de source-page-nummers van de top chunks.

### Bonus 3 — Multi-document

Index 2-3 verschillende PDFs. Documenteer hoe AI ermee omgaat.

---

## Inleveren

1. **GitHub repo URL** in Brightspace
2. **`RAG.md`** in repo-root (4 secties)
3. **Working app** lokaal demonstreerbaar (`pnpm dev`)

---

## Beoordeling

| Criterium | Punten |
|-----------|--------|
| A — UI + indexing + chat werkend | 5 |
| B — RAG.md compleet (4 secties) | 3 |
| Fail-case analyse is concreet | 1 |
| Antwoorden zijn echt gebaseerd op PDF | 1 |
| **Totaal** | **10** |

Voldoende = 6+. Bonus telt mee bij twijfelgevallen.

---

## Tijd-indicatie

| Onderdeel | Tijd |
|-----------|------|
| A — UI + chat | 45 min |
| B — RAG.md schrijven (incl. testen) | 30 min |
| **Totaal** | **~1,5 uur** |

---

## Veelvoorkomende valkuilen

| Probleem | Oplossing |
|----------|-----------|
| Scan-PDF — geen text | unpdf werkt niet op image-PDFs. Kies andere PDF |
| Vector dimension mismatch | Index én query moeten zelfde model gebruiken |
| Top-1 is altijd irrelevant | Chunks te klein, of vraag te abstract — experimenteer |
| Cost-pieken bij grote PDF | Indexing per chunk-batch van 100 ipv all-at-once |
| RAG hallucineert | Prompt strikter: "Als info niet in context staat, zeg dat" |

---

## Tips

- **Test met simpele vragen eerst** — zorg dat basis werkt voor complexer wordt
- **Log similarity scores** — onder 0.4 is meestal niet relevant
- **Eén goede fail-case is meer waard dan 10 succesvolle**
- **Houd het simpel** — geen agents, geen multi-step. Just RAG.

Volgende les: MCP — Model Context Protocol. Tot dan!
