빠른 답변
웹페이지를 깔끔한 Markdown으로 LangChain이나 LlamaIndex에 로드하려면 어떻게 하나요?
WebReaderAPI의 /read endpoint를 호출하고 각 응답을 Document로 감싸세요. Markdown은 content로, final_url와 title은 metadata로 넣습니다. 에이전트에는 같은 호출을 read_url 도구로 노출하세요. 페이지당 1 크레딧, 브라우저 사용 시 2 크레딧이며, 2026년 10월 기준 US$0.07/1K 페이지부터입니다. 저희 패키지는 필요 없습니다. KYC 없이 암호화폐로 결제하세요.
원본 HTML 대신 Markdown 문서를 쓰는 이유는 무엇인가요?
검색은 텍스트에서 작동하고, Markdown은 중요한 구조를 유지하면서 나머지는 버리기 때문입니다. 제목(heading)이 유지되므로 스플리터가 주제가 바뀌는 지점에서 나눌 수 있습니다. 목록과 표가 유지되므로 단계와 사양이 함께 남습니다. 내비게이션, 쿠키 배너, 광고는 제거되므로 임베딩을 오염시키거나 토큰을 낭비하지 않습니다.
메타데이터는 같은 호출에서 나옵니다. 인용할 출처로 final_url, 필터링용으로 title, published_at, language을 넣습니다.
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
LangChain 문서 로더는 어떻게 만드나요?
각 페이지를 langchain_core.documents.Document로 바꾼 다음, langchain-text-splitters 패키지의 MarkdownHeaderTextSplitter로 제목 기준으로 나누세요. 각 청크는 메타데이터에 제목 경로를 유지하고, 출처 URL을 추가해 모든 답변이 해당 페이지를 다시 가리킬 수 있게 하세요.
robots.txt에서 거부된 페이지처럼 건너뛴 페이지는 코드와 함께 PageError를 발생시키므로, 로그에 남기고 계속 진행하세요.
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 "")
LangChain 에이전트에 read_url 도구를 제공하려면 어떻게 하나요?
langchain_core.tools의 @tool로 함수를 데코레이션하세요. docstring이 모델이 읽는 도구 설명이 되므로, 도구가 무엇을 반환하는지와 언제 사용해야 하는지를 적으세요. 제목 줄, 출처, Markdown이 담긴 일반 텍스트를 반환하고, 모델에 맞게 max_chars로 잘라내세요.
도구 호출을 지원하는 모든 채팅 모델에 도구를 바인딩하거나, 에이전트 생성자에 전달하세요. 그러면 모델이 대화나 검색 결과에서 가져온 URL로 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)
LlamaIndex에서는 같은 작업을 어떻게 하나요?
LlamaIndex는 text과 metadata를 사용하는 자체 Document 클래스를 쓰고, 함수를 도구로 바꿀 때는 FunctionTool.from_defaults를 사용합니다. 헬퍼는 그대로 유지됩니다. VectorStoreIndex.from_documents로 문서에서 인덱스를 만들며, 이는 LlamaIndex 설정에 지정된 임베딩 모델을 사용합니다.
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)
소규모 RAG 인덱스에는 얼마나 큰 크롤링이 필요한가요?
생각보다 적습니다. 제품 문서는 대개 수백 페이지 정도이고, include_paths를 문서 섹션으로 설정한 크롤링은 해당 섹션만 읽습니다. 페이지당 1 크레딧이면 300페이지 문서 사이트의 비용은 약 300 크레딧이며, 10,000페이지(US$3.75)으로 여러 번 새로고침할 수 있습니다.
출처가 바뀌는 빈도에 맞춰 정기적으로 재크롤링하고, final_url와 word_count를 이전 실행과 비교해 변경된 페이지만 다시 임베딩하세요. 선불 크레딧은 만료되지 않습니다. 사용하지 않은 페이지는 사용할 때까지 계정에 남아 있습니다.
LangChain 및 LlamaIndex: 자주 묻는 질문
LangChain이나 LlamaIndex용 WebReaderAPI 패키지가 있나요?
아니요. 이 페이지의 헬퍼와 도구가 통합의 전부이므로 자유롭게 응용할 수 있습니다. WebReaderAPI는 어떤 Python HTTP 클라이언트든 호출할 수 있는 일반 HTTPS API입니다.
어떤 LangChain 버전이 필요한가요?
langchain_core, langchain-text-splitters, init_chat_model가 있는 최신 버전이면 됩니다. 도구 데코레이터와 Document 클래스는 최근 릴리스에서 안정적으로 유지되어 왔습니다.
변경되지 않은 페이지를 다시 읽지 않으려면 어떻게 하나요?
각 읽기의 final_url, word_count, 날짜를 저장하세요. 정해진 일정에 따라 다시 읽되, 단어 수나 콘텐츠 해시가 바뀐 페이지만 다시 임베딩하세요. 다시 읽을 때마다 1 크레딧이 들므로 출처에 맞는 주기를 선택하세요.
일부 페이지가 거부되면 어떻게 되나요?
blocked_by_robots 같은 코드와 함께 PageError가 발생하며, 크레딧은 차감되지 않습니다. 사이트가 우리 요청을 거부하면 그 응답을 그대로 반환합니다. 우리는 CAPTCHA를 풀거나, 계정에 로그인하거나, 차단이나 페이월을 우회하려 시도하지 않습니다.