# สร้าง deep research agent ใน Python ด้วย web search API

> สร้าง deep research agent ด้วย Python ประมาณ 60 บรรทัด: วางแผนคำค้นหา ค้นหาพร้อมเนื้อหาหน้าเว็บ ตัดแหล่งซ้ำ และเขียนคำตอบที่มีการอ้างอิงด้วย LLM ตัวใดก็ได้

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

Deep research คือ loop ไม่ใช่โมเดล คู่มือนี้จะสร้างตัวหนึ่งที่คุณอ่านจบได้ในไม่กี่นาที: วางแผนคำค้นหา ค้นหาเว็บพร้อมเนื้อหาหน้าเว็บ เก็บแต่ละแหล่งไว้ครั้งเดียว มองหาช่องว่าง และเขียนคำตอบพร้อมการอ้างอิงแบบมีหมายเลข

**คำตอบสั้น — ฉันจะสร้าง deep research agent ใน Python ได้อย่างไร**

เขียน loop ที่ถาม LLM ให้สร้างคำค้นหา รันแต่ละคำผ่าน web search API ที่คืนเนื้อหาหน้าเว็บ เก็บแต่ละแหล่งไว้ครั้งเดียว ถามคำค้นหาติดตามผล แล้วเขียนคำตอบที่มีการอ้างอิง ด้วย **LLMWebSearch** การค้นหาแต่ละครั้งจะคืน 3 หน้าแรกเป็น Markdown ในราคาหนึ่งเครดิต สคริปต์ด้านล่างรันหกการค้นหาต่อหนึ่งคำถาม เริ่มต้นที่ US$1.20/1K การค้นหา ไม่ต้อง KYC จ่ายด้วยคริปโต

## Agent จะทำอะไร

1. **แพ็กเกจ** — ถามโมเดลให้สร้างคำค้นหาสั้น ๆ สี่คำที่ครอบคลุมคำถามด้วยกัน
2. **ค้นหา** — รันแต่ละคำค้นหาด้วย read=3 เพื่อให้ได้สามหน้าแรกกลับมาเป็น Markdown
3. **เก็บแต่ละแหล่งไว้ครั้งเดียว** — เก็บผลลัพธ์ตาม URL และข้ามหน้าที่เก็บไปแล้วหรือไม่มีข้อความ
4. **มองหาช่องว่าง** — แสดงชื่อเรื่องที่พบ到目前为止ให้โมเดลดู แล้วถามคำค้นหาใหม่สองคำเกี่ยวกับสิ่งที่ยังขาด
5. **เขียน** — ใส่หมายเลขแหล่งข้อมูลสูงสุด 12 แหล่ง แล้วขอคำตอบที่อ้างอิงเป็น [1], [2]

## ต้องเตรียมอะไรก่อนเริ่ม

สามอย่าง:

- **API key ของ LLMWebSearch** ใน environment variable `LLMWEBSEARCH_API_KEY` key จะปรากฏในแดชบอร์ดหลังซื้อแพ็กแรก
- **chat endpoint ที่รองรับ OpenAI** ตัวใดก็ได้ ตั้งค่าด้วย `LLM_BASE_URL`, `LLM_API_KEY` และ `LLM_MODEL` ทั้งโมเดลแบบ hosted และเซิร์ฟเวอร์ในเครื่องใช้ได้
- **Python 3.9 หรือใหม่กว่า** พร้อมแพ็กเกจ `requests`

สคริปต์ใช้ HTTP ธรรมดาสำหรับทั้งสองบริการ คุณจึงสลับโมเดลได้โดยไม่ต้องแก้โค้ดค้นหา

*Python: deep_research.py*

