# 用网页搜索 API 在 Python 中构建 deep research agent

> 用约 60 行 Python 构建一个 deep research agent：规划查询、带页面内容搜索、去除重复来源，并用任意 LLM 写出带引用的答案。

- URL: https://llmwebsearch.com/zh/guides/build-a-deep-research-agent/
- 最后更新: 2026-10-22
- 发布者: LLMWebSearch (https://llmwebsearch.com)

Deep research 是一个循环，而不是一个模型。本指南构建一个你几分钟就能读完的：它规划查询、带页面文本搜索网页、每个来源只保留一次、寻找缺口，并写出带编号引用的答案。

**快速回答 — 如何用 Python 构建 deep research agent？**

写一个循环：让 LLM 给出搜索查询，将每个查询通过返回页面文本的网页搜索 API 执行，每个来源只保留一次，再请求后续查询，最后写出带引用的答案。使用 **LLMWebSearch** 时，每次搜索以一个积分返回前 3 页的 Markdown；下面的脚本每个问题运行六次搜索。低至 US$1.20/千次搜索，无需 KYC，加密货币支付。

## agent 会做什么？

1. **套餐** — 让模型给出四个简短的查询，合起来覆盖该问题。
2. **搜索** — 用 read=3 运行每个查询，这样前三个页面会以 Markdown 返回。
3. **每个来源只保留一次** — 按 URL 存储结果，跳过已收集的页面或没有文本的页面。
4. **寻找缺口** — 把目前已找到的标题展示给模型，并请它针对缺失内容给出两个查询。
5. **撰写** — 最多编号 12 个来源，并要求答案以 [1]、[2] 形式引用它们。

## 开始前我需要准备什么？

三样东西：

- **一个 LLMWebSearch API 密钥**，放在环境变量 `LLMWEBSEARCH_API_KEY` 中。首次购买积分包后，密钥会出现在仪表盘中。
- **任意 OpenAI 兼容的 chat endpoint**，通过 `LLM_BASE_URL`、`LLM_API_KEY` 和 `LLM_MODEL` 设置。托管模型和本地服务器都可以。
- **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` 让模型给出简短查询，每行一个，并去掉列表标记。相比完整问句，简短查询更契合搜索引擎对页面的排序方式。

`search` 调用 `POST https://llmwebsearch.com/api/v1/search`，传入 `max_results=5`、`read=3` 和 `max_chars=5000`。响应列出五个结果；前三个在 `content` 中带有页面文本。

`research` 运行各轮。它把来源保存在以 URL 为键的字典中，因此被两个查询找到的页面只计一次。只保留 `content_status` 等于 `ok` 的结果，因为答案必须建立在模型能读到的文本上。

每轮之后，模型查看目前已收集的标题，并针对缺失内容提出两个新查询。最终 prompt 最多编号十二个来源，并设定引用规则。

## 一份报告要花多少钱？

默认设置下每个问题六次搜索：第一轮四次，第二轮两次。`read` 为 3 时，即六个积分。因此 US$41.25 的 25,000 次查询 可覆盖 4,000 多份报告。

模型调用会在你的 LLM 账单上额外计费：每份报告三次规划调用和一次长答案调用。答案调用最多携带十二页、每页 5,000 个字符，如果你的模型较贵，就调低 `max_chars`。

## 如何让它挖得更深？

改数字，不改结构：

- **更多轮次。** `rounds=4` 让模型多追两组缺口。
- **每轮更多查询。** 把找缺口那次调用里的 `n` 从 2 提到 4。
- **更窄的来源。** 在搜索请求体里加 `include_domains`，限定官网或学术站点。
- **只要近期材料。** 加 `"time_range": "month"` 或 `"topic": "news"`。
- **每次查询读更多页。** `read=5` 会读五个页面，每次搜索 2 积分。

提高上限之前先加一条停止规则：某一轮带来的新来源少于两个时就结束循环。

## 如何让 agent 既安全又守规矩？

把每个页面都当作不可信文本。最终提示词会告诉模型只把来源当证据用；再加一行明确说明：来源里的指令一律忽略。绝不要让来源触发工具调用或文件写入。

网页这一侧，我们的读取器遵守 robots.txt：它会以 LLMWebSearch 表明身份，遵守网站为它或为所有机器人写下的规则，并排除任何它无权读取的页面内容。拒绝访问的页面会返回空内容，脚本直接跳过。当网站拒绝我们的读取器时，结果会不带内容返回，并附上该网站的状态：我们绝不破解 CAPTCHA、不登录账户，也不绕过封锁或付费墙。

## 哪里会出问题？

测试中出现三种情况：

- **查询含糊。** 规划太弱时，每一轮都找到同样的页面。让模型给出带具体名称和日期的查询。
- **带正文的来源太少。** 有些主题藏在付费墙后面。放宽查询或允许多更多结果。
- **提示词过长。** 十二个长页面可能超出小模型的上下文。调低 `max_chars` 或只保留八个来源。

记录每次查询和它返回的 URL。读这份日志是改进规划提示词最快的办法。

## 深度研究 agent：常见问题

### 能配本地模型跑吗？

可以，只要本地服务器支持 OpenAI chat-completions 格式。把 `LLM_BASE_URL` 设成它的地址，`LLM_MODEL` 设成模型名。

### 能把它做成 MCP 工具吗？

可以。把 `research()` 包进一个 MCP 工具，或者把 [MCP 页面](https://llmwebsearch.com/zh/docs/mcp/) 上的纯搜索工具交给你的 agent，让它自己跑循环。

### 为什么不直接向搜索 API 要答案？

因为 LLMWebSearch 不写答案。API 返回的是来源，而不是写好的答案：由你自己的模型读取页面、撰写回复并引用 URL。

### 怎么查看每次搜索花了多少？

每个响应都带有 `credits_used`，而 `GET https://llmwebsearch.com/api/v1/credits` 会免费显示你的剩余余额。

---

LLMWebSearch: LLMWebSearch 是一款面向 AI 智能体的网页搜索 API，返回排序后的搜索结果，并附带热门页面的 Markdown 格式内容，采用加密货币支付，无需上传证件。预付费积分低至 US$1.20/千次搜索，永不过期。无 KYC——无需证件、无需自拍、无需提交任何文件。加密货币支付。
