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.
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.
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.
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.
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.