快速回答
如何为 LangChain 或 LlamaIndex 添加实时网页搜索?
将 LLMWebSearch 的 /search 调用包装进 LangChain @tool、LangChain retriever 或 LlamaIndex FunctionTool。它们都会返回带排名的来源,其中前 3 个页面为 Markdown 及其 URL,可直接用于引用。无需安装我们的包:下面的代码就是集成方式。一次搜索花费 1 积分,低至 US$1.20/千次搜索(截至 2026 年 10 月)。无需 KYC;用加密货币支付。
各个包装器的用途
| LangChain 工具 | agent 在判断需要网页时调用 search_web |
|---|---|
| LangChain retriever | 链会为每个问题获取最新文档,无需询问 |
| LlamaIndex FunctionTool | 同一个 search_web 函数,供 LlamaIndex agents 使用 |
| 软件包 | langchain-core 或 llama-index-core,外加 requests |
| 密钥 | 环境变量中的 LLMWEBSEARCH_API_KEY |
| 计费 | 1 积分支付一次查询:最多 20 条排名结果,其中前 3 条的页面会被读取并以 Markdown 返回。读取 4 到 10 个页面为 2 积分。以错误结束的调用不收费 |
如何为 LangChain agent 构建搜索工具?
用 langchain_core.tools 中的 @tool 装饰一个函数。LangChain 会读取名称、带类型的参数和 docstring 来向模型描述该工具,因此请保持 docstring 简短精确。函数返回一个带编号来源的字符串,适用于所有聊天模型,并让 URL 紧邻文本。
用 .invoke() 直接调用它来测试,然后把它传入你 agent 的 tools 列表。
import os
import requests
from langchain_core.tools import tool
API = "https://llmwebsearch.com/api/v1/search"
HEADERS = {"Authorization": "Bearer " + os.environ["LLMWEBSEARCH_API_KEY"]}
@tool
def search_web(query: str, read: int = 3) -> str:
"""Search the live web. Returns numbered sources with URLs; the first `read` include page text."""
body = {"q": query, "max_results": 5, "read": max(0, min(read, 10)), "max_chars": 5000}
resp = requests.post(API, headers=HEADERS, json=body, timeout=60)
resp.raise_for_status()
parts = []
for i, r in enumerate(resp.json()["results"], start=1):
text = r["content"] if r["content_status"] == "ok" else "(snippet only) " + r["snippet"]
parts.append(f"[{i}] {r['title']} - {r['url']}\n{text}")
return "\n\n".join(parts)
print(search_web.invoke({"query": "postgres connection pool size"})[:500])
如何把实时网页用作 retriever?
继承 BaseRetriever,并为每个读取的页面返回一个 Document。页面文本放在 page_content 中;URL、标题、排名和日期放在 metadata 中,需要引用来源的链会在这里查找它们。无内容的结果会被跳过,因为 retriever 应当交给模型可以引用的文本。
retriever 会对每个问题运行。对于关于时事话题的问答链来说这没问题,但对于大多数轮次都无需搜索的聊天来说则很浪费;这种情况下请使用工具。
import os
from typing import List
import requests
from langchain_core.callbacks import CallbackManagerForRetrieverRun
from langchain_core.documents import Document
from langchain_core.retrievers import BaseRetriever
class LLMWebSearchRetriever(BaseRetriever):
"""Live web search results as Documents, one per page that was read."""
max_results: int = 5
read: int = 3
max_chars: int = 6000
def _get_relevant_documents(self, query: str, *, run_manager: CallbackManagerForRetrieverRun) -> List[Document]:
resp = requests.post(
"https://llmwebsearch.com/api/v1/search",
headers={"Authorization": "Bearer " + os.environ["LLMWEBSEARCH_API_KEY"]},
json={"q": query, "max_results": self.max_results, "read": self.read, "max_chars": self.max_chars},
timeout=60,
)
resp.raise_for_status()
docs = []
for r in resp.json()["results"]:
if r["content_status"] != "ok":
continue
meta = {"source": r["url"], "title": r["title"], "position": r["position"], "published_at": r.get("published_at")}
docs.append(Document(page_content=r["content"], metadata=meta))
return docs
retriever = LLMWebSearchRetriever(read=3)
for doc in retriever.invoke("pgbouncer transaction mode limitations"):
print(doc.metadata["position"], doc.metadata["source"], len(doc.page_content))
如何把同样的搜索提供给 LlamaIndex agent?
编写一个普通函数,并用 llama_index.core.tools 中的 FunctionTool.from_defaults 包装它。LlamaIndex 会根据函数名、签名和 docstring 构建工具的名称和描述。调用该工具会返回一个 ToolOutput,其中 content 是 agent 将读取的文本。
该函数与 LangChain 版本相同,因此同时使用这两个框架的团队可以共享它。
import os
import requests
from llama_index.core.tools import FunctionTool
def search_web(query: str, read: int = 3) -> str:
"""Search the live web and return numbered sources; the first `read` include their page text."""
resp = requests.post(
"https://llmwebsearch.com/api/v1/search",
headers={"Authorization": "Bearer " + os.environ["LLMWEBSEARCH_API_KEY"]},
json={"q": query, "max_results": 5, "read": max(0, min(read, 10)), "max_chars": 5000},
timeout=60,
)
resp.raise_for_status()
parts = []
for i, r in enumerate(resp.json()["results"], start=1):
text = r["content"] if r["content_status"] == "ok" else "(snippet only) " + r["snippet"]
parts.append(f"[{i}] {r['title']} - {r['url']}\n{text}")
return "\n\n".join(parts)
search_tool = FunctionTool.from_defaults(fn=search_web)
print(search_tool.metadata.name)
print(search_tool.call(query="postgres connection pool size").content[:300])
如何控制上下文窗口?
在调用 API 时设置 max_chars,而不是事后裁剪:传输的数据更少,框架也不会保留长版本。每页 5,000 个字符、3 页约为 4,000 个 token,对大多数模型来说很充裕。
对于将实时网络与你自己的文档结合的检索链,给网页文档较低的权重或单独的提示词部分。网页会变化,也可能出错;你自己的文档通常是参考依据。
我可以把检索器返回的内容存入向量数据库吗?
可以,对我们来说没问题。我们不会用你的查询或 API 返回的内容训练任何模型,也不会自行添加任何禁止存储结果的规则;你读取的页面所属网站的权利仍然适用。将 URL 和 published_at 与每个分块一起存储,这样 agent 可以引用该页面,你也可以删除过期的分块。
大多数团队会将高频问题的网页结果缓存一天或一周。这能减少重复搜索,每避免一次搜索就节省一个积分。
LangChain 和 LlamaIndex:常见问题
有 langchain-llmwebsearch 包吗?
没有。我们未发布任何包。本页的代码足够短,可以直接自己维护:复制它、修改默认值,并保存在你的代码库中。
我可以把工具改成异步的吗?
可以。将 requests 替换为 httpx.AsyncClient,并用 async def 定义函数。LangChain 的 @tool 直接装饰异步函数;在 LlamaIndex 中,将异步版本作为 async_fn 传给 FunctionTool.from_defaults。
如何读取 agent 在其他地方找到的页面?
添加第二个工具,将 URL 提交到 https://llmwebsearch.com/api/v1/extract。读取你已持有 URL 的页面:每开始一组 3 个返回内容的页面计 1 积分,每次调用最多 20 个 URL;被拒绝或失败的 URL 不计入。extract 参考 展示了该请求。