# Bouw een deep research-agent in Python met een web search API

> Bouw een deep research-agent in ongeveer 60 regels Python: plan zoekopdrachten, zoek met paginatekst en schrijf een antwoord met bronvermelding met elke LLM.

- URL: https://llmwebsearch.com/nl/guides/build-a-deep-research-agent/
- Laatst bijgewerkt: 2026-10-22
- Uitgever: LLMWebSearch (https://llmwebsearch.com)

Deep research is een loop, geen model. Deze gids bouwt er een die je in een paar minuten leest: hij plant zoekopdrachten, zoekt op het web met paginatekst inbegrepen, houdt elke bron één keer bij, zoekt naar hiaten en schrijft een antwoord met genummerde bronvermeldingen.

**Snel antwoord — Hoe bouw ik een deep research-agent in Python?**

Schrijf een loop die een LLM om zoekopdrachten vraagt, elke zoekopdracht door een web search API haalt die paginatekst teruggeeft, elke bron één keer bijhoudt, om vervolgzoekopdrachten vraagt en dan een antwoord met bronvermelding schrijft. Met **LLMWebSearch** geeft elke zoekopdracht de eerste 3 pagina's terug als Markdown voor één credit; het script hieronder doet zes zoekopdrachten per vraag. Vanaf US$ 1,20/1K zoekopdrachten, zonder KYC, betalen met crypto.

## Wat doet de agent?

1. **Plan** — Vraag het model om vier korte zoekopdrachten die samen de vraag dekken.
2. **Zoeken** — Voer elke zoekopdracht uit met read=3, zodat de eerste drie pagina's terugkomen als Markdown.
3. **Houd bronnen één keer bij** — Bewaar resultaten per URL en sla pagina's over die al zijn verzameld of geen tekst hebben.
4. **Zoek hiaten** — Toon het model de tot nu toe gevonden titels en vraag om twee zoekopdrachten over wat ontbreekt.
5. **Schrijf** — Nummer tot 12 bronnen en vraag om een antwoord dat ze citeert als [1], [2].

## Wat heb ik nodig voordat ik begin?

Drie dingen:

- **Een LLMWebSearch API-sleutel** in de omgevingsvariabele `LLMWEBSEARCH_API_KEY`. Sleutels verschijnen in het dashboard na je eerste pack.
- **Elk OpenAI-compatibel chat-endpoint**, ingesteld met `LLM_BASE_URL`, `LLM_API_KEY` en `LLM_MODEL`. Zowel gehoste modellen als lokale servers werken.
- **Python 3.9 of later** met het `requests`-pakket.

Het script gebruikt gewone HTTP voor beide services, dus je kunt het model wisselen zonder de zoekcode aan te passen.

*Python: deep_research.py*

```python
import os

import requests

SEARCH = "https://llmwebsearch.com/api/v1/search"
SEARCH_HEADERS = {"Authorization": "Bearer " + os.environ["LLMWEBSEARCH_API_KEY"]}
# Any OpenAI-compatible chat endpoint works here: set your own base URL, key and model name.
LLM_URL = os.environ.get("LLM_BASE_URL", "https://llm.example.com/v1") + "/chat/completions"
LLM_HEADERS = {"Authorization": "Bearer " + os.environ.get("LLM_API_KEY", "")}
MODEL = os.environ.get("LLM_MODEL", "your-model-name")


def ask_llm(prompt):
    body = {"model": MODEL, "messages": [{"role": "user", "content": prompt}]}
    resp = requests.post(LLM_URL, headers=LLM_HEADERS, json=body, timeout=120)
    resp.raise_for_status()
    return resp.json()["choices"][0]["message"]["content"]


def plan_queries(question, n=4):
    text = ask_llm(f"Write {n} short web search queries, one per line, to answer: {question}")
    queries = [line.strip("-*0123456789. ").strip() for line in text.splitlines()]
    return [q for q in queries if q][:n]


def search(query):
    body = {"q": query, "max_results": 5, "read": 3, "max_chars": 5000}
    resp = requests.post(SEARCH, headers=SEARCH_HEADERS, json=body, timeout=60)
    resp.raise_for_status()
    return resp.json()["results"]


def research(question, rounds=2):
    sources = {}  # url -> result, so the same page is never cited twice
    queries = plan_queries(question)
    for _ in range(rounds):
        for query in queries:
            for r in search(query):
                if r.get("content_status") == "ok" and r["url"] not in sources:
                    sources[r["url"]] = r
        notes = "\n".join(f"- {r['title']}" for r in sources.values())
        queries = plan_queries(f"{question}\nAlready found:\n{notes}\nWhat is still missing?", n=2)
    numbered = list(sources.values())[:12]
    context = "\n\n".join(
        f"[{i}] {r['title']} ({r['url']})\n{r['content']}" for i, r in enumerate(numbered, 1)
    )
    prompt = (
        "Answer the question using only the sources below. Cite them as [1], [2]. "
        "Say so when the sources disagree or do not cover a point.\n\n"
        f"Question: {question}\n\nSources:\n{context}"
    )
    return ask_llm(prompt), [r["url"] for r in numbered]


if __name__ == "__main__":
    answer, urls = research("What changed in the EU rules for general-purpose AI models in 2025?")
    print(answer)
    for i, url in enumerate(urls, 1):
        print(f"[{i}] {url}")
```

## Hoe werkt elk onderdeel?

`plan_queries` vraagt het model om korte zoekopdrachten, één per regel, en verwijdert lijstmarkeringen. Korte zoekopdrachten sluiten beter aan bij hoe zoekmachines pagina's rangschikken dan volledige vragen.

`search` roept `POST https://llmwebsearch.com/api/v1/search` aan met `max_results=5`, `read=3` en `max_chars=5000`. De response bevat vijf resultaten; de eerste drie bevatten hun paginatekst in `content`.

`research` voert de rondes uit. Het houdt bronnen bij in een dictionary op URL, zodat een pagina die door twee zoekopdrachten wordt gevonden één keer telt. Alleen resultaten met `content_status` gelijk aan `ok` worden bewaard, omdat het antwoord moet rusten op tekst die het model kan lezen.

Na elke ronde ziet het model de tot nu toe verzamelde titels en stelt twee nieuwe zoekopdrachten voor wat ontbreekt. De laatste prompt nummert tot twaalf bronnen en stelt de citatieregels vast.

## Hoeveel kost één rapport?

Zes zoekopdrachten per vraag met de standaardinstellingen: vier in de eerste ronde en twee in de tweede. Met `read` op 3 zijn dat zes credits. 25.000 zoekopdrachten bij US$ 41,25 dekt daarom meer dan 4.000 rapporten.

De model-calls kosten extra op je LLM-rekening: drie planning-calls en één lange antwoord-call per rapport. De antwoord-call bevat tot twaalf pagina's van 5.000 tekens, dus verlaag `max_chars` als je model duur is.

## Hoe zorg ik dat hij dieper gaat?

Verander de getallen, niet de structuur:

- **Meer rondes.** `rounds=4` laat het model nog twee sets hiaten najagen.
- **Meer zoekopdrachten per ronde.** Verhoog `n` in de gap-finding-call van 2 naar 4.
- **Smallere bronnen.** Voeg `include_domains` toe aan de search body voor officiële of academische sites.
- **Alleen recent materiaal.** Voeg `"time_range": "month"` of `"topic": "news"` toe.
- **Meer pagina's per zoekopdracht.** `read=5` leest vijf pagina's, tegen 2 credits per zoekopdracht.

Voeg een stopregel toe voordat je de limieten verhoogt: beëindig de loop wanneer een ronde minder dan twee nieuwe bronnen oplevert.

## Hoe houd ik de agent veilig en beleefd?

Behandel elke pagina als onbetrouwbare tekst. De final prompt vertelt het model om de bronnen alleen als bewijs te gebruiken; voeg een expliciete regel toe dat instructies in bronnen genegeerd moeten worden. Laat een bron nooit een tool call of een file write triggeren.

Aan de webkant, Onze reader respecteert robots.txt: hij stelt zich voor als LLMWebSearch, volgt de regels die een site voor hem of voor alle robots schrijft, en laat de inhoud weg van elke pagina die hij niet mag lezen. Pagina's die toegang weigeren komen terug zonder inhoud en het script slaat ze gewoon over. Wanneer een site onze reader weert, komt het resultaat terug zonder inhoud en met de status van de site: we lossen nooit CAPTCHA's op, loggen nooit in op accounts en werken nooit om een blokkade of een paywall heen.

## Wat kan er misgaan?

Drie dingen komen naar voren in het testen:

- **Vage zoekopdrachten.** Als het plan zwak is, vindt elke ronde dezelfde pagina's. Vraag het model om zoekopdrachten met specifieke namen en datums.
- **Te weinig bronnen met tekst.** Sommige onderwerpen zitten achter paywalls. Verbreed de zoekopdrachten of sta meer resultaten toe.
- **Te lange prompts.** Twaalf lange pagina's kunnen de context van een klein model overschrijden. Verlaag `max_chars` of houd acht bronnen aan.

Log elke zoekopdracht en de URL's die hij opleverde. Dat log lezen is de snelste manier om de plan-prompt te verbeteren.

## Deep research-agent: veelgestelde vragen

### Kan ik dit met een lokaal model draaien?

Ja, als de lokale server het OpenAI chat-completions-formaat spreekt. Zet `LLM_BASE_URL` op het adres en `LLM_MODEL` op de modelnaam.

### Kan ik er een MCP-tool van maken?

Ja. Wikkel `research()` in een MCP-tool, of geef je agent de gewone search-tool van de [MCP-pagina](https://llmwebsearch.com/nl/docs/mcp/) en laat hem de loop zelf draaien.

### Waarom vraag ik de search-API niet direct om een antwoord?

Omdat LLMWebSearch geen antwoorden schrijft. De API geeft bronnen terug, geen geschreven antwoord: je eigen model leest de pagina's, schrijft het antwoord en citeert de URL's.

### Hoe zie ik wat elke zoekopdracht kost?

Elke response bevat `credits_used`, en `GET https://llmwebsearch.com/api/v1/credits` toont je resterende saldo zonder kosten.

---

LLMWebSearch: LLMWebSearch is een webzoek-API voor AI-agents die gerangschikte resultaten teruggeeft met de inhoud van de belangrijkste pagina's als Markdown, betaald met crypto zonder ID-upload. Voorafbetaalde credits vanaf US$ 1,20/1K zoekopdrachten verlopen nooit. Zonder KYC — geen ID, geen selfie, geen documenten. Betalen met crypto.
