Agente é o próximo passo após tool calling: loop autônomo de pensar→agir até concluir. ReAct é o padrão dominante; planner/executor é alternativa para tarefas estruturadas. Aqui você aprende a controlar o loop — sem isso, agente vira disaster de custo.
🔁 Anatomia de um agente robusto
Não é só 'modelo + tools'. É um sistema com instrumentação, limites e recovery — como qualquer sistema de produção.
- •Loop core: ReAct ou planner/executor.
- •Limites: max_iterations, max_tokens, timeout.
- •Tracing: cada step estruturado.
- •Recovery: retry, abort, fallback.
- •Sandbox: tools sempre jailed.
📊 Yao et al. (2022) — ReAct
- +34% em HotpotQA com ReAct vs CoT puro.
- Reasoning trace explícito é o que dá ganho — facilita debug humano também.
- Loops longos degradam: tarefas além de 8-10 steps quase sempre regridem.
🧭 ReAct: Reason + Act
Yao et al. 2022
ReAct (Yao et al. 2022): a cada iteração, o modelo escreve Pensamento em texto livre, depois decide Ação (tool call), recebe Observação (resultado do tool), e continua. O pensamento explícito ajuda debug humano e melhora qualidade em tarefas multi-passo. Em HotpotQA (multi-hop QA), ReAct bate CoT puro em +34% (Yao 2022).
Pensamento: preciso buscar a janela do Claude 3.
Ação: buscar_em_corpus(query='janela contexto Claude 3', top_k=5)
Observação: [chunks com info sobre Claude 3 200k...]
Pensamento: agora preciso da janela do GPT-4.
Ação: buscar_em_corpus(query='janela contexto GPT-4', top_k=5)
Observação: [chunks com info GPT-4 128k Turbo...]
Pensamento: tenho ambos. Vou comparar.
Resposta final: Claude 3 tem 200k tokens; GPT-4 Turbo tem 128k. Claude 3 é maior.
📑 Resumo navegável
📐 Planner / Executor: separar plano de execução
Decomposição prévia
Planner/executor separa plano de execução. Primeiro passo: LLM gera plano em N steps. Segundo: executor segue passo por passo, sem o LLM redecidir. Mais auditável que ReAct para workflows conhecidos; menos flexível em casos novos.
plano = llm.generate(
f'Decomponha em 3-5 passos. Saída JSON: [{step, tool, args}].\n'
f'Tarefa: {tarefa}'
)
for passo in json.loads(plano):
resultado = TOOLS[passo['tool']](**passo['args'])
if not resultado.ok:
# plano falhou — replanejar OU abortar
break
📑 Resumo navegável
⛔ Controle de loop: max_iterations, max_tokens
Limites duros
Sem limites duros, agente loopa em produção e queima orçamento — bug clássico. Defina: max_iterations (10 default), max_tokens (orçamento total), timeout (wall clock). O primeiro que disparar para o agente. Sem isso, qualquer problema vira incidente de custo.
def react_agent(query, tools, max_iter=10, max_tokens=50_000, timeout=60):
inicio = time.time()
tokens = 0
for i in range(max_iter):
if tokens > max_tokens or time.time() - inicio > timeout:
return AbortReason.BUDGET_OR_TIMEOUT
# ... step do agente ...
📑 Resumo navegável
🔍 Tracing por step: a chave para debug
Cada decisão fica registrada
Tracing por step é a ferramenta mais valiosa de debug em agentes. Cada step vira span estruturado: timestamp, tool chamada, args sanitizados, resultado, próximo step. OpenTelemetry tem semconv específica para GenAI desde 2024. Local: salve em traces/<run_id>.jsonl.
{
'run_id': 'run-2026-05-03-abc',
'step': 2,
'ts': '2026-05-03T14:23:45Z',
'tool': 'buscar_em_corpus',
'args': {'query': '<redacted-len-23>', 'top_k': 5},
'result': {'ok': true, 'n_chunks': 5, 'tokens_in': 1234, 'tokens_out': 0},
'cost_usd': 0.0023,
'wall_ms': 487,
'next_action': 'continue'
}
📑 Resumo navegável
🛡️ Recovery: tool error, JSON inválido, loop
Padrões de robustez
Em produção, ferramentas falham e modelos respondem fora do schema. Sem recovery, agente quebra no primeiro hiccup. Padrões: retry com backoff em erros transitórios; fallback para LLM puro em tool down prolongado; abort com mensagem ao usuário quando recovery não é possível.
padrões de recovery
- ▸Tool retorna
retry_able=True→ tentar 3× com backoff exponencial. - ▸JSON malformado de output → pedir ao modelo 'reformate em JSON válido'.
- ▸Tool consistente em fail (3+ runs) → abrir issue + degradar para resposta sem tool.
- ▸Loop sem progresso (mesma ação 3×) → abortar com 'agent stuck'.
📑 Resumo navegável
💸 Orçamento de custo por agente
Budget cap antes de loop
Cap nativo do provedor é última linha de defesa. Você quer budget explícito por tarefa: antes de iniciar o agente, declare 'esta tarefa custa no máximo X tokens / $Y'. Aborta cedo, antes de virar incidente. Especialmente importante em agentes lançados por usuário externo.
@dataclass
class TaskBudget:
max_tokens: int = 20_000
max_cost_usd: float = 0.50
max_wall_seconds: int = 60
def run_with_budget(task, budget: TaskBudget):
metrics = ResourceTracker()
# incrementa metrics em cada step; aborta quando estoura
if metrics.cost_usd > budget.max_cost_usd:
raise BudgetExceeded(metrics)
📑 Resumo navegável
📑 Resumo navegável dos tópicos
1 🧭 ReAct: Reason + Act — Yao et al. 2022
2 📐 Planner / Executor: separar plano de execução — Decomposição prévia
3 ⛔ Controle de loop: max_iterations, max_tokens — Limites duros
4 🔍 Tracing por step: a chave para debug — Cada decisão fica registrada
5 🛡️ Recovery: tool error, JSON inválido, loop — Padrões de robustez
6 💸 Orçamento de custo por agente — Budget cap antes de loop
✓ O que FAZER
- ✓max_iterations sempre declarado (ex.: 10)
- ✓Tracing estruturado por step
- ✓Tools com erro tipado para recovery inteligente
- ✓Budget de custo explícito por tarefa
✗ O que NÃO fazer
- ✗Loop sem limite — disaster em produção
- ✗Print debug ad hoc
- ✗Exceção genérica que para o agente
- ✗Confiar só no cap do provedor
🚫 Quando NÃO usar
- •Tarefa one-shot estruturada: prompt + tool calling resolve sem loop.
- •Latência crítica (UX live): agente faz N roundtrips, latência multiplica.
- •Sem tracing: agente sem instrumentação é dívida técnica imediata.
💻 Exemplo de código
from fec_sdk import Message, MessageRole
from fec_sdk.adapters import get_adapter
def react_agent(query: str, tools, max_iter: int = 10) -> str:
msgs = [Message(role=MessageRole.USER, content=query)]
client = get_adapter("mock")
trace = []
for step in range(max_iter):
resp = client.chat(msgs, tools=tools)
trace.append({"step": step, "content": resp.content,
"tool_calls": [tc.model_dump() for tc in resp.tool_calls]})
if not resp.tool_calls:
return resp.content # agente decidiu parar
# Executa cada tool e adiciona resultado
for call in resp.tool_calls:
result = executar_tool(call) # com sandbox, retry, etc.
msgs.append(Message(role=MessageRole.TOOL, content=str(result), name=call.name))
return "max_iterations excedido" # fallback explícito
🏋️ Exercício hands-on
Implemente ReAct agent com 3 tools + max_iterations + tracing estruturado. Bateria de 30 traços-canário (golden FEC-GS-AGENT-v1) deve passar 30/30 sem loop infinito. Em exercicios/modulo-4-2/.
📚 Bibliografia
- Yao et al. (2022) — ReAct: Synergizing Reasoning and Acting
- Wang et al. (2023) — Plan-and-Solve Prompting
- Anthropic (2024) — Building Effective Agents
- OpenAI (2024) — Practices for Governing Agentic AI Systems
🎯 Resumo do Módulo
- ✓Agente é loop: ReAct (pensa-age) ou planner/executor.
- ✓max_iterations obrigatório — sem isso, loop em produção.
- ✓Tracing por step é pré-requisito de debugability.
- ✓Recovery: retry, abort, fallback — não exceção crua.
- ✓Budget de custo explícito por tarefa.
Próximo Módulo:
Multi-agente e MCP (beta)