Senza KYC — niente documento, niente selfie, niente documenti. Paga in criptovalute: BTC, USDT, USDC, ETH, SOL, LTC
Lingua: Italiano
Ottieni una chiave API

Docs · LangChain · LlamaIndex

LangChain e LlamaIndex: un web page loader e un tool read_url

Due modi per usare l'API in uno stack AI Python: caricare le pagine come documenti per il retrieval, oppure dare a un agente un tool read_url. Le stesse 20 righe di collante servono per LangChain e LlamaIndex.

Ultimo aggiornamento: · 5 min di lettura

Risposta rapida

Come carico le pagine web in LangChain o LlamaIndex come Markdown pulito?

Chiama l'endpoint /read di WebReaderAPI e avvolgi ogni risposta in un Document: il Markdown come content, final_url e il titolo come metadata. Per gli agenti, esponi la stessa chiamata come tool read_url. Le pagine costano 1 credito ciascuna, 2 con un browser, a partire da 0,07 USD/1K pagine a ottobre 2026. Non serve nessun nostro pacchetto. Senza KYC; paghi in criptovalute.

Perché documenti Markdown invece di HTML grezzo?

Perché il retrieval lavora sul testo, e il Markdown mantiene la struttura che conta scartando il resto. Le intestazioni sopravvivono, così uno splitter può tagliare ai cambi di argomento. Liste e tabelle sopravvivono, così passaggi e specifiche restano insieme. Navigazione, banner dei cookie e pubblicità spariscono, così non inquinano i tuoi embedding né sprecano token.

I metadata arrivano dalla stessa chiamata: final_url come fonte da citare, title, published_at e language per il filtraggio.

reader.py: l'helper condiviso (salvalo accanto al tuo codice)
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

Come costruisco un document loader per LangChain?

Trasforma ogni pagina in un langchain_core.documents.Document, poi dividila per intestazioni con MarkdownHeaderTextSplitter dal pacchetto langchain-text-splitters. Ogni chunk conserva il percorso delle intestazioni nei suoi metadata, e aggiungi l'URL di origine così ogni risposta può rimandare alla sua pagina.

Le pagine saltate, come quelle rifiutate da robots.txt, sollevano PageError con il codice; registrale nei log e continua.

LangChain: carica e dividi le pagine
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 "")

Come do a un agente LangChain un tool read_url?

Decora una funzione con @tool da langchain_core.tools. La docstring diventa la descrizione del tool che il modello legge, quindi indica cosa restituisce il tool e quando usarlo. Restituisci testo semplice: una riga di titolo, la fonte e il Markdown, tagliato con max_chars in base al tuo modello.

Collega il tool a qualsiasi chat model che supporta il tool calling, oppure passalo al costruttore del tuo agente. Il modello chiama poi read_url con un URL preso dalla conversazione o da un risultato di ricerca.

LangChain: il tool read_url
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)

Come faccio lo stesso in LlamaIndex?

LlamaIndex usa la propria classe Document con text e metadata, e FunctionTool.from_defaults per trasformare una funzione in un tool. L'helper resta lo stesso. Costruisci un indice dai documenti con VectorStoreIndex.from_documents, che usa qualunque embedding model nominino le tue impostazioni di LlamaIndex.

LlamaIndex: indicizza un crawling e un tool read_url
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)

Quanto deve essere grande un crawling per un piccolo indice RAG?

Meno di quanto pensi. La documentazione di un prodotto è spesso qualche centinaio di pagine, e un crawling con include_paths impostato sulla sezione dei docs legge solo quelle. A 1 credito a pagina, un sito di docs da 300 pagine costa circa 300 crediti; 10.000 pagine (3,75 USD) lo aggiorna molte volte.

Ripeti il crawling con una cadenza che corrisponde a quanto spesso cambia la fonte, e confronta final_url e word_count con l'esecuzione precedente per ri-incorporare solo le pagine cambiate. I crediti prepagati non scadono mai: le pagine non utilizzate restano sul tuo account finché non le usi.

LangChain e LlamaIndex: domande frequenti

Esiste un pacchetto WebReaderAPI per LangChain o LlamaIndex?

No. L'helper e il tool in questa pagina sono tutta l'integrazione, così puoi adattarli liberamente. WebReaderAPI è un'API HTTPS semplice che qualsiasi client HTTP Python può chiamare.

Quale versione di LangChain serve?

Qualsiasi versione recente con langchain_core, langchain-text-splitters e init_chat_model. Il decoratore del tool e la classe Document sono stati stabili nelle release recenti.

Come evito di rileggere pagine che non sono cambiate?

Salva final_url, word_count e la data di ogni lettura. Rileggi con una cadenza regolare, e ri-incorpora una pagina solo quando il suo conteggio parole o l'hash del contenuto cambia. Ogni rilettura costa un credito, quindi scegli un ritmo che corrisponde alla fonte.

E se alcune pagine vengono rifiutate?

Sollevano PageError con un codice come blocked_by_robots, e non costano nulla. Se un sito rifiuta la nostra richiesta, restituiamo la sua risposta: non risolviamo mai CAPTCHA, non accediamo agli account e non tentiamo di aggirare un blocco o un paywall.

Da 0,07 USD/1K pagine · senza KYC

Ottieni una chiave API