Guia Prático: Construindo Agentes Autônomos com Claude e Python em 2026
Tutorial completo para construir agentes autônomos com Claude e Python — tool use, agentic loops, error handling, multi-step tasks e exemplos funcionais com o Anthropic SDK. Do zero ao agente em produção.
Há uma diferença fundamental entre usar um LLM e construir um agente. Usar um LLM é como fazer uma pergunta e receber uma resposta. Construir um agente é criar um sistema que percebe, raciocina e age — de forma iterativa, com ferramentas, sem intervenção humana a cada passo.
Neste tutorial, você vai construir agentes reais do zero. Não proof-of-concepts que impressionam em demo. Sistemas que funcionam em produção.
Vou cobrir:
- Fundamentos do Tool Use com Claude
- Agentic loops robustos
- Error handling que não quebra em produção
- Multi-step tasks com estado
- Um agente completo de análise e coding
Pré-requisitos: Python 3.11+, conhecimento básico de programação assíncrona, conta na Anthropic com chave API.
Setup do Ambiente
# Criar ambiente virtual
python -m venv .venv
source .venv/bin/activate # Linux/Mac
# .venv\Scripts\activate # Windows
# Instalar dependências
pip install anthropic>=0.40.0 httpx pydantic python-dotenv loguru
# Verificar instalação
python -c "import anthropic; print(anthropic.__version__)"
Crie um arquivo .env:
ANTHROPIC_API_KEY=sk-ant-...
E um módulo base para carregar as configs:
# config.py
from dotenv import load_dotenv
import os
load_dotenv()
ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY")
if not ANTHROPIC_API_KEY:
raise ValueError("ANTHROPIC_API_KEY não configurado no .env")
# Modelos disponíveis em 2026
MODELS = {
"opus": "claude-opus-4-6-20250205",
"sonnet": "claude-sonnet-4-6-20250220",
"haiku": "claude-haiku-3-5-20241022" # para tasks simples e baratas
}
Parte 1: Tool Use — A Base de Tudo
Tool Use (ou Function Calling) é o mecanismo pelo qual o modelo pode invocar código que você define. É o que transforma um chatbot em um agente.
O fluxo é:
- Você define ferramentas (nome, descrição, schema de inputs)
- O modelo decide quando e como usar cada ferramenta
- Você executa a ferramenta e retorna o resultado
- O modelo continua o raciocínio com o resultado
# tools.py — Definindo ferramentas
from typing import Any
import httpx
import subprocess
import json
from pathlib import Path
from loguru import logger
# ============================================================
# Definições das ferramentas (schema para o Claude)
# ============================================================
TOOLS = [
{
"name": "fetch_url",
"description": "Busca o conteúdo de uma URL e retorna o texto. Use para acessar documentação, APIs ou páginas web.",
"input_schema": {
"type": "object",
"properties": {
"url": {
"type": "string",
"description": "URL completa a ser acessada"
},
"headers": {
"type": "object",
"description": "Headers HTTP opcionais (ex: Authorization)",
"additionalProperties": {"type": "string"}
}
},
"required": ["url"]
}
},
{
"name": "run_python",
"description": "Executa um script Python e retorna stdout e stderr. Use para cálculos, transformações de dados e validações.",
"input_schema": {
"type": "object",
"properties": {
"code": {
"type": "string",
"description": "Código Python a executar"
},
"timeout_seconds": {
"type": "integer",
"description": "Timeout em segundos (padrão: 30)",
"default": 30
}
},
"required": ["code"]
}
},
{
"name": "read_file",
"description": "Lê o conteúdo de um arquivo local.",
"input_schema": {
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "Caminho absoluto ou relativo do arquivo"
}
},
"required": ["path"]
}
},
{
"name": "write_file",
"description": "Escreve ou sobrescreve um arquivo local com o conteúdo fornecido.",
"input_schema": {
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "Caminho do arquivo a ser escrito"
},
"content": {
"type": "string",
"description": "Conteúdo a ser escrito no arquivo"
}
},
"required": ["path", "content"]
}
},
{
"name": "list_directory",
"description": "Lista o conteúdo de um diretório.",
"input_schema": {
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "Caminho do diretório"
},
"pattern": {
"type": "string",
"description": "Padrão glob para filtrar (ex: '*.py')"
}
},
"required": ["path"]
}
}
]
# ============================================================
# Implementação das ferramentas
# ============================================================
def execute_tool(name: str, inputs: dict) -> str:
"""
Executa uma ferramenta pelo nome e retorna o resultado como string.
Sempre retorna string — nunca lança exceção para o agente.
"""
try:
if name == "fetch_url":
return _fetch_url(**inputs)
elif name == "run_python":
return _run_python(**inputs)
elif name == "read_file":
return _read_file(**inputs)
elif name == "write_file":
return _write_file(**inputs)
elif name == "list_directory":
return _list_directory(**inputs)
else:
return f"ERRO: Ferramenta '{name}' não reconhecida."
except Exception as e:
logger.error(f"Erro ao executar ferramenta '{name}': {e}")
return f"ERRO ao executar {name}: {type(e).__name__}: {str(e)}"
def _fetch_url(url: str, headers: dict = None) -> str:
with httpx.Client(timeout=30, follow_redirects=True) as client:
response = client.get(url, headers=headers or {})
response.raise_for_status()
# Truncar se muito grande
text = response.text
if len(text) > 50000:
text = text[:50000] + "\n\n[... conteúdo truncado após 50K chars ...]"
return text
def _run_python(code: str, timeout_seconds: int = 30) -> str:
result = subprocess.run(
["python3", "-c", code],
capture_output=True,
text=True,
timeout=timeout_seconds
)
output = ""
if result.stdout:
output += f"STDOUT:\n{result.stdout}"
if result.stderr:
output += f"\nSTDERR:\n{result.stderr}"
if result.returncode != 0:
output += f"\nExit code: {result.returncode}"
return output or "(sem output)"
def _read_file(path: str) -> str:
content = Path(path).read_text(encoding="utf-8")
if len(content) > 100000:
content = content[:100000] + "\n\n[... arquivo truncado após 100K chars ...]"
return content
def _write_file(path: str, content: str) -> str:
p = Path(path)
p.parent.mkdir(parents=True, exist_ok=True)
p.write_text(content, encoding="utf-8")
return f"Arquivo escrito com sucesso: {path} ({len(content)} chars)"
def _list_directory(path: str, pattern: str = "*") -> str:
p = Path(path)
if not p.exists():
return f"ERRO: Diretório não existe: {path}"
files = list(p.glob(pattern))
if not files:
return f"Diretório vazio (padrão: {pattern})"
lines = [f"{'[DIR]' if f.is_dir() else '[ARQ]'} {f.name}" for f in sorted(files)]
return "\n".join(lines)
Parte 2: O Agentic Loop
O coração de qualquer agente é o loop: o modelo recebe uma tarefa, pensa, usa ferramentas, recebe resultados, e continua até terminar.
# agent.py — O loop principal do agente
import anthropic
from loguru import logger
from typing import Optional
from dataclasses import dataclass, field
from config import MODELS, ANTHROPIC_API_KEY
from tools import TOOLS, execute_tool
client = anthropic.Anthropic(api_key=ANTHROPIC_API_KEY)
@dataclass
class AgentConfig:
"""Configuração do agente"""
model: str = MODELS["sonnet"] # padrão: Sonnet (custo/benefício)
max_tokens: int = 8192
max_iterations: int = 20 # limite de segurança
thinking_budget: Optional[int] = None # None = sem extended thinking
system_prompt: str = """Você é um agente de engenharia de software especializado.
Suas capacidades:
- Buscar informações na web e em documentação
- Escrever, ler e modificar arquivos
- Executar código Python para validação
- Analisar código e identificar problemas
Princípios:
- Seja sistemático: planeje antes de executar
- Valide resultados: teste antes de afirmar que funcionou
- Seja transparente: explique o que está fazendo
- Trate erros: se algo falhar, tente uma abordagem alternativa
- Conclua a tarefa: continue até o objetivo estar completo"""
@dataclass
class AgentState:
"""Estado da sessão do agente"""
messages: list = field(default_factory=list)
iterations: int = 0
tool_calls_made: list = field(default_factory=list)
class Agent:
def __init__(self, config: AgentConfig = None):
self.config = config or AgentConfig()
def run(self, task: str, verbose: bool = True) -> str:
"""
Executa uma tarefa de forma autônoma.
Retorna a resposta final do agente.
"""
state = AgentState()
state.messages = [{"role": "user", "content": task}]
if verbose:
logger.info(f"🚀 Iniciando tarefa: {task[:100]}...")
while state.iterations < self.config.max_iterations:
state.iterations += 1
if verbose:
logger.info(f"📍 Iteração {state.iterations}/{self.config.max_iterations}")
# Montar parâmetros da chamada
call_params = {
"model": self.config.model,
"max_tokens": self.config.max_tokens,
"tools": TOOLS,
"messages": state.messages,
"system": self.config.system_prompt
}
# Adicionar extended thinking se configurado
if self.config.thinking_budget:
call_params["thinking"] = {
"type": "enabled",
"budget_tokens": self.config.thinking_budget
}
# Chamar o modelo
try:
response = client.messages.create(**call_params)
except anthropic.RateLimitError:
import time
logger.warning("Rate limit atingido. Aguardando 30s...")
time.sleep(30)
continue
except anthropic.APIError as e:
logger.error(f"Erro na API: {e}")
return f"ERRO: Falha na API — {str(e)}"
# Verificar se terminou
if response.stop_reason == "end_turn":
final_text = self._extract_text(response.content)
if verbose:
logger.success(f"✅ Tarefa concluída em {state.iterations} iterações")
return final_text
# Processar tool calls
if response.stop_reason == "tool_use":
tool_results = []
# Extrair e executar todas as ferramentas chamadas
for block in response.content:
if block.type == "tool_use":
tool_name = block.name
tool_inputs = block.input
if verbose:
logger.info(f"🔧 Usando ferramenta: {tool_name}")
logger.debug(f" Inputs: {str(tool_inputs)[:200]}")
# Executar a ferramenta
result = execute_tool(tool_name, tool_inputs)
state.tool_calls_made.append({
"tool": tool_name,
"inputs": tool_inputs,
"result_preview": result[:100]
})
if verbose:
logger.debug(f" Resultado: {result[:200]}...")
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": result
})
# Adicionar ao histórico
state.messages.append({
"role": "assistant",
"content": response.content
})
state.messages.append({
"role": "user",
"content": tool_results
})
continue
# Stop reason inesperado
logger.warning(f"Stop reason inesperado: {response.stop_reason}")
return self._extract_text(response.content)
# Limite de iterações atingido
logger.warning(f"⚠️ Limite de {self.config.max_iterations} iterações atingido")
return f"AVISO: Tarefa não concluída após {self.config.max_iterations} iterações."
def _extract_text(self, content: list) -> str:
"""Extrai texto dos blocos de conteúdo da resposta"""
texts = []
for block in content:
if hasattr(block, 'text'):
texts.append(block.text)
# Ignorar blocos thinking — são internos do modelo
return "\n".join(texts) if texts else "(sem resposta textual)"
Parte 3: Multi-Step Tasks com Estado Persistente
Agentes reais precisam de estado entre chamadas. Aqui está como construir um agente com memória de sessão:
# persistent_agent.py — Agente com memória de sessão
import json
from pathlib import Path
from datetime import datetime
from agent import Agent, AgentConfig
from config import MODELS
from loguru import logger
class PersistentAgent(Agent):
"""
Agente com memória persistente entre sessões.
Salva histórico de conversas e pode continuar de onde parou.
"""
def __init__(self, session_id: str, config: AgentConfig = None):
super().__init__(config)
self.session_id = session_id
self.session_path = Path(f"sessions/{session_id}.json")
self.session_data = self._load_session()
def _load_session(self) -> dict:
if self.session_path.exists():
data = json.loads(self.session_path.read_text())
logger.info(f"📂 Sessão carregada: {self.session_id} ({len(data.get('tasks', []))} tarefas anteriores)")
return data
return {"session_id": self.session_id, "created_at": datetime.now().isoformat(), "tasks": []}
def _save_session(self):
self.session_path.parent.mkdir(exist_ok=True)
self.session_path.write_text(json.dumps(self.session_data, indent=2, default=str))
def run_with_memory(self, task: str, verbose: bool = True) -> str:
"""Executa uma tarefa e salva no histórico da sessão"""
# Construir contexto das tarefas anteriores
context = self._build_context()
if context:
full_task = f"""CONTEXTO DA SESSÃO:
{context}
NOVA TAREFA:
{task}"""
else:
full_task = task
# Executar
result = self.run(full_task, verbose=verbose)
# Salvar no histórico
self.session_data["tasks"].append({
"timestamp": datetime.now().isoformat(),
"task": task,
"result_preview": result[:500],
"tool_calls": len(self._last_state_tool_calls()) if hasattr(self, '_state') else 0
})
self._save_session()
return result
def _build_context(self) -> str:
"""Constrói resumo das tarefas anteriores para contexto"""
tasks = self.session_data.get("tasks", [])
if not tasks:
return ""
# Últimas 5 tarefas
recent = tasks[-5:]
lines = []
for t in recent:
lines.append(f"[{t['timestamp'][:19]}] TAREFA: {t['task'][:100]}")
lines.append(f" RESULTADO: {t['result_preview'][:200]}")
return "\n".join(lines)
def _last_state_tool_calls(self) -> list:
return getattr(self, '_last_tool_calls', [])
Parte 4: Tratamento de Erros em Produção
Este é o capítulo que a maioria dos tutoriais ignora. Em produção, tudo que pode falhar vai falhar. Seu agente precisa sobreviver a isso.
# robust_agent.py — Agente com error handling robusto
import anthropic
import time
from typing import Optional
from loguru import logger
from agent import Agent, AgentConfig, AgentState
from tools import execute_tool, TOOLS
class RobustAgent(Agent):
"""
Agente com error handling para produção.
Inclui: retry com backoff, circuit breaker, timeout, logging detalhado.
"""
def __init__(self, config: AgentConfig = None, max_retries: int = 3):
super().__init__(config)
self.max_retries = max_retries
self._circuit_open = False
self._consecutive_errors = 0
self._circuit_threshold = 5
def run(self, task: str, verbose: bool = True) -> str:
"""Override com retry e circuit breaker"""
if self._circuit_open:
return "ERRO: Circuit breaker aberto — muitas falhas consecutivas. Aguarde e tente novamente."
for attempt in range(1, self.max_retries + 1):
try:
result = self._run_with_timeout(task, verbose)
self._consecutive_errors = 0 # reset no sucesso
return result
except anthropic.RateLimitError as e:
wait_time = self._extract_retry_after(e) or (30 * attempt)
logger.warning(f"Rate limit (tentativa {attempt}/{self.max_retries}). Aguardando {wait_time}s...")
time.sleep(wait_time)
except anthropic.InternalServerError as e:
wait_time = 5 * attempt
logger.error(f"Erro interno da API (tentativa {attempt}): {e}. Aguardando {wait_time}s...")
time.sleep(wait_time)
except anthropic.APIConnectionError as e:
wait_time = 10 * attempt
logger.error(f"Erro de conexão (tentativa {attempt}): {e}. Aguardando {wait_time}s...")
time.sleep(wait_time)
except TimeoutError:
logger.error(f"Timeout na tentativa {attempt}/{self.max_retries}")
if attempt == self.max_retries:
return "ERRO: Timeout — tarefa demorou mais que o esperado."
except Exception as e:
self._consecutive_errors += 1
if self._consecutive_errors >= self._circuit_threshold:
self._circuit_open = True
logger.critical(f"Circuit breaker ABERTO após {self._consecutive_errors} erros consecutivos")
logger.error(f"Erro inesperado (tentativa {attempt}): {type(e).__name__}: {e}")
if attempt == self.max_retries:
return f"ERRO: Falha após {self.max_retries} tentativas — {str(e)}"
return "ERRO: Todas as tentativas falharam."
def _run_with_timeout(self, task: str, verbose: bool, timeout_seconds: int = 300) -> str:
"""Executa com timeout via threading"""
import threading
result_holder = [None]
error_holder = [None]
def target():
try:
result_holder[0] = super(RobustAgent, self).run(task, verbose)
except Exception as e:
error_holder[0] = e
thread = threading.Thread(target=target)
thread.start()
thread.join(timeout=timeout_seconds)
if thread.is_alive():
raise TimeoutError(f"Tarefa excedeu {timeout_seconds}s")
if error_holder[0]:
raise error_holder[0]
return result_holder[0]
def _extract_retry_after(self, error: anthropic.RateLimitError) -> Optional[int]:
"""Tenta extrair o tempo de espera do header Retry-After"""
try:
# A API da Anthropic pode incluir isso no erro
return int(str(error).split("retry after")[1].split("s")[0].strip())
except:
return None
def reset_circuit(self):
"""Reset manual do circuit breaker"""
self._circuit_open = False
self._consecutive_errors = 0
logger.info("Circuit breaker resetado")
Parte 5: Agente Completo — Análise de Código e Geração de Relatório
Vamos juntar tudo em um exemplo completo e funcional:
# code_analysis_agent.py — Agente de análise de código
from robust_agent import RobustAgent
from agent import AgentConfig
from config import MODELS
def create_code_analyst() -> RobustAgent:
"""Cria um agente especializado em análise de código"""
config = AgentConfig(
model=MODELS["opus"], # Opus para análise crítica
max_tokens=8192,
max_iterations=15,
thinking_budget=20000, # Extended thinking para análise profunda
system_prompt="""Você é um engenheiro sênior especializado em revisão de código Python.
Suas responsabilidades:
1. Analisar código em busca de bugs, vulnerabilidades e problemas de performance
2. Verificar boas práticas (PEP 8, type hints, docstrings)
3. Identificar oportunidades de refatoração
4. Gerar relatórios detalhados e acionáveis
5. Sugerir código melhorado quando necessário
Ao analisar código:
- Leia os arquivos primeiro
- Execute análises estáticas quando possível
- Verifique testes existentes
- Gere um relatório estruturado em markdown"""
)
return RobustAgent(config=config, max_retries=3)
def analyze_project(project_path: str) -> str:
"""
Analisa um projeto Python e retorna relatório de qualidade.
Args:
project_path: Caminho para o diretório do projeto
Returns:
Relatório em markdown com findings e recomendações
"""
agent = create_code_analyst()
task = f"""Analise o projeto Python em: {project_path}
Siga este processo:
1. Liste todos os arquivos Python no diretório
2. Leia cada arquivo Python relevante
3. Para cada arquivo, execute análise usando ferramentas quando necessário
4. Execute os testes se existirem (procure pytest.ini ou conftest.py)
5. Gere um relatório markdown com:
- Resumo executivo (3-5 bullets)
- Bugs encontrados (por severidade: critical/high/medium/low)
- Vulnerabilidades de segurança
- Problemas de qualidade de código
- Cobertura de testes (se houver)
- Recomendações prioritárias (top 5)
- Exemplo de refatoração para o problema mais crítico
Salve o relatório em: {project_path}/code_review_report.md"""
return agent.run(task)
if __name__ == "__main__":
import sys
project = sys.argv[1] if len(sys.argv) > 1 else "."
print(f"\n🔍 Analisando projeto: {project}\n")
result = analyze_project(project)
print("\n" + "="*60)
print("RESULTADO:")
print("="*60)
print(result)
Resultado Esperado
Quando você executa o agente de análise:
python code_analysis_agent.py /meu/projeto
Output esperado:
🚀 Iniciando tarefa: Analise o projeto Python em: /meu/projeto...
📍 Iteração 1/15
🔧 Usando ferramenta: list_directory
Resultado: [ARQ] main.py
[ARQ] api.py
[ARQ] models.py
[ARQ] utils.py
[ARQ] tests/test_main.py...
📍 Iteração 2/15
🔧 Usando ferramenta: read_file
Resultado: # main.py...
📍 Iteração 3/15
🔧 Usando ferramenta: run_python
Resultado: STDOUT: Found 3 issues...
📍 Iteração 4/15
🔧 Usando ferramenta: write_file
Resultado: Arquivo escrito com sucesso: /meu/projeto/code_review_report.md...
✅ Tarefa concluída em 4 iterações
============================================================
RESULTADO:
============================================================
Relatório salvo em /meu/projeto/code_review_report.md
## Resumo Executivo
- 2 vulnerabilidades de segurança encontradas (SQL injection e credenciais hardcoded)
- 5 funções sem type hints em módulos críticos
- Cobertura de testes: 34% (recomendado: >80%)
- 1 memory leak identificado em utils.py
- Performance: 3 consultas N+1 em models.py
## Bugs Críticos
### [CRITICAL] SQL Injection em api.py:45
...
O relatório completo em markdown é salvo no projeto com todos os detalhes.
Parte 6: Boas Práticas para Produção
Depois de meses rodando agentes em produção, aqui está o que aprendi:
1. Sempre limite iterações
config = AgentConfig(max_iterations=20) # nunca sem limite
2. Implemente observabilidade desde o início
# Use Langfuse ou similar para tracking em produção
from langfuse import Langfuse
langfuse = Langfuse()
trace = langfuse.trace(name="agent_run", input={"task": task})
# ... log de cada iteração e tool call
3. Cuide do custo
def estimate_cost(model: str, input_tokens: int, output_tokens: int) -> float:
prices = {
"claude-opus-4-6-20250205": (5.0, 25.0),
"claude-sonnet-4-6-20250220": (3.0, 15.0),
"claude-haiku-3-5-20241022": (0.8, 4.0)
}
input_price, output_price = prices.get(model, (5.0, 25.0))
return (input_tokens / 1_000_000 * input_price) + (output_tokens / 1_000_000 * output_price)
4. Valide inputs e sanitize ferramentas
import re
def safe_path(path: str) -> str:
"""Previne path traversal em ferramentas de arquivo"""
# Normaliza e verifica que está no diretório permitido
resolved = Path(path).resolve()
allowed_base = Path("/workspace").resolve()
if not str(resolved).startswith(str(allowed_base)):
raise ValueError(f"Acesso negado: {path} fora do diretório permitido")
return str(resolved)
5. Documente o contrato de cada ferramenta
Ferramentas bem documentadas = modelos que usam certo. Dedique tempo às descriptions e aos exemplos no schema.
Próximos Passos
Com essa base, você pode construir:
- Agentes de data analysis: conectar com bancos de dados, gerar visualizações
- Agentes de DevOps: monitorar sistemas, interpretar logs, escalar recursos
- Agentes financeiros: analisar relatórios, calcular métricas, gerar alertas
- Agentes de pesquisa: buscar, filtrar e sintetizar informações
Para ver como esses agentes se encaixam no panorama de IA de 2026, confira o artigo IA em 2026: Tendências e Ferramentas que Todo Desenvolvedor Precisa Conhecer.
Para escolher o modelo certo para seu agente, compare o Claude Opus 4.6 e o Claude Sonnet 4.6 — a escolha importa dependendo do tipo de tarefa.
Paulo Cesar é desenvolvedor e arquiteto de sistemas com 20 anos de experiência, especializado em IA aplicada e automação financeira.
Sobre o autor
Paulo Cesar
Desenvolvedor sênior com mais de 20 anos de experiência em desenvolvimento de software, IA aplicada, automação e sistemas financeiros. Criador do Inteligência em Código, onde compartilha conhecimento prático sobre inteligência artificial para desenvolvedores brasileiros.