# LangChain en LlamaIndex: een webpagina-loader en een read_url-tool

> LangChain en LlamaIndex webpagina-loader: read_url-tool, document loader en kleine RAG-index uit een crawl, in Python. Zonder KYC, betalen met crypto.

- URL: https://webreaderapi.com/nl/docs/langchain/
- Laatst bijgewerkt: 2026-10-22
- Uitgever: WebReaderAPI (https://webreaderapi.com)

Twee manieren om de API te gebruiken in een Python AI-stack: pagina's laden als documenten voor retrieval, of een agent een `read_url`-tool geven. Dezelfde 20 regels glue dienen zowel LangChain als LlamaIndex.

**Kort antwoord — Hoe laad ik webpagina's in LangChain of LlamaIndex als schone Markdown?**

Roep het `/read`-endpoint van **WebReaderAPI** aan en verpak elk antwoord in een `Document`: de Markdown als content, `final_url` en de titel als metadata. Voor agents exposeer je dezelfde call als een `read_url`-tool. Pagina's kosten 1 credit per stuk, 2 met een browser, vanaf US$ 0,07/1K pagina's per oktober 2026. Je hebt geen package van ons nodig. Zonder KYC; betalen met crypto.

## Waarom Markdown-documenten in plaats van ruwe HTML?

Omdat retrieval op tekst werkt, en Markdown de structuur behoudt die ertoe doet terwijl de rest wegvalt. Koppen blijven behouden, zodat een splitter kan knippen op onderwerpwisselingen. Lijsten en tabellen blijven behouden, zodat stappen en specificaties bij elkaar blijven. Navigatie, cookiebanners en advertenties zijn weg, zodat ze je embeddings niet vervuilen of tokens verspillen.

De metadata komt uit dezelfde call: `final_url` als bron om te citeren, `title`, `published_at` en `language` voor filteren.

*reader.py: de gedeelde helper (sla het op naast je code)*

```python
import os

import requests

API = "https://webreaderapi.com/api/v1/read"
HEADERS = {"Authorization": "Bearer " + os.environ["WEBREADER_API_KEY"]}


class PageError(Exception):
    pass


def read_page(url, **options):
    resp = requests.get(API, headers=HEADERS, params={"url": url, **options}, timeout=75)
    body = resp.json()
    if resp.status_code != 200:
        raise PageError(body["error"]["code"])
    return body

```

## Hoe bouw ik een LangChain document loader?

Zet elke pagina om in een `langchain_core.documents.Document` en splits die vervolgens op koppen met `MarkdownHeaderTextSplitter` uit het `langchain-text-splitters`-package. Elk chunk behoudt het kop-pad in zijn metadata, en je voegt de bron-URL toe zodat elk antwoord terug kan wijzen naar zijn pagina.

Overgeslagen pagina's, zoals die geweigerd door robots.txt, gooien een `PageError` op met de code; log ze en ga verder.

*LangChain: pagina's laden en splitsen*

```python
from langchain_core.documents import Document
from langchain_text_splitters import MarkdownHeaderTextSplitter

from reader import PageError, read_page

splitter = MarkdownHeaderTextSplitter(headers_to_split_on=[("#", "h1"), ("##", "h2"), ("###", "h3")])


def load_pages(urls):
    chunks = []
    for url in urls:
        try:
            page = read_page(url)
        except PageError as exc:
            print("skipped", url, exc)
            continue
        for part in splitter.split_text(page["markdown"]):
            part.metadata.update({"source": page["final_url"], "title": page.get("title", "")})
            chunks.append(part)
    return chunks


docs = load_pages(["https://example.com/docs/install", "https://example.com/docs/configure"])
print(len(docs), "chunks", isinstance(docs[0], Document) if docs else "")

```

## Hoe geef ik een LangChain-agent een read_url-tool?

Versier een functie met `@tool` uit `langchain_core.tools`. De docstring wordt de tool-beschrijving die het model leest, dus zeg wat de tool teruggeeft en wanneer je hem gebruikt. Geef platte tekst terug: een titelregel, de bron en de Markdown, afgekapt met `max_chars` passend bij je model.

Bind de tool aan elk chatmodel dat tool calling ondersteunt, of geef hem door aan je agent-constructor. Het model roept dan `read_url` aan met een URL uit het gesprek of uit een zoekresultaat.

*LangChain: de read_url-tool*

```python
import os

from langchain.chat_models import init_chat_model
from langchain_core.tools import tool

from reader import PageError, read_page


@tool
def read_url(url: str, max_chars: int = 20000) -> str:
    """Read a public web page and return its main content as Markdown, with its title and source URL."""
    try:
        page = read_page(url, max_chars=max_chars)
    except PageError as exc:
        return "Could not read the page: " + str(exc)
    return "# " + (page.get("title") or url) + "\nSource: " + page["final_url"] + "\n\n" + page["markdown"]


model = init_chat_model(os.environ.get("CHAT_MODEL", "openai:gpt-4.1-mini")).bind_tools([read_url])
reply = model.invoke("Summarise https://example.com/blog/first-post in three bullet points.")
print(reply.tool_calls)

```

## Hoe doe ik hetzelfde in LlamaIndex?

LlamaIndex gebruikt zijn eigen `Document`-klasse met `text` en `metadata`, en `FunctionTool.from_defaults` om een functie om te zetten in een tool. De helper blijft hetzelfde. Bouw een index van de documenten met `VectorStoreIndex.from_documents`, dat gebruikmaakt van welk embedding-model je LlamaIndex-instellingen ook noemen.

*LlamaIndex: een crawl indexeren, en een read_url-tool*

```python
import os
import time

import requests
from llama_index.core import Document, VectorStoreIndex
from llama_index.core.tools import FunctionTool

from reader import HEADERS, PageError, read_page


def crawl_pages(start_url, limit=200):
    job = requests.post("https://webreaderapi.com/api/v1/crawl", headers=HEADERS, json={"url": start_url, "limit": limit}, timeout=30).json()
    pages, cursor = [], None
    while True:
        params = {"cursor": cursor} if cursor else {}
        res = requests.get("https://webreaderapi.com/api/v1/jobs/" + job["job_id"], headers=HEADERS, params=params, timeout=30).json()
        pages += [r for r in res["results"] if "error" not in r]
        cursor = res.get("next_cursor")
        if not cursor and res["status"] == "done":
            return pages
        if not cursor:
            time.sleep(5)


pages = crawl_pages("https://docs.example.com/")
docs = [Document(text=p["markdown"], metadata={"source": p["final_url"], "title": p.get("title", "")}) for p in pages]
index = VectorStoreIndex.from_documents(docs)
print(index.as_query_engine().query("How do I install the product?"))


def read_url(url: str, max_chars: int = 20000) -> str:
    """Read a public web page and return its main content as Markdown."""
    try:
        return read_page(url, max_chars=max_chars)["markdown"]
    except PageError as exc:
        return "Could not read the page: " + str(exc)


read_tool = FunctionTool.from_defaults(fn=read_url, name="read_url")
print(os.environ.get("CHAT_MODEL", "set CHAT_MODEL for your agent"), read_tool.metadata.name)

```

## Hoe groot moet een crawl zijn voor een kleine RAG-index?

Kleiner dan je misschien denkt. De documentatie van een product is vaak een paar honderd pagina's, en een crawl met `include_paths` ingesteld op de docs-sectie leest alleen die. Bij 1 credit per pagina kost een docs-site van 300 pagina's ongeveer 300 credits; 10.000 pagina's (US$ 3,75) ververst hem vele malen.

Her-crawl volgens een schema dat past bij hoe vaak de bron verandert, en vergelijk `final_url` en `word_count` met de vorige run om alleen de gewijzigde pagina's opnieuw te embedden. Prepaid credits verlopen nooit: ongebruikte pagina's blijven op je account staan tot je ze gebruikt.

## LangChain en LlamaIndex: veelgestelde vragen

### Is er een WebReaderAPI-package voor LangChain of LlamaIndex?

Nee. De helper en tool op deze pagina zijn de hele integratie, dus je kunt ze vrij aanpassen. WebReaderAPI is een gewone HTTPS-API die elke Python HTTP-client kan aanroepen.

### Welke LangChain-versie is hiervoor nodig?

Elke recente met `langchain_core`, `langchain-text-splitters` en `init_chat_model`. De tool-decorator en de `Document`-klasse zijn stabiel gebleven over recente releases.

### Hoe voorkom ik dat ik pagina's opnieuw lees die niet zijn veranderd?

Sla `final_url`, `word_count` en de datum van elke read op. Lees opnieuw volgens een schema, en embed een pagina alleen opnieuw wanneer het woordenaantal of de content-hash verandert. Elke her-read kost een credit, dus kies een ritme dat past bij de bron.

### Wat als sommige pagina's geweigerd worden?

Ze gooien een `PageError` op met een code zoals `blocked_by_robots`, en ze kosten niets. Als een site ons verzoek weigert, geven we het antwoord terug: we lossen nooit CAPTCHA's op, loggen niet in op accounts en proberen nooit een blokkade of paywall te omzeilen.

---

WebReaderAPI: WebReaderAPI is een web reader-API voor AI-agents die elke openbare webpagina omzet in schone Markdown, betaald met crypto zonder ID te uploaden. Prepaid credits vanaf US$ 0,07/1K pagina's verlopen nooit, en robots.txt wordt gerespecteerd. Zonder KYC — geen ID, geen selfie, geen documenten. Betalen met crypto.