```python
import os

import requests

SEARCH = "https://llmwebsearch.com/api/v1/search"
SEARCH_HEADERS = {"Authorization": "Bearer " + os.environ["LLMWEBSEARCH_API_KEY"]}
# Any OpenAI-compatible chat endpoint works here: set your own base URL, key and model name.
LLM_URL = os.environ.get("LLM_BASE_URL", "https://llm.example.com/v1") + "/chat/completions"
LLM_HEADERS = {"Authorization": "Bearer " + os.environ.get("LLM_API_KEY", "")}
MODEL = os.environ.get("LLM_MODEL", "your-model-name")


def ask_llm(prompt):
    body = {"model": MODEL, "messages": [{"role": "user", "content": prompt}]}
    resp = requests.post(LLM_URL, headers=LLM_HEADERS, json=body, timeout=120)
    resp.raise_for_status()
    return resp.json()["choices"][0]["message"]["content"]


def plan_queries(question, n=4):
    text = ask_llm(f"Write {n} short web search queries, one per line, to answer: {question}")
    queries = [line.strip("-*0123456789. ").strip() for line in text.splitlines()]
    return [q for q in queries if q][:n]


def search(query):
    body = {"q": query, "max_results": 5, "read": 3, "max_chars": 5000}
    resp = requests.post(SEARCH, headers=SEARCH_HEADERS, json=body, timeout=60)
    resp.raise_for_status()
    return resp.json()["results"]


def research(question, rounds=2):
    sources = {}  # url -> result, so the same page is never cited twice
    queries = plan_queries(question)
    for _ in range(rounds):
        for query in queries:
            for r in search(query):
                if r.get("content_status") == "ok" and r["url"] not in sources:
                    sources[r["url"]] = r
        notes = "\n".join(f"- {r['title']}" for r in sources.values())
        queries = plan_queries(f"{question}\nAlready found:\n{notes}\nWhat is still missing?", n=2)
    numbered = list(sources.values())[:12]
    context = "\n\n".join(
        f"[{i}] {r['title']} ({r['url']})\n{r['content']}" for i, r in enumerate(numbered, 1)
    )
    prompt = (
        "Answer the question using only the sources below. Cite them as [1], [2]. "
        "Say so when the sources disagree or do not cover a point.\n\n"
        f"Question: {question}\n\nSources:\n{context}"
    )
    return ask_llm(prompt), [r["url"] for r in numbered]


if __name__ == "__main__":
    answer, urls = research("What changed in the EU rules for general-purpose AI models in 2025?")
    print(answer)
    for i, url in enumerate(urls, 1):
        print(f"[{i}] {url}")
```

## แต่ละส่วนทำงานอย่างไร

`plan_queries` ขอคำค้นหาสั้น ๆ จากโมเดล บรรทัดละคำ และตัดเครื่องหมาย list ออก คำค้นหาสั้น ๆ เข้ากับวิธีที่ search engine จัดอันดับหน้าเว็บได้ดีกว่าคำถามเต็มประโยค

`search` เรียก `POST https://llmwebsearch.com/api/v1/search` ด้วย `max_results=5`, `read=3` และ `max_chars=5000` ผลลัพธ์มีห้ารายการ โดยสามรายการแรกมีเนื้อหาหน้าเว็บอยู่ใน `content`

`research` รันเป็นรอบ ๆ เก็บแหล่งข้อมูลใน dictionary ที่ใช้ URL เป็น key หน้าที่พบจากสองคำค้นหาจึงนับครั้งเดียว เก็บเฉพาะผลลัพธ์ที่ `content_status` เท่ากับ `ok` เพราะคำตอบต้องอ้างอิงจากข้อความที่โมเดลอ่านได้

หลังแต่ละรอบ โมเดลจะเห็นชื่อเรื่องที่เก็บได้到目前为止และเสนอคำค้นหาใหม่สองคำสำหรับสิ่งที่ยังขาด prompt สุดท้ายใส่หมายเลขแหล่งข้อมูลสูงสุดสิบสองแหล่งและกำหนดกติกาการอ้างอิง

## หนึ่งรายงานมีค่าใช้จ่ายเท่าไร

หกการค้นหาต่อคำถามด้วยการตั้งค่าเริ่มต้น: สี่ครั้งในรอบแรกและสองครั้งในรอบที่สอง เมื่อ `read` อยู่ที่ 3 นั่นคือหกเครดิต ค้นหา 25,000 ครั้ง ที่ US$41.25 จึงครอบคลุมมากกว่า 4,000 รายงาน

การเรียกโมเดลมีค่าใช้จ่ายเพิ่มบนบิล LLM ของคุณ: การเรียกวางแผนสามครั้งและการเรียกคำตอบยาวหนึ่งครั้งต่อรายงาน การเรียกคำตอบมีเนื้อหาสูงสุดสิบสองหน้าครั้งละ 5,000 ตัวอักษร ดังนั้นลด `max_chars` ลงถ้าโมเดลของคุณมีราคาแพง

## ทำอย่างไรให้ค้นได้ลึกขึ้น?

เปลี่ยนตัวเลข ไม่ใช่โครงสร้าง:

- **เพิ่มรอบ** `rounds=4` ให้โมเดลตามช่องว่างเพิ่มอีกสองชุด
- **เพิ่มคำค้นหาต่อรอบ** ปรับ `n` ในคำสั่งหาช่องว่างจาก 2 เป็น 4
- **จำกัดแหล่งข้อมูลให้แคบลง** เพิ่ม `include_domains` ใน body ของการค้นหาเพื่อเจาะเว็บไซต์ทางการหรือวิชาการ
- **เอาเฉพาะเนื้อหาใหม่** เพิ่ม `"time_range": "month"` หรือ `"topic": "news"`
- **เพิ่มหน้าเว็บต่อคำค้นหา** `read=5` อ่านห้าหน้า ที่ 2 เครดิตต่อการค้นหา

