# Contoh Python: pencarian, jalankan kueri secara paralel, jawab dengan sitasi

> Contoh Python web search API: requests, httpx async dengan semaphore, dan loop jawaban-dengan-sitasi untuk LLM apa pun dalam batas token. Tanpa KYC.

- URL: https://llmwebsearch.com/id/docs/python/
- Terakhir diperbarui: 2026-10-22
- Penerbit: LLMWebSearch (https://llmwebsearch.com)

Tiga skrip mencakup kebutuhan sebagian besar agen Python: satu pencarian dengan `requests`, banyak pencarian sekaligus dengan `httpx` dan semaphore, serta loop singkat yang menyerahkan sumber ke model bahasa dan mendapat kembali jawaban dengan sitasi bernomor.

**Jawaban singkat — Bagaimana cara memanggil web search API untuk agen AI dari Python?**

POST kueri ke `https://llmwebsearch.com/api/v1/search` dengan `requests` atau `httpx` dan Bearer key. **LLMWebSearch** mengembalikan hasil berperingkat beserta 3 halaman teratas sebagai Markdown, yang bisa Anda beri nomor dan teruskan ke LLM apa pun untuk dijawab dengan sitasi. Satu pencarian berbiaya 1 kredit, mulai dari US$1,20/1K pencarian per Oktober 2026. Python 3.9+ dan tanpa SDK. Tanpa KYC; bayar dengan kripto.

## Yang Anda butuhkan

| Atribut | Nilai |
| --- | --- |
| Python | 3.9 atau lebih baru |
| Paket | `requests` untuk skrip sederhana, `httpx` untuk yang async |
| Key | `LLMWEBSEARCH_API_KEY` di environment Anda |
| Untuk loop jawaban | Endpoint chat apa pun yang kompatibel dengan OpenAI: `LLM_BASE_URL`, `LLM_API_KEY`, `LLM_MODEL` |
| Penagihan | Satu kredit membayar satu pencarian: hingga 20 hasil berperingkat, dengan halaman dari 3 hasil teratas dibaca dan dikembalikan sebagai Markdown. Membaca 4 hingga 10 halaman menjadi 2 kredit. Panggilan yang berakhir dengan error tidak dikenakan biaya |

## Bagaimana cara menjalankan satu pencarian dengan requests?

Susun body JSON dengan `q` dan opsi yang Anda butuhkan, POST, lalu loop atas `results`. Periksa `content_status` sebelum menyentuh `content`: hanya hasil yang ditandai `ok` yang membawa teks halaman. Skrip di bawah menyimpan setiap halaman yang dibaca sebagai file Markdown bernama sesuai posisinya, dengan URL sumber di baris pertama.

*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"])

```

## Bagaimana cara menjalankan banyak pencarian sekaligus?

Gunakan `httpx.AsyncClient` dan `asyncio.Semaphore`. Semaphore membatasi berapa banyak panggilan yang sedang berjalan, sehingga Anda tetap di bawah batas per menit akun Anda dan beban tersebar. Empat sampai delapan panggilan bersamaan cocok untuk sebagian besar agen riset. Setiap akun memiliki batas panggilan per menit yang ditampilkan di dashboard; hubungi support dan batas itu bisa dinaikkan.

Contoh mengirim satu pencarian per sub-pertanyaan, langkah khas agen riset, dan mengumpulkan URL halaman yang dibaca. Setiap panggilan independen: satu kegagalan tidak menghentikan yang lain.

*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())

```

## Bagaimana cara mendapatkan jawaban dengan sitasi dari LLM apa pun?

Cari dulu, lalu beri model sumber bernomor dan minta ia mengutipnya. API mengembalikan sumber, bukan jawaban tertulis: model Anda sendiri yang membaca halaman, menulis balasan, dan mengutip URL-nya, jadi Anda memilih model dan gaya sitasi. Loop di bawah berfungsi dengan endpoint chat apa pun yang berbicara dalam format `chat/completions` yang kompatibel dengan OpenAI; atur alamat, key, dan modelnya di tiga environment variable.

Jaga konteks tetap terkendali. Skrip memangkas setiap sumber agar totalnya tetap dalam anggaran sekitar 6.000 token, dengan hitungan kira-kira empat karakter per token. Hasil tanpa konten diteruskan sebagai snippet-nya, ditandai sebagai snippet, agar model tidak berpura-pura telah membaca halaman itu.

*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?"))

```

## Berapa banyak teks yang harus saya kirim ke model?

Sesedikit yang cukup untuk menjawab pertanyaan. Tiga halaman yang dipangkas menjadi 2.000 token masing-masing biasanya mengalahkan sepuluh halaman penuh: model lebih fokus dan setiap jawaban lebih murah. Turunkan `max_chars` pada pencarian itu sendiri bila Anda tahu akan memangkas, agar lebih sedikit data yang berpindah.

Memangkas dari awal halaman berhasil untuk sebagian besar artikel, karena poin utamanya cenderung muncul lebih dulu. Untuk halaman referensi yang panjang, pecah konten berdasarkan heading dan pertahankan bagian yang judulnya cocok dengan pertanyaan.

## Apa yang harus dilakukan kode ketika sebuah halaman hilang?

Lanjutkan dengan sumber lain. Halaman yang ditandai `blocked_by_robots`, `blocked_by_site_policy` atau `target_refused` tidak akan dibaca pada percobaan kedua, jadi jangan loop di situ. Halaman `fetch_timeout` atau `fetch_failed` mungkin berhasil nanti melalui `https://llmwebsearch.com/api/v1/extract`.

Error HTTP berbeda: `429`, `502` dan `504` layak dicoba ulang dengan jeda yang makin panjang. [Halaman errors](https://llmwebsearch.com/id/docs/errors/) punya helper retry yang siap pakai.

## Python: pertanyaan umum

### Apakah ada paket pip untuk LLMWebSearch?

Tidak. API ini adalah HTTPS dan JSON biasa, jadi Anda hanya perlu `requests` atau `httpx`. Skrip di halaman ini adalah integrasinya: salin ke proyek Anda dan sesuaikan.

### Bisakah saya menggunakan skrip ini dengan model lokal?

Bisa. Server apa pun yang menyediakan route `chat/completions` yang kompatibel dengan OpenAI berfungsi dengan loop sitasi: atur `LLM_BASE_URL` ke alamatnya dan `LLM_MODEL` ke nama model yang diharapkannya.

### Bagaimana cara menghitung kredit dari Python?

Jumlahkan `credits_used` dari setiap respons, atau panggil `GET https://llmwebsearch.com/api/v1/credits` sebelum dan sesudah tugas; panggilan itu gratis.

---

LLMWebSearch: LLMWebSearch adalah API pencarian web untuk agen AI yang mengembalikan hasil berperingkat beserta konten halaman teratas dalam Markdown, dibayar dengan kripto tanpa unggah ID. Kredit prabayar mulai dari US$1,20/1K pencarian tidak pernah kedaluwarsa. Tanpa KYC — tanpa ID, tanpa selfie, tanpa dokumen. Bayar dengan kripto.
