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?
Plan
Vraag het model om vier korte zoekopdrachten die samen de vraag dekken.
Zoeken
Voer elke zoekopdracht uit met
read=3, zodat de eerste drie pagina's terugkomen als Markdown.Houd bronnen één keer bij
Bewaar resultaten per URL en sla pagina's over die al zijn verzameld of geen tekst hebben.
Zoek hiaten
Toon het model de tot nu toe gevonden titels en vraag om twee zoekopdrachten over wat ontbreekt.
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_KEYenLLM_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.
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=4laat het model nog twee sets hiaten najagen. - Meer zoekopdrachten per ronde. Verhoog
nin de gap-finding-call van 2 naar 4. - Smallere bronnen. Voeg
include_domainstoe 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=5leest 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_charsof 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 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.