Resposta rápida
Como adiciono pesquisa na web ao vivo ao LangChain ou ao LlamaIndex?
Envolva a chamada /search do LLMWebSearch em um @tool do LangChain, um retriever do LangChain ou um FunctionTool do LlamaIndex. Cada um retorna fontes classificadas com as 3 principais páginas em Markdown e suas URLs, prontas para citações. Nenhum pacote nosso para instalar: o código abaixo é a integração. Uma pesquisa custa 1 crédito, a partir de US$ 1,20/1K pesquisas em outubro de 2026. Sem KYC; pague com cripto.
Para que serve cada wrapper
| Ferramenta LangChain | Um agente chama search_web quando decide que precisa da web |
|---|---|
| Retriever LangChain | Uma cadeia busca documentos atualizados para cada pergunta, sem perguntar |
| FunctionTool LlamaIndex | A mesma função search_web, para agentes LlamaIndex |
| Pacotes | langchain-core ou llama-index-core, mais requests |
| Chave | LLMWEBSEARCH_API_KEY no ambiente |
| Cobrança | Um crédito paga uma pesquisa: até 20 resultados classificados, com as páginas dos 3 primeiros lidas e devolvidas em Markdown. Ler de 4 a 10 páginas custa 2 créditos. Uma chamada que termina em erro não custa nada |
Como construo uma ferramenta de pesquisa para um agente LangChain?
Decore uma função com @tool de langchain_core.tools. O LangChain lê o nome, os argumentos tipados e a docstring para descrever a ferramenta ao modelo, então mantenha a docstring curta e precisa. A função retorna uma string com as fontes numeradas, o que funciona com todo modelo de chat e mantém as URLs junto ao texto.
Chame diretamente com .invoke() para testar, depois passe na lista de tools do seu agente.
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])
Como uso a web ao vivo como retriever?
Crie uma subclasse de BaseRetriever e retorne um Document por página lida. O texto da página vai em page_content; a URL, o título, o rank e a data vão em metadata, onde as cadeias que citam suas fontes os procuram. Resultados sem conteúdo são ignorados, porque um retriever deve entregar texto que o modelo possa citar.
Um retriever roda a cada pergunta. Isso é correto para uma cadeia de perguntas e respostas sobre temas atuais, e desperdício para um chat em que a maioria dos turnos não precisa de pesquisa; nesse caso, use a ferramenta.
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))
Como dou a mesma pesquisa a um agente LlamaIndex?
Escreva uma função simples e envolva-a com FunctionTool.from_defaults de llama_index.core.tools. O LlamaIndex monta o nome e a descrição da ferramenta a partir do nome, da assinatura e da docstring da função. Chamar a ferramenta retorna um ToolOutput cujo content é o texto que o agente vai ler.
A função é a mesma do LangChain, então uma equipe que usa os dois frameworks pode compartilhá-la.
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])
Como mantenho a janela de contexto sob controle?
Defina max_chars na chamada da API em vez de aparar depois: menos dados trafegam e o framework nunca guarda a versão longa. 5.000 caracteres por página e 3 páginas dão cerca de 4.000 tokens, confortável para a maioria dos modelos.
Para chains de retrieval que combinam a web ao vivo com os seus próprios documentos, dê um peso menor aos documentos da web ou uma seção de prompt separada. Páginas web mudam e podem estar erradas; os seus próprios documentos geralmente são a referência.
Posso armazenar o que o retriever retorna em um banco de dados vetorial?
Sim, no que depender de nós. Não treinamos nenhum modelo com as suas consultas ou com o que a API retorna, e não adicionamos nenhuma regra própria contra armazenar resultados; os direitos dos sites cujas páginas você lê continuam valendo. Armazene a URL e published_at com cada chunk, para que o agente possa citar a página e você possa descartar chunks que envelhecem.
A maioria das equipes faz cache dos resultados da web de perguntas frequentes por um dia ou uma semana. Isso reduz pesquisas repetidas, e cada pesquisa evitada é um crédito economizado.
LangChain e LlamaIndex: perguntas comuns
Existe um pacote langchain-llmwebsearch?
Não. Não publicamos nenhum pacote. O código desta página é curto o bastante para ser seu: copie, altere os padrões e mantenha no seu repositório.
Posso tornar a ferramenta async?
Sim. Substitua requests por httpx.AsyncClient e defina a função com async def. O @tool do LangChain decora funções async diretamente; no LlamaIndex, passe a versão async para FunctionTool.from_defaults como async_fn.
Como leio uma página que o agente encontrou em outro lugar?
Adicione uma segunda ferramenta que envia URLs para https://llmwebsearch.com/api/v1/extract. Ler páginas cujas URLs você já tem custa 1 crédito por cada grupo iniciado de 3 páginas devolvidas com conteúdo, até 20 URLs por chamada; URLs recusadas ou com falha não são contadas. A referência de extract mostra a requisição.