Você usa o ChatGPT todo dia. Cola texto, recebe texto de volta, se sente produtivo. Mas toda conferência de tecnologia em 2026 fica repetindo uma palavra: agents. Seu PM diz "a gente precisa de um agent." Seu CTO diz "agents são o futuro." O LinkedIn está afogado em textão sobre agents. E você sentado ali pensando: "Eu nem sei o que isso significa."
Aqui tá a lacuna. Um chatbot espera você digitar e responde — tipo trocar mensagem com um amigo esperto. Um AI agent é diferente. Um agent tem um objetivo, escolhe suas próprias ferramentas, lida com erros e continua trabalhando até terminar o serviço ou decidir que não dá. Ninguém segura na mão dele. É a diferença entre fazer uma pergunta pra alguém e contratar alguém pra fazer um trabalho.
Hoje à noite, você fecha essa lacuna. Vai construir um agent de verdade — em Python, do zero, sem frameworks — que pesquisa na web, analisa informações, toma decisões e salva um relatório de pesquisa no disco. Na hora de dormir, você vai entender o padrão exato que roda por trás do Claude Code, Codex, Devin e todo outro produto de agent cobrando $200/mês. O padrão em si leva umas 30 linhas.
O que vamos construir
Um Research Agent que:
- Recebe um tópico de você
- Pesquisa na web por informações relevantes
- Lê e analisa o que encontra
- Escreve um resumo estruturado da pesquisa
- Salva o resultado em um arquivo
Isso não é um demo de brinquedo. É a mesma arquitetura usada por agents em produção — tool use, loops de raciocínio, output estruturado. A única diferença entre isso e um "agent de produção" é tratamento de erros e escala.
Passo 1: Configurar o projeto (10 minutos)
mkdir research-agent && cd research-agent
python3 -m venv venv
source venv/bin/activate
pip install anthropic httpx
Duas dependências:
- anthropic — o SDK Python (software development kit — uma biblioteca pronta pra conversar com a API do Claude)
- httpx — pra fazer requisições web pelo Python
Você também precisa de uma API key da Anthropic — basicamente uma senha que permite seu código falar com o Claude. Pegue uma em console.anthropic.com. Contas novas ganham $5 em créditos grátis, o que dá pra rodar esse agent centenas de vezes.
export ANTHROPIC_API_KEY=sk-ant-...
touch agent.py
Passo 2: Definir as tools (15 minutos)
Um agent sem tools é só um chatbot. Tools são funções que permitem o agent interagir com o mundo real — pesquisar na web, ler arquivos, chamar APIs (como programas gritam uns com os outros pela internet — tipo mensagem de máquina pra máquina).
Vamos dar ao nosso agent duas tools:
# agent.py
import anthropic
import httpx
import json
import os
from datetime import datetime
client = anthropic.Anthropic()
MODEL = "claude-haiku-4.5"
# Definições das tools — isso diz pro Claude o que tá disponível
tools = [
{
"name": "web_search",
"description": "Search the web for information on a topic. Returns results with titles, URLs, and snippets.",
"input_schema": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "The search query"
}
},
"required": ["query"]
}
},
{
"name": "save_file",
"description": "Save text content to a file on disk.",
"input_schema": {
"type": "object",
"properties": {
"filename": {
"type": "string",
"description": "Name of the file to save"
},
"content": {
"type": "string",
"description": "Content to write to the file"
}
},
"required": ["filename", "content"]
}
}
]
Essas definições funcionam como um cardápio de restaurante. O Claude lê as descrições e decide quando usar cada tool — você não define a ordem no código. A parte input_schema usa JSON Schema — um formato padrão pra descrever como dados se parecem, pra que o Claude saiba exatamente quais parâmetros cada tool espera. Sim, você descreve o formato dos seus dados usando outro formato de dados. Bem-vindo à programação.
Passo 3: Implementar as tools (15 minutos)
Definições dizem pro Claude o que existe. Agora a gente escreve o código que realmente roda quando o Claude chama uma tool. É aqui que a teoria encontra a prática — ou, mais precisamente, onde suas abstrações bonitas encontram a realidade nua e crua de parsear HTML como se fosse 2003:
def execute_tool(tool_name: str, tool_input: dict) -> str:
"""Execute a tool and return the result as a string."""
if tool_name == "web_search":
return do_web_search(tool_input["query"])
elif tool_name == "save_file":
return do_save_file(tool_input["filename"], tool_input["content"])
else:
return f"Error: Unknown tool '{tool_name}'"
def do_web_search(query: str) -> str:
"""Search using DuckDuckGo's HTML endpoint. No API key needed."""
try:
response = httpx.get(
"https://html.duckduckgo.com/html/",
params={"q": query},
headers={"User-Agent": "ResearchAgent/1.0"},
timeout=10.0,
)
response.raise_for_status()
text = response.text
results = []
parts = text.split('class="result__snippet"')
for part in parts[1:6]: # Pega até 5 resultados
snippet_end = part.find("</a>")
if snippet_end > 0:
snippet = part[:snippet_end]
clean = snippet.replace("<b>", "").replace("</b>", "")
clean = clean.split(">")[-1] if ">" in clean else clean
if clean.strip():
results.append(clean.strip())
if results:
return "Search results:\n" + "\n".join(
f"- {r}" for r in results
)
return f"Search completed but no clear results for: {query}"
except Exception as e:
return f"Search error: {str(e)}"
def do_save_file(filename: str, content: str) -> str:
"""Save content to the output directory."""
os.makedirs("output", exist_ok=True)
filepath = os.path.join("output", filename)
try:
with open(filepath, "w") as f:
f.write(content)
return f"File saved successfully: {filepath}"
except Exception as e:
return f"Error saving file: {str(e)}"
A busca web usa o endpoint HTML do DuckDuckGo — sem API key, sem cadastro, sem custo. O parsing de HTML é mantido junto com fita adesiva e fé (estamos raspando markup cru da página em vez de usar um feed de dados decente), mas funciona. Pra produção, você trocaria por Brave Search API (2.000 consultas grátis/mês) ou um SearXNG self-hosted.
Passo 4: Construir o loop do agent (20 minutos)
Esse é o coração de tudo. Todo produto de agent com landing page bonita e valuation de $50M roda alguma versão dessas 30 linhas:
def run_agent(topic: str, max_turns: int = 10) -> str:
"""Run the research agent on a topic."""
print(f"\n{'='*60}")
print(f"Research Agent — Topic: {topic}")
print(f"{'='*60}\n")
system_prompt = """You are a research agent. Your job is to research a topic
thoroughly and produce a well-structured summary.
Your process:
1. Search for information on the topic (multiple searches with different angles)
2. Analyze what you find
3. Write a comprehensive research summary
4. Save the summary to a file
Be thorough — do at least 3 different searches to cover the topic well.
Be critical — evaluate sources and note conflicting information.
When done, save the final summary as a markdown file.
Current date: """ + datetime.now().strftime("%Y-%m-%d")
messages = [
{
"role": "user",
"content": f"Research this topic and produce a detailed summary: {topic}"
}
]
# O loop do agent
for turn in range(max_turns):
print(f"--- Turn {turn + 1} ---")
response = client.messages.create(
model=MODEL,
max_tokens=4096,
system=system_prompt,
tools=tools,
messages=messages,
)
print(f"Stop reason: {response.stop_reason}")
if response.stop_reason == "tool_use":
tool_results = []
for block in response.content:
if block.type == "tool_use":
tool_name = block.name
tool_input = block.input
tool_id = block.id
print(f" Tool: {tool_name}")
print(f" Input: {json.dumps(tool_input, indent=2)[:200]}")
result = execute_tool(tool_name, tool_input)
print(f" Result: {result[:200]}...")
tool_results.append({
"type": "tool_result",
"tool_use_id": tool_id,
"content": result,
})
messages.append({"role": "assistant", "content": response.content})
messages.append({"role": "user", "content": tool_results})
elif response.stop_reason == "end_turn":
final_text = ""
for block in response.content:
if hasattr(block, "text"):
final_text += block.text
print(f"\nAgent completed in {turn + 1} turns.")
return final_text
return "Agent did not complete within turn limit."
Deixa eu destrinchar esse loop, porque é o truque inteiro de mágica:
- Envia a tarefa pro Claude junto com as definições das tools
- Claude pensa e decide se vai usar uma tool ou responder com texto final
- Se tool_use — executamos a tool e mandamos o resultado de volta como nova mensagem
- Claude vê o resultado e decide o próximo passo
- Repete até o Claude retornar
end_turn— ou seja, ele terminou
O insight crucial: você não codificou "primeiro pesquisa, depois analisa, depois escreve." O Claude descobre o fluxo de trabalho com base na tarefa. É isso que separa um agent de um script. Um script segue suas instruções. Um agent segue as próprias.
O campo stop_reason é a chave. A API do Claude retorna ou "tool_use" (quero chamar uma tool) ou "end_turn" (terminei). Seu loop só checa qual é e age de acordo.
Passo 5: Adicionar o entry point (5 minutos)
A parte chata. Mas até as partes chatas precisam existir, senão nada roda — uma lição que metade dos repos de demo de IA no GitHub ainda não aprendeu:
if __name__ == "__main__":
import sys
if len(sys.argv) > 1:
topic = " ".join(sys.argv[1:])
else:
topic = input("Enter research topic: ")
result = run_agent(topic)
print(f"\n{'='*60}")
print("Research complete. Check the output/ directory.")
print(f"{'='*60}")
Passo 6: Rodar (5 minutos)
python agent.py "current state of MCP protocol adoption in 2026"
Observe o terminal. Você vai ver o agent pensar sobre o problema sozinho:
============================================================
Research Agent — Topic: current state of MCP protocol adoption in 2026
============================================================
--- Turn 1 ---
Stop reason: tool_use
Tool: web_search
Input: {"query": "MCP model context protocol adoption 2026"}
Result: Search results: - The MCP ecosystem has grown...
--- Turn 2 ---
Stop reason: tool_use
Tool: web_search
Input: {"query": "MCP servers enterprise production 2026"}
Result: Search results: - Amazon Bedrock AgentCore...
--- Turn 3 ---
Stop reason: tool_use
Tool: web_search
Input: {"query": "MCP protocol limitations challenges 2026"}
Result: Search results: - Stateful sessions fight with...
--- Turn 4 ---
Stop reason: tool_use
Tool: save_file
Input: {"filename": "mcp-research-2026.md", "content": "# MCP Protocol..."}
Result: File saved successfully: output/mcp-research-2026.md
--- Turn 5 ---
Stop reason: end_turn
Agent completed in 5 turns.
Cinco turns. Três pesquisas, um save de arquivo, um resumo final. Ninguém mandou ele pesquisar de ângulos diferentes — ele decidiu sozinho. Isso é agência, não scripting.
Passo 7: Deixar mais esperto (30 minutos)
O agent básico funciona. Agora vamos adicionar três upgrades que transformam ele de demo em algo que você realmente vai continuar usando.
Memória entre sessões
Agora, toda execução começa do zero. Vamos dar ao agent uma memória simples — um arquivo JSON (um formato de texto estruturado que programas conseguem ler e escrever facilmente) que armazena o que ele pesquisou antes:
from pathlib import Path
MEMORY_FILE = "memory.json"
def load_memory() -> list:
"""Load previous research topics and findings."""
if Path(MEMORY_FILE).exists():
with open(MEMORY_FILE) as f:
return json.load(f)
return []
def save_memory(topic: str, summary: str):
"""Save this research session to memory."""
memory = load_memory()
memory.append({
"date": datetime.now().isoformat(),
"topic": topic,
"summary": summary[:500],
})
memory = memory[-20:] # Mantém as últimas 20 entradas
with open(MEMORY_FILE, "w") as f:
json.dump(memory, f, indent=2)
Injete a memória no system prompt — o texto de instrução que molda como o Claude se comporta:
memory = load_memory()
if memory:
memory_context = "\n\nPrevious research sessions:\n"
for m in memory[-5:]:
memory_context += f"- [{m['date'][:10]}] {m['topic']}: {m['summary'][:100]}...\n"
system_prompt += memory_context
Agora o agent sabe o que pesquisou antes. Ele pode referenciar descobertas anteriores, evitar pesquisas duplicadas e construir em cima do trabalho passado.
Uma tool de pensamento
Essa aqui é um truque. Adicione uma tool que literalmente não faz nada:
tools.append({
"name": "think",
"description": "Use this tool to think through your approach before acting. Write out your reasoning and what you need to find out next.",
"input_schema": {
"type": "object",
"properties": {
"thought": {
"type": "string",
"description": "Your reasoning and plan"
}
},
"required": ["thought"]
}
})
No executor de tools, ela só retorna uma confirmação:
elif tool_name == "think":
print(f" Thinking: {tool_input['thought'][:300]}")
return "Thought recorded. Continue with your plan."
Por que adicionar uma tool que não faz nada? Porque ela dá ao agent um lugar estruturado pra raciocinar antes de agir. Sem ela, o Claude pula direto pras chamadas de tools. Com ela, o Claude para, planeja e depois executa — produzindo resultados visivelmente melhores. A Anthropic documenta essa técnica no guia de tool use, e agents em produção dependem dela.
Recuperação de erros
def execute_tool_safe(tool_name: str, tool_input: dict) -> str:
"""Execute a tool with automatic retries."""
for attempt in range(3):
try:
result = execute_tool(tool_name, tool_input)
if "error" in result.lower() and attempt < 2:
print(f" Retry {attempt + 1}...")
continue
return result
except Exception as e:
if attempt < 2:
print(f" Error, retrying: {e}")
continue
return f"Tool failed after 3 attempts: {str(e)}"
Requisições web falham. APIs caem. Timeouts acontecem. Um agent de produção tenta de novo antes de desistir. Três tentativas com fallback é o mínimo.
A estrutura final
research-agent/
├── agent.py # ~150 linhas de Python
├── memory.json # Criado automaticamente, armazena histórico de sessões
├── output/ # Criado automaticamente, armazena relatórios de pesquisa
│ └── *.md
└── requirements.txt # anthropic, httpx
Menos de 200 linhas. Duas dependências. Zero frameworks.
Por que sem framework?
Você pode estar se perguntando: "Por que não usar LangChain ou LlamaIndex?" (Ambos são frameworks Python populares que adicionam abstrações prontas em cima de chamadas LLM.)
Porque o loop do agent acima tem 30 linhas. O LangChain adicionaria 15 dependências e três camadas de abstração pro mesmo resultado.
Use um framework quando:
- Você precisa de 10+ tools com lógica de roteamento complexa
- Você precisa de memória de conversa que escale pra milhares de usuários
- Você precisa de múltiplos agents coordenando na mesma tarefa
- Você superou o Python simples e precisa da arquitetura de outra pessoa
Pule o framework quando:
- Você está construindo seu primeiro agent
- Seu agent tem 2–5 tools
- Você quer entender cada linha do que está rodando
- "Funciona e eu entendo" vale mais que "funciona e eu confio na abstração"
Em março de 2026, a documentação do SDK da Anthropic mostra o mesmo padrão de loop básico que acabamos de construir. A recomendação oficial é começar sem framework.
O que você construiu hoje à noite
Vamos fazer o inventário:
- Um AI agent funcional — recebe um objetivo, persegue ele de forma autônoma
- Tool use — o agent chama sistemas externos (busca web, I/O de arquivo)
- Um loop de raciocínio — o Claude decide a próxima ação com base nos resultados, não num script fixo
- Memória — o agent lembra de sessões passadas e constrói em cima delas
- Tratamento de erros — tenta de novo antes de falhar
- Persistência de output — resultados ficam em arquivos reais no seu disco
Essa é a mesma arquitetura central do Claude Code, Devin, OpenAI Codex e todo outro produto de agent. Eles têm ferramentas melhores, mais tratamento de erros e context windows maiores — a quantidade de texto que o modelo consegue "ver" de uma vez, tipo a memória de trabalho dele. Mas o loop é idêntico ao que você acabou de escrever.
Pra onde ir a partir daqui
Agora você entende o padrão fundamental. Todo o resto é engenharia em cima dele:
- Mais tools — uma calculadora, um web scraper, um conector de banco de dados, um executor de código
- Memória melhor — vector databases (sistemas que armazenam texto por significado, não só por palavras-chave) pra busca semântica entre sessões passadas
- Chamadas de tools em paralelo — rodar múltiplas pesquisas ao mesmo tempo em vez de sequencialmente
- Sistemas multi-agent — um segundo agent que revisa o trabalho do primeiro, tipo code review
- Integração com MCP — Model Context Protocol, um padrão pra conectar AI agents a ferramentas externas, tipo USB mas pra fontes de dados
Você não está aprendendo o framework de alguém que pode morrer em seis meses. Você está aprendendo o padrão. O mesmo padrão que funcionou em 2024, funciona em 2026 e vai funcionar em 2028 — porque a mecânica subjacente (modelo decide → tool executa → resultado volta) é como todos os sistemas de agents funcionam, independente do nome de marketing que usam.
A "indústria de AI agents" quer que você acredite que construir agents exige um PhD e uma Series B de $100M. Exige entender um loop: chamar o modelo, checar se ele quer uma tool, executar a tool, mandar o resultado de volta, repetir. É isso. Todo o resto é engenharia em torno desse loop — e agora você sabe o suficiente pra fazer essa engenharia sozinho.





