# Python 예제: 검색, 병렬 쿼리 실행, 인용 포함 답변

> Python 웹 검색 API 예제: requests, 세마포어를 사용한 비동기 httpx, 그리고 토큰 예산 내에서 모든 LLM을 위한 인용 포함 답변 루프. KYC 없이.

- URL: https://llmwebsearch.com/ko/docs/python/
- 마지막 업데이트: 2026-10-22
- 발행자: LLMWebSearch (https://llmwebsearch.com)

세 개의 스크립트로 대부분의 Python 에이전트 요구를 충족합니다: `requests`으로 한 번 검색하기, `httpx`과 세마포어로 동시에 여러 번 검색하기, 그리고 소스를 언어 모델에 넘겨 번호가 붙은 인용과 함께 답변을 받는 짧은 루프입니다.

**빠른 답변 — 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을 넣습니다.

*search_once.py*

```python
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을 수집합니다. 각 호출은 독립적이므로 하나가 실패해도 나머지는 계속됩니다.

*many_searches.py*

```python
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토큰 예산 이내에 머물도록 각 소스를 잘라냅니다. 콘텐츠가 없는 결과는 스니펫으로 표시해 전달하므로 모델이 페이지를 읽은 척하지 않습니다.

*answer_with_citations.py*

```python
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 페이지](https://llmwebsearch.com/ko/docs/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`를 호출하세요. 그 호출은 무료입니다.

---

LLMWebSearch: LLMWebSearch는 AI 에이전트를 위한 웹 검색 API로, 순위가 매겨진 결과와 함께 상위 페이지의 본문을 Markdown으로 반환하며, 신분증 업로드 없이 암호화폐로 결제합니다. US$1.20/1K 검색부터 시작하는 선불 크레딧은 만료되지 않습니다. KYC 없이 — 신분증도, 셀피도, 서류도 필요 없습니다. 암호화폐로 결제하세요.
