# ตัวอย่าง Python: ค้นหา รันคำค้นหาพร้อมกัน ตอบพร้อมการอ้างอิง

> ตัวอย่าง Python web search API: requests, async httpx พร้อม semaphore และลูปตอบพร้อมการอ้างอิงสำหรับ LLM ใดก็ได้ภายในงบ token ไม่ต้อง KYC

- URL: https://llmwebsearch.com/th/docs/python/
- อัปเดตล่าสุด: 2026-10-22
- ผู้เผยแพร่: LLMWebSearch (https://llmwebsearch.com)

สามสคริปต์ครอบคลุมสิ่งที่ agent Python ส่วนใหญ่ต้องการ: ค้นหาหนึ่งครั้งด้วย `requests` ค้นหาหลายครั้งพร้อมกันด้วย `httpx` และ semaphore และลูปสั้นๆ ที่ส่งแหล่งข้อมูลให้โมเดลภาษาแล้วได้คำตอบพร้อมการอ้างอิงแบบมีหมายเลขกลับมา

**คำตอบสั้น — จะเรียก web search API สำหรับ AI agent จาก Python ได้อย่างไร**

POST คำค้นหาไปที่ `https://llmwebsearch.com/api/v1/search` พร้อม `requests` หรือ `httpx` และคีย์แบบ Bearer **LLMWebSearch** ส่งคืนผลลัพธ์ที่จัดอันดับพร้อม 3 หน้าแรกในรูปแบบ Markdown ซึ่งคุณสามารถใส่หมายเลขและส่งให้ LLM ใดก็ได้เพื่อตอบพร้อมการอ้างอิง การค้นหาหนึ่งครั้งมีค่า 1 เครดิต เริ่มต้นที่ US$1.20/1K การค้นหา ณ ตุลาคม 2026 ใช้ Python 3.9+ และไม่ต้องใช้ SDK ไม่ต้อง KYC; จ่ายด้วยคริปโต

## สิ่งที่คุณต้องมี

| คุณลักษณะ | ค่า |
| --- | --- |
| Python | 3.9 ขึ้นไป |
| แพ็กเกจ | `requests` สำหรับสคริปต์ง่ายๆ, `httpx` สำหรับตัว async |
| Key | `LLMWEBSEARCH_API_KEY` ใน environment ของคุณ |
| สำหรับลูปตอบ | endpoint แชตที่เข้ากันได้กับ OpenAI ใดก็ได้: `LLM_BASE_URL`, `LLM_API_KEY`, `LLM_MODEL` |
| การเรียกเก็บเงิน | หนึ่งเครดิตจ่ายสำหรับการค้นหาหนึ่งครั้ง: ผลลัพธ์แบบจัดอันดับสูงสุด 20 รายการ โดยอ่านหน้าเว็บของ 3 รายการแรกและส่งกลับเป็น Markdown การอ่าน 4 ถึง 10 หน้าใช้ 2 เครดิต การเรียกที่จบลงด้วยข้อผิดพลาดไม่มีค่าใช้จ่าย |

## จะรันการค้นหาหนึ่งครั้งด้วย requests ได้อย่างไร

สร้าง JSON body ด้วย `q` และตัวเลือกที่ต้องการ ส่ง POST แล้ววนลูปเหนือ `results` ตรวจสอบ `content_status` ก่อนแตะ `content`: เฉพาะผลลัพธ์ที่ทำเครื่องหมาย `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` semaphore จำกัดจำนวนการเรียกที่ทำงานอยู่พร้อมกัน ซึ่งช่วยให้คุณอยู่ภายใต้ขีดจำกัดต่อนาทีของบัญชีและกระจายโหลด การเรียกพร้อมกันสี่ถึงแปดครั้งเหมาะกับ research agent ส่วนใหญ่ ทุกบัญชีมีขีดจำกัดการเรียกต่อนาที แสดงในแดชบอร์ด; แจ้งฝ่ายสนับสนุนและสามารถเพิ่มได้.

ตัวอย่างส่งหนึ่งคำค้นหาต่อคำถามย่อย ซึ่งเป็นขั้นตอนทั่วไปของ research agent และรวบรวม 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 ดังนั้นคุณเลือกโมเดลและรูปแบบการอ้างอิงได้เอง ลูปด้านล่างใช้ได้กับ endpoint แชตใดก็ได้ที่ใช้รูปแบบ `chat/completions` ที่เข้ากันได้กับ OpenAI; ตั้งค่าที่อยู่ คีย์ และโมเดลในตัวแปรสภาพแวดล้อมสามตัว

ควบคุม context ให้พอดี สคริปต์ตัดแต่ละแหล่งข้อมูลเพื่อให้รวมแล้วอยู่ในงบประมาณราว 6,000 token โดยนับประมาณสี่ตัวอักษรต่อ token ผลลัพธ์ที่ไม่มีเนื้อหาจะถูกส่งเป็น snippet พร้อมระบุว่าเป็นเช่นนั้น เพื่อไม่ให้โมเดลแสร้งว่าอ่านหน้าเว็บแล้ว

*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 token ต่อหน้ามักดีกว่าสิบหน้าเต็ม: โมเดลโฟกัสได้ดีกว่าและแต่ละคำตอบมีค่าใช้จ่ายน้อยลง ลด `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` ควรลองใหม่โดยเพิ่มระยะเวลารอขึ้นเรื่อยๆ [หน้าข้อผิดพลาด](https://llmwebsearch.com/th/docs/errors/) มีตัวช่วยลองใหม่พร้อมใช้

## Python: คำถามที่พบบ่อย

### มีแพ็กเกจ pip สำหรับ LLMWebSearch ไหม

ไม่ต้องใช้ API เป็น HTTPS และ JSON ธรรมดา คุณแค่ต้องมี `requests` หรือ `httpx` สคริปต์ในหน้านี้คือการเชื่อมต่อ: คัดลอกไปยังโปรเจกต์ของคุณแล้วดัดแปลง

### ฉันใช้สคริปต์เหล่านี้กับโมเดลในเครื่องได้ไหม

ได้ เซิร์ฟเวอร์ใดก็ตามที่เปิดเส้นทาง `chat/completions` ที่เข้ากันได้กับ OpenAI จะทำงานกับลูปการอ้างอิง: ตั้งค่า `LLM_BASE_URL` เป็นที่อยู่ของเซิร์ฟเวอร์ และ `LLM_MODEL` เป็นชื่อโมเดลที่มันคาดหวัง

### ฉันนับเครดิตจาก Python ได้อย่างไร

บวก `credits_used` จากแต่ละ response หรือเรียก `GET https://llmwebsearch.com/api/v1/credits` ก่อนและหลังงาน การเรียกนั้นฟรี

---

LLMWebSearch: LLMWebSearch คือ web search API สำหรับ AI agents ที่คืนผลการค้นหาที่จัดอันดับแล้วพร้อมเนื้อหาของหน้าอันดับต้นๆ ในรูปแบบ Markdown จ่ายด้วยคริปโตโดยไม่ต้องอัปโหลดเอกสารยืนยันตัวตน เครดิตแบบเติมล่วงหน้าตั้งแต่ US$1.20/1K การค้นหา ไม่มีวันหมดอายุ ไม่ต้อง KYC — ไม่ต้องใช้บัตรประชาชน ไม่ต้องเซลฟี่ ไม่ต้องใช้เอกสาร จ่ายด้วยคริปโต