เพิ่มกฎหยุดก่อนจะเพิ่มขีดจำกัด: จบลูปเมื่อรอบหนึ่งได้แหล่งข้อมูลใหม่น้อยกว่าสองแหล่ง

## ทำอย่างไรให้เอเจนต์ปลอดภัยและสุภาพ?

ถือว่าทุกหน้าเว็บเป็นข้อความที่ไม่น่าไว้วางใจ พรอมป์ต์สุดท้ายบอกโมเดลให้ใช้แหล่งข้อมูลเป็นหลักฐานเท่านั้น; เพิ่มบรรทัดชัดเจนว่าต้องเพิกเฉยต่อคำสั่งที่อยู่ในแหล่งข้อมูล ห้ามให้แหล่งข้อมูลทริกเกอร์ tool call หรือการเขียนไฟล์

ฝั่งเว็บ ตัวอ่านของเราเคารพ robots.txt: แนะนำตัวว่าเป็น LLMWebSearch ทำตามกฎที่เว็บไซต์เขียนไว้สำหรับมันหรือสำหรับ robots ทั้งหมด และละเว้นเนื้อหาของหน้าใดก็ตามที่มันไม่ได้รับอนุญาตให้อ่าน หน้าที่ปฏิเสธการเข้าถึงจะส่งกลับมาโดยไม่มีเนื้อหา และสคริปต์ก็ข้ามไปเลย เมื่อเว็บไซต์ปิดกั้นตัวอ่านของเรา ผลลัพธ์จะกลับมาโดยไม่มีเนื้อหาและมีสถานะของเว็บไซต์: เราไม่แก้ CAPTCHA ไม่ลงชื่อเข้าใช้บัญชี และไม่หลบเลี่ยงการบล็อกหรือ paywall

## มีอะไรที่อาจผิดพลาดได้?

มีสามอย่างที่เจอตอนทดสอบ:

- **คำค้นหากำกวม** ถ้าแผนอ่อน ทุกรอบจะเจอหน้าเดิม ขอให้โมเดลสร้างคำค้นหาที่ใช้ชื่อและวันที่เฉพาะเจาะจง
- **แหล่งข้อมูลที่มีข้อความน้อยเกินไป** บางหัวข้ออยู่หลัง paywall ขยายคำค้นหาหรืออนุญาตผลลัพธ์มากขึ้น
- **พรอมป์ต์ยาวเกินไป** หน้าที่ยาวสิบสองหน้าอาจเกิน context ของโมเดลเล็ก ลด `max_chars` ลงหรือเก็บไว้แปดแหล่ง

ล็อกทุกคำค้นหาและ URL ที่ได้มา การอ่านล็อกนั้นคือวิธีที่เร็วที่สุดในการปรับพรอมป์ต์วางแผน

## เอเจนต์วิจัยเชิงลึก: คำถามที่พบบ่อย

### รันด้วยโมเดลโลคอลได้ไหม?

ได้ ถ้าเซิร์ฟเวอร์โลคอลรองรับรูปแบบ OpenAI chat-completions ตั้ง `LLM_BASE_URL` เป็นที่อยู่ของมัน และ `LLM_MODEL` เป็นชื่อโมเดล

### แปลงเป็น MCP tool ได้ไหม?

ได้ ห่อ `research()` ใน MCP tool หรือให้เอเจนต์ของคุณใช้ search tool ธรรมดาจาก [หน้า MCP](https://llmwebsearch.com/th/docs/mcp/) แล้วปล่อยให้มันรันลูปเอง

### ทำไมไม่ถาม search API ให้ตอบคำถามโดยตรงเลย?

เพราะ LLMWebSearch ไม่ได้เขียนคำตอบ API ส่งคืนแหล่งข้อมูล ไม่ใช่คำตอบที่เขียนไว้: โมเดลของคุณเองอ่านหน้าเว็บ เขียนคำตอบ และอ้างอิง URL

### ดูได้อย่างไรว่าแต่ละการค้นหาใช้ไปเท่าไร?

ทุกการตอบกลับมี `credits_used` และ `GET https://llmwebsearch.com/api/v1/credits` แสดงยอดคงเหลือที่เหลืออยู่โดยไม่มีค่าใช้จ่าย

---

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