빠른 답변
Python에서 AI 에이전트용 웹 검색 API를 어떻게 호출하나요?
requests 또는 httpx와 Bearer 키를 사용해 검색 쿼리를 https://llmwebsearch.com/api/v1/search에 POST하세요. LLMWebSearch은 상위 3페이지를 Markdown으로 포함한 순위 결과를 반환하며, 번호를 붙여 어떤 LLM에든 전달해 인용 포함 답변을 얻을 수 있습니다. 검색 1회당 1크레딧이며, 2026년 10월 기준 US$1.20/1K 검색부터 시작합니다. Python 3.9+ 환경이면 되고 SDK는 필요 없습니다. KYC 없이 암호화폐로 결제하세요.
필요한 것
| Python | 3.9 이상 |
|---|---|
| 패키지 | 간단한 스크립트에는 requests, 비동기 스크립트에는 httpx |
| 키 | 환경 변수의 LLMWEBSEARCH_API_KEY |
| 답변 루프용 | OpenAI 호환 채팅 endpoint: LLM_BASE_URL, LLM_API_KEY, LLM_MODEL |
| 과금 | 크레딧 1개로 검색 1회를 이용할 수 있습니다. 최대 20개의 순위 결과를 받고, 상위 3개 페이지의 본문을 읽어 Markdown으로 반환합니다. 4~10개 페이지를 읽으면 크레딧 2개가 됩니다. 오류로 끝난 호출에는 비용이 청구되지 않습니다 |
requests로 검색을 한 번 실행하려면 어떻게 하나요?
q과 필요한 옵션으로 JSON 본문을 만들고 POST한 뒤 results을 순회하세요. content을 다루기 전에 content_status를 확인하세요: ok로 표시된 결과만 페이지 텍스트를 포함합니다. 아래 스크립트는 읽은 각 페이지를 위치 순서대로 이름 붙인 Markdown 파일로 저장하며, 첫 줄에는 소스 URL을 넣습니다.
import os
import pathlib
import requests
resp = requests.post(
"https://llmwebsearch.com/api/v1/search",
headers={"Authorization": "Bearer " + os.environ["LLMWEBSEARCH_API_KEY"]},
json={"q": "how to size a postgres connection pool", "max_results": 5, "read": 3},
timeout=60,
)
resp.raise_for_status()
data = resp.json()
out = pathlib.Path("sources")
out.mkdir(exist_ok=True)
for r in data["results"]:
if r["content_status"] != "ok":
print("skip", r["position"], r["content_status"], r["url"])
continue
path = out / f"{r['position']:02d}.md"
path.write_text("Source: " + r["url"] + "\n\n" + r["content"], encoding="utf-8")
print("saved", path, r["word_count"], "words")
print("credits used:", data["credits_used"])
여러 검색을 한 번에 실행하려면 어떻게 하나요?
httpx.AsyncClient과 asyncio.Semaphore를 사용하세요. 세마포어가 동시에 진행 중인 호출 수를 제한해 계정의 분당 한도 이내로 유지하고 부하를 분산합니다. 대부분의 리서치 에이전트에는 4~8개의 동시 호출이 적합합니다. 모든 계정에는 분당 호출 제한이 있으며 대시보드에 표시됩니다. 지원팀에 요청하면 상향할 수 있습니다.
예제는 리서치 에이전트의 일반적인 단계인 하위 질문마다 검색을 하나씩 보내고, 읽은 페이지의 URL을 수집합니다. 각 호출은 독립적이므로 하나가 실패해도 나머지는 계속됩니다.
import asyncio
import os
import httpx
API = "https://llmwebsearch.com/api/v1/search"
HEADERS = {"Authorization": "Bearer " + os.environ["LLMWEBSEARCH_API_KEY"]}
QUESTIONS = [
"what is connection pooling in postgres",
"recommended pool size per cpu core",
"pgbouncer transaction mode limitations",
"connection pool exhaustion symptoms",
]
async def search(client, sem, q):
async with sem:
resp = await client.post(API, json={"q": q, "read": 2, "max_chars": 4000})
if resp.status_code != 200:
return q, resp.json().get("error", {}).get("code"), []
data = resp.json()
read = [r["url"] for r in data["results"] if r["content_status"] == "ok"]
return q, "ok", read
async def main():
sem = asyncio.Semaphore(4)
async with httpx.AsyncClient(headers=HEADERS, timeout=60) as client:
for q, status, urls in await asyncio.gather(*(search(client, sem, q) for q in QUESTIONS)):
print(status, q, urls)
asyncio.run(main())
어떤 LLM에서든 인용 포함 답변을 얻으려면 어떻게 하나요?
먼저 검색한 뒤, 모델에 번호가 붙은 소스를 제공하고 인용하도록 요청하세요. API는 작성된 답변이 아니라 출처를 반환합니다. 자체 모델이 페이지를 읽고 답변을 작성하며 URL을 인용합니다이므로 모델과 인용 스타일을 직접 선택할 수 있습니다. 아래 루프는 OpenAI 호환 chat/completions 형식을 지원하는 모든 채팅 endpoint에서 작동합니다. 주소, 키, 모델은 세 개의 환경 변수로 설정하세요.
컨텍스트를 관리하세요. 스크립트는 토큰당 약 4자를 기준으로 전체가 약 6,000토큰 예산 이내에 머물도록 각 소스를 잘라냅니다. 콘텐츠가 없는 결과는 스니펫으로 표시해 전달하므로 모델이 페이지를 읽은 척하지 않습니다.
import os
import requests
SEARCH = "https://llmwebsearch.com/api/v1/search"
KEY = os.environ["LLMWEBSEARCH_API_KEY"]
BUDGET_CHARS = 6000 * 4 # about 6,000 tokens of sources
def sources_for(question):
resp = requests.post(SEARCH, headers={"Authorization": "Bearer " + KEY},
json={"q": question, "max_results": 5, "read": 3}, timeout=60)
resp.raise_for_status()
results = resp.json()["results"]
share = BUDGET_CHARS // max(len(results), 1)
blocks = []
for i, r in enumerate(results, start=1):
if r["content_status"] == "ok":
body = r["content"][:share]
else:
body = "(snippet only) " + r["snippet"]
blocks.append(f"[{i}] {r['title']} - {r['url']}\n{body}")
return "\n\n".join(blocks)
def answer(question):
prompt = ("Answer the question using only the sources below. "
"Cite them as [1], [2] and so on. Say so if the sources do not answer it.\n\n"
+ sources_for(question) + "\n\nQuestion: " + question)
resp = requests.post(
os.environ["LLM_BASE_URL"].rstrip("/") + "/chat/completions",
headers={"Authorization": "Bearer " + os.environ["LLM_API_KEY"]},
json={"model": os.environ["LLM_MODEL"], "messages": [{"role": "user", "content": prompt}]},
timeout=120,
)
resp.raise_for_status()
return resp.json()["choices"][0]["message"]["content"]
print(answer("How should I size a Postgres connection pool?"))
모델에 얼마나 많은 텍스트를 보내야 하나요?
질문에 답할 만큼만 보내면 됩니다. 각각 2,000토큰으로 자른 3페이지가 보통 전체 10페이지보다 낫습니다: 모델이 더 집중하고 답변 비용도 줄어듭니다. 잘라낼 것을 알고 있다면 검색 자체에서 max_chars을 낮춰 전송되는 데이터를 줄이세요.
대부분의 기사는 핵심이 앞부분에 오므로 페이지 시작부터 자르는 방식이 잘 맞습니다. 긴 참고 문서라면 콘텐츠를 제목별로 나눠 질문과 제목이 일치하는 섹션만 유지하세요.
페이지가 없을 때 코드는 어떻게 해야 하나요?
다른 소스로 계속 진행하세요. blocked_by_robots, blocked_by_site_policy, target_refused로 표시된 페이지는 다시 시도해도 읽히지 않으므로 반복하지 마세요. fetch_timeout 또는 fetch_failed 페이지는 나중에 https://llmwebsearch.com/api/v1/extract을 통해 작동할 수 있습니다.
HTTP 오류는 다릅니다: 429, 502, 504은 대기 시간을 늘려가며 재시도할 가치가 있습니다. errors 페이지에 바로 쓸 수 있는 재시도 헬퍼가 있습니다.
Python: 자주 묻는 질문
LLMWebSearch용 pip 패키지가 있나요?
아니요. API는 일반 HTTPS와 JSON이므로 requests 또는 httpx만 있으면 됩니다. 이 페이지의 스크립트가 곧 통합입니다. 프로젝트에 복사해서 맞게 수정하세요.
이 스크립트를 로컬 모델과 함께 사용할 수 있나요?
네. OpenAI 호환 chat/completions 경로를 제공하는 서버라면 인용 루프와 함께 작동합니다. LLM_BASE_URL을 해당 주소로 설정하고 LLM_MODEL를 그 서버가 기대하는 모델 이름으로 설정하세요.
Python에서 크레딧을 어떻게 계산하나요?
각 응답의 credits_used을 더하거나, 작업 전후에 GET https://llmwebsearch.com/api/v1/credits를 호출하세요. 그 호출은 무료입니다.