# LangChain và LlamaIndex: web page loader và tool read_url

> WebReaderAPI: một tool read_url và một document loader bằng Python, có RAG index nhỏ dựng từ một lần crawl. Không cần KYC, thanh toán bằng crypto.

- URL: https://webreaderapi.com/vi/docs/langchain/
- Cập nhật lần cuối: 2026-10-22
- Đơn vị phát hành: WebReaderAPI (https://webreaderapi.com)

Hai cách dùng API trong một stack AI Python: tải trang thành document để truy xuất, hoặc cấp cho agent một tool `read_url`. Cùng 20 dòng keo dính phục vụ cả LangChain và LlamaIndex.

**Câu trả lời nhanh — Làm thế nào để tải web page vào LangChain hoặc LlamaIndex dưới dạng Markdown sạch?**

Gọi endpoint `/read` của **WebReaderAPI** và bọc mỗi câu trả lời trong một `Document`: Markdown làm content, `final_url` và tiêu đề làm metadata. Với agent, hãy expose cùng lời gọi đó dưới dạng tool `read_url`. Mỗi trang tốn 1 credit, 2 credit nếu dùng trình duyệt, từ 0,07 US$/1K trang tính đến tháng 10 năm 2026. Không cần package nào của chúng tôi. Không cần KYC; thanh toán bằng crypto.

## Tại sao dùng document Markdown thay vì HTML thô?

Vì truy xuất hoạt động trên văn bản, và Markdown giữ lại cấu trúc quan trọng trong khi bỏ phần còn lại. Tiêu đề được giữ, nên splitter có thể cắt theo chỗ đổi chủ đề. Danh sách và bảng được giữ, nên các bước và thông số kỹ thuật nằm cùng nhau. Thanh điều hướng, banner cookie và quảng cáo biến mất, nên chúng không làm bẩn embedding của bạn hay lãng phí token.

Metadata đến từ cùng lời gọi: `final_url` làm nguồn để trích dẫn, `title`, `published_at` và `language` để lọc.

*reader.py: helper dùng chung (lưu cạnh code của bạn)*

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

```

## Làm thế nào để tạo một LangChain document loader?

Chuyển mỗi trang thành một `langchain_core.documents.Document`, rồi tách theo tiêu đề bằng `MarkdownHeaderTextSplitter` từ package `langchain-text-splitters`. Mỗi chunk giữ đường dẫn tiêu đề trong metadata, và bạn thêm URL nguồn để mỗi câu trả lời có thể dẫn ngược về trang của nó.

Các trang bị bỏ qua, như những trang bị robots.txt từ chối, sẽ raise `PageError` kèm mã; hãy log lại và tiếp tục.

*LangChain: tải và tách trang*

```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 "")

```

## Làm thế nào để cấp cho LangChain agent một tool read_url?

Trang trí một hàm bằng `@tool` từ `langchain_core.tools`. Docstring trở thành mô tả tool mà model đọc, nên hãy nói rõ tool trả về gì và khi nào dùng. Trả về văn bản thuần: một dòng tiêu đề, nguồn và Markdown, cắt bằng `max_chars` cho phù hợp với model của bạn.

Bind tool với bất kỳ chat model nào hỗ trợ tool calling, hoặc truyền nó vào constructor của agent. Model sau đó gọi `read_url` với một URL từ cuộc hội thoại hoặc từ kết quả tìm kiếm.

*LangChain: tool read_url*

```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)

```

## Làm thế nào để làm điều tương tự trong LlamaIndex?

LlamaIndex dùng class `Document` riêng với `text` và `metadata`, và `FunctionTool.from_defaults` để biến một hàm thành tool. Helper vẫn giữ nguyên. Dựng index từ các document bằng `VectorStoreIndex.from_documents`, dùng bất kỳ embedding model nào mà cài đặt LlamaIndex của bạn chỉ định.

*LlamaIndex: index một lần crawl, và một tool read_url*

```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)

```

## Một RAG index nhỏ cần crawl lớn cỡ nào?

Ít hơn bạn tưởng. Tài liệu của một sản phẩm thường chỉ vài trăm trang, và một lần crawl với `include_paths` đặt vào phần docs chỉ đọc đúng phần đó. Với 1 credit mỗi trang, một site docs 300 trang tốn khoảng 300 credit; 10.000 trang (3,75 US$) làm mới nó nhiều lần.

Re-crawl theo lịch khớp với tần suất nguồn thay đổi, và so sánh `final_url` và `word_count` với lần chạy trước để chỉ re-embed những trang đã thay đổi. Credit trả trước không bao giờ hết hạn: số trang chưa dùng vẫn ở trong tài khoản của bạn cho đến khi bạn dùng.

## LangChain và LlamaIndex: câu hỏi thường gặp

### Có package WebReaderAPI cho LangChain hay LlamaIndex không?

Không. Helper và tool trên trang này chính là toàn bộ tích hợp, nên bạn có thể tùy chỉnh thoải mái. WebReaderAPI là một HTTPS API đơn thuần mà bất kỳ HTTP client Python nào cũng gọi được.

### Cái này cần phiên bản LangChain nào?

Bất kỳ phiên bản gần đây nào có `langchain_core`, `langchain-text-splitters` và `init_chat_model`. Tool decorator và class `Document` đã ổn định qua các bản phát hành gần đây.

### Làm thế nào để tránh đọc lại những trang không thay đổi?

Lưu `final_url`, `word_count` và ngày đọc của mỗi lần. Đọc lại theo lịch, và chỉ re-embed một trang khi số từ hoặc content hash của nó thay đổi. Mỗi lần đọc lại tốn một credit, nên hãy chọn nhịp khớp với nguồn.

### Nếu một số trang bị từ chối thì sao?

Chúng raise `PageError` với một mã như `blocked_by_robots`, và không tốn credit. Nếu một website từ chối yêu cầu của chúng tôi, chúng tôi trả lại câu trả lời của trang đó: chúng tôi không bao giờ giải CAPTCHA, đăng nhập vào tài khoản, hay tìm cách vượt qua chặn hoặc paywall.

---

WebReaderAPI: WebReaderAPI là web reader API cho AI agent, chuyển mọi trang web công khai thành Markdown sạch, thanh toán bằng crypto không cần tải lên giấy tờ. Credit trả trước từ 0,07 US$/1K trang không bao giờ hết hạn, và robots.txt được tôn trọng. Không cần KYC — không ID, không selfie, không giấy tờ. Thanh toán bằng crypto.
