Crie um agente de research com Web Search + URL Extract (tool use do Claude, de ponta a ponta)

A maioria dos tutoriais de 'agente de research com IA' para em 'aqui está uma definição de ferramenta'. Este entrega um agente funcional: entra uma pergunta, sai uma resposta com citações. Buscar, extrair, raciocinar, citar — tudo em menos de 120 linhas de Python.
TL;DR
- •O menor loop de agente de research que realmente serve é: buscar → escolher links → extrair os corpos → pedir ao modelo que responda com citações inline.
- •Duas ferramentas — Web Search (15 créditos) e URL Extract (2 créditos por URL) — cobrem 95% dos casos de resposta fundamentada.
- •O tool use do Claude cuida da orquestração; você só transporta blocos tool_use → chamadas de API → blocos tool_result até o modelo parar.
- •Custo de ponta a ponta: ~25 créditos + ~$0.02 em tokens do LLM por pergunta em profundidade típica.
O que vamos construir
Um agente de research que recebe uma entrada — uma pergunta em linguagem natural — e devolve uma resposta com fontes. Arquiteturalmente:
question → [Claude]
↓ tool_use(search)
[API Pick Web Search] → ranked URLs
↓ tool_use(extract)
[API Pick URL Extract] → cleaned bodies
↓ Claude reads, decides if more is needed
↓ end_turn
answer with inline [source: URL] citationsCerca de 120 linhas de Python. Duas ferramentas — search e extract — e um loop de agente que lida com o que quer que o Claude decida fazer. Vamos construí-lo do zero em 4 passos.
1Puxe os schemas de ferramenta (sem JSON na mão)
Ambos os endpoints publicam uma rota de tool-schema que retorna uma definição de ferramenta do Claude no formato exato que messages.create espera.
import requests
API_KEY = "pk_yourkey"
def fetch_tool(tool_path: str) -> dict:
"""Fetch a Claude tool definition from API Pick's tool-schema endpoint."""
schema = requests.get(f"https://www.apipick.com{tool_path}/tool-schema").json()
return schema["claude"]
WEB_SEARCH_TOOL = fetch_tool("/api/search/web")
URL_EXTRACT_TOOL = fetch_tool("/api/extract")
# Cache these once at module load. They don't change between requests.Duas coisas importantes que isso te dá de graça: o schema de parâmetros (para que o Claude saiba que você aceita query, country_code, start_date, etc.) e uma descrição clara e amigável ao modelo do que a ferramenta faz.
2Escreva o handler da ferramenta
Quando o Claude retorna um bloco tool_use, seu trabalho é chamar a API real e retornar um bloco tool_result. Uma função por ferramenta, um dispatcher que roteia com base no nome que o Claude escolheu.
def call_tool(tool_use_block) -> dict:
"""Execute the tool Claude asked for and return a tool_result block."""
name = tool_use_block.name
args = tool_use_block.input
if name == "web_search":
endpoint = "https://www.apipick.com/api/search/web"
elif name == "extract_urls":
endpoint = "https://www.apipick.com/api/extract"
else:
return {
"type": "tool_result",
"tool_use_id": tool_use_block.id,
"content": f"Unknown tool: {name}",
"is_error": True,
}
resp = requests.post(
endpoint,
headers={"x-api-key": API_KEY},
json=args,
timeout=30,
)
if resp.status_code != 200:
return {
"type": "tool_result",
"tool_use_id": tool_use_block.id,
"content": f"HTTP {resp.status_code}: {resp.text[:500]}",
"is_error": True,
}
return {
"type": "tool_result",
"tool_use_id": tool_use_block.id,
"content": resp.text,
}Três coisas que vale destacar. tool_use_id é como o Claude correlaciona o resultado com sua chamada anterior — você precisa devolvê-lo tal como veio. is_error: True diz ao Claude para se recuperar com elegância (geralmente tentando outra consulta). E passamos o texto da resposta cru — o Claude se sente à vontade com o formato JSON que o API Pick retorna, então não é preciso transformação.
3O loop do agente
O loop é curto: envie a conversa, olhe cada bloco da resposta, execute as ferramentas, anexe à conversa tanto o tool_use do Claude quanto o seu tool_result, e repita até que stop_reason seja end_turn.
import anthropic
client = anthropic.Anthropic()
SYSTEM_PROMPT = """You are a research assistant. When the user asks a question:
1. Use web_search to find relevant sources. Prefer recent (set start_date) for time-sensitive queries.
2. Use extract_urls to read the body of the top 3-5 most relevant URLs from the search results.
3. Answer the question concisely. After every factual claim, include an inline citation in the form [source: URL].
4. If you don't have enough information, say so — don't fabricate.
Be concise. Aim for 3-5 sentence answers unless the user asks for depth."""
def research(question: str) -> str:
messages = [{"role": "user", "content": question}]
while True:
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=2048,
system=SYSTEM_PROMPT,
tools=[WEB_SEARCH_TOOL, URL_EXTRACT_TOOL],
messages=messages,
)
# Append the assistant's response (may contain tool_use blocks)
messages.append({"role": "assistant", "content": response.content})
if response.stop_reason == "end_turn":
# Pull out the text answer
return "\n".join(b.text for b in response.content if b.type == "text")
if response.stop_reason == "tool_use":
# Run every tool the model asked for in this turn
tool_results = [
call_tool(b)
for b in response.content if b.type == "tool_use"
]
messages.append({"role": "user", "content": tool_results})
continue
raise RuntimeError(f"Unexpected stop_reason: {response.stop_reason}")Repare que o modelo pode disparar várias chamadas de ferramenta no mesmo turno — por exemplo, ele pode emitir uma única chamada a extract_urls com cinco URLs para processá-las em lote. Lidamos com isso reunindo todos os blocos tool_use do turno do assistente e devolvendo todos os blocos tool_result no turno de usuário seguinte.
4Execute
if __name__ == "__main__":
answer = research("What were the major announcements at OpenAI DevDay this year?")
print(answer)Você verá algo como:
OpenAI's DevDay focused on three themes: cheaper inference (a new gpt-mini
tier at roughly half the prior cost) [source: https://openai.com/devday-2026],
agent infrastructure (a managed agent runtime with persistent memory and
tool sandboxing) [source: https://techcrunch.com/...], and developer
tooling (a new Responses API replacing the legacy Assistants API)
[source: https://platform.openai.com/docs/...].Isso é um agente de research. ~120 linhas, duas ferramentas, saída com fontes.
O que isso custa
Uma chamada de research típica:
- 1 chamada de busca — 15 créditos ($0.015)
- 1 chamada de extração cobrindo 3–5 URLs — 6–10 créditos ($0.006–$0.010)
- ~3,000 tokens de entrada + 800 de saída do Claude — ~$0.02
Número redondo: ~3 centavos por resposta pesquisada. A 1,000 chamadas de research por dia, isso dá ~$30/dia — mais ou menos o orçamento de café matinal de um analista. Faça orçamento para o LLM, não para as APIs de busca.
Três refinamentos para produção
1. Faça cache por pergunta
Se o seu produto faz perguntas semelhantes, calcule o hash de (question, today's date) e faça cache da resposta final por uma hora. A maior parte do research impulsionado por usuários tem taxas de acerto de cauda longa acima de 30%.
2. Restrinja quando a atualidade importa
Para perguntas de 'esta semana' / 'hoje', defina start_date na chamada de busca para a data atual ou ontem. Sem isso, a busca pode retornar artigos do ano passado. A versão mais simples: uma regra de system prompt como 'sempre inclua start_date quando a pergunta contiver hoje, esta semana, recente ou mais recente'.
3. Salvaguardas contra alucinação
A regra de maior alavancagem individual: 'Se você não tiver conteúdo extraído suficiente para responder, retorne: Não consegui encontrar uma fonte confiável para isto. Não adivinhe.' Adicionar isso ao system prompt reduz as respostas inventadas em uma ordem de magnitude em nossos testes.
Para onde levá-lo a seguir
Três extensões naturais:
- Verticalize: troque Web Search por Academic Search para um agente de revisão de literatura, SEC Filings Search para due diligence, ou Clinical Search para pesquisa médica. O handler não muda — só o system prompt.
- Adicione saída estruturada: peça ao modelo que retorne JSON com os campos
answerecitations[]para que você possa renderizá-los como um card de UI. - Faça streaming de tokens: mude para
client.messages.stream()para saída incremental — útil quando a resposta é longa. O loop de tool-call é o mesmo.
Perguntas Frequentes
Por que duas ferramentas em vez de um único endpoint 'answer'?
Separar busca e extração dá a você controle sobre quantas URLs ler, quando parar e quais citar. Um único endpoint hospedado de 'answer' esconde isso — você não consegue mudar a estratégia facilmente sem rearquitetar. Com duas ferramentas, o mesmo código vira um agente de briefing matinal, um revisor de literatura ou um scraper de inteligência competitiva apenas ajustando o prompt.
Isso também funciona com OpenAI Assistants / Responses API?
Sim. A arquitetura é idêntica — a única coisa que muda é como você faz o parsing dos blocos de tool-call e envia os resultados. O formato do handler passa a ser submit_tool_outputs(...) em vez de um bloco de conteúdo tool_result, mas o loop do agente e as definições de ferramenta são o mesmo JSON.
Como o agente decide quando parar?
O Claude retorna stop_reason: end_turn quando tem contexto suficiente para responder, e tool_use quando quer chamar uma ferramenta. O loop simplesmente continua até end_turn. Na prática, o modelo costuma fazer de 1 a 3 ciclos de busca/extração antes de responder.
Como garanto que ele realmente cite as fontes?
Duas alavancas. Primeira: no system prompt, exija explicitamente '[source: URL]' inline após cada afirmação. Segunda: a resposta do URL Extract inclui a URL, então quando o modelo a tem no contexto ele tende a citar naturalmente. Se as citações escaparem, adicione um passo final de formatação que rejeite a resposta e peça ao modelo que adicione as citações.
E quanto à latência?
Cada ida e volta de busca leva ~1s; cada lote de extração leva de 1 a 4s dependendo do número de URLs e da renderização de JS. Uma chamada típica de 'pesquise uma pergunta' fica entre 5 e 15s de relógio. Se você precisar mais rápido, paralelize o passo de extração (o extract já recebe uma lista de URLs em uma única chamada) e limite o modelo a uma rodada de ferramentas por pergunta com instruções no system prompt.
APIs usadas neste artigo
Sarah Choy é a CEO da API Pick. Ela escreve sobre a construção de APIs prontas para produção para agentes de IA e fluxos de trabalho com LLMs.