Pular para o conteúdo
🧠Inteligência em Código
📚 Tutoriais14 min de leitura

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.

#claude#agentes#python#automacao#anthropic#tutorial

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 é:

  1. Você define ferramentas (nome, descrição, schema de inputs)
  2. O modelo decide quando e como usar cada ferramenta
  3. Você executa a ferramenta e retorna o resultado
  4. 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.

Paulo Cesar

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.