🧠FFVAcademy
🤖

Agent Patterns: ReAct, Reflexion e Tree of Thoughts

18 min de leitura·+90 XP
Pré-requisitos (0/1)0%

Recomendamos completar os pré-requisitos antes de seguir, mas nada te impede de continuar.

"Agent" virou palavra-ônibus — engloba desde um loop ReAct simples até swarms hierárquicos com dezenas de nós. Este módulo organiza os padrões que se consolidaram entre 2022 e 2026: ReAct, Reflexion, Plan-and-Execute, Tree of Thoughts, Router. Para cada um: como funciona, quando brilha, quando é overkill.

Agent vs Workflow: a pergunta antes da pergunta

💡
Definições (Anthropic, "Building Effective Agents", 2024):
Workflow = LLM(s) encadeados em ordem fixa, determinada pelo código.
Agent = LLM decide dinamicamente próximos passos e tools, em loop, até atingir objetivo ou limite.
AtributoWorkflowAgent
Controle de fluxoCódigo fixoLLM decide
PrevisibilidadeAltaBaixa
Custo por execuçãoPrevisívelVariável, pode estourar
DebugDireto — cada passo é códigoMais difícil — trace do modelo
Quando usar80% dos casos reaisTarefa com caminho desconhecido a priori
⚠️
Anti-padrão comum: transformar tudo em "agent" porque soa moderno. Um pipeline de extrair dados + resumir + mandar e-mail é workflow — não precisa de loop ReAct. Agent só quando a sequência de tools não é conhecida.

ReAct: o padrão fundacional

ReAct (Reasoning + Acting) foi o paper que legitimou o LLM como agente. A estrutura é um loop onde o modelo alterna Thought (raciocínio em NL), Action (tool call) e Observation (resultado da tool).

🗺️ ReAct loop

 User query
    │
    ▼
 ┌────────────────────────────────────┐
 │ Thought: "Preciso do preço atual.  │
 │  Vou buscar no sistema de cotação."│
 │ Action: get_price(ticker="PETR4")  │
 └──────────────┬─────────────────────┘
                │ (call tool)
                ▼
 ┌────────────────────────────────────┐
 │ Observation: 38.21                 │
 └──────────────┬─────────────────────┘
                │
                ▼
 ┌────────────────────────────────────┐
 │ Thought: "Tenho o preço. Falta o   │
 │  histórico. Vou chamar hist_price."│
 │ Action: hist_price(ticker, days=30)│
 └──────────────┬─────────────────────┘
                │
                ▼
      (Observation → Thought → ... → Final Answer)
python
# ReAct canônico com tool_use do Anthropic (estrutura idêntica em OpenAI)
from anthropic import Anthropic

client = Anthropic()

TOOLS = [
    {
        "name": "get_price",
        "description": "Retorna preço atual de um ticker.",
        "input_schema": {
            "type": "object",
            "properties": {"ticker": {"type": "string"}},
            "required": ["ticker"],
        },
    },
    {
        "name": "hist_price",
        "description": "Retorna histórico de preços.",
        "input_schema": {
            "type": "object",
            "properties": {
                "ticker": {"type": "string"},
                "days":   {"type": "integer"},
            },
            "required": ["ticker", "days"],
        },
    },
]

def react_agent(user_query: str, max_steps: int = 8) -> str:
    messages = [{"role": "user", "content": user_query}]
    for _ in range(max_steps):
        r = client.messages.create(
            model="claude-sonnet-4-6",
            max_tokens=1024,
            tools=TOOLS,
            messages=messages,
        )
        if r.stop_reason == "end_turn":
            return r.content[0].text           # resposta final
        if r.stop_reason == "tool_use":
            tool_block = next(b for b in r.content if b.type == "tool_use")
            result = execute_tool(tool_block.name, tool_block.input)
            messages.append({"role": "assistant", "content": r.content})
            messages.append({
                "role": "user",
                "content": [{
                    "type": "tool_result",
                    "tool_use_id": tool_block.id,
                    "content": str(result),
                }],
            })
    raise RuntimeError("max_steps atingido — agent não convergiu")
Sempre limite max_steps. Loops infinitos de tool call são a causa #1 de estouro de budget em produção — seja porque o modelo entrou em loop, seja porque uma tool está devolvendo dado inútil que ele re-tenta.

Reflexion: self-critique com memória

Reflexion adiciona uma fase de crítica verbal após falha, armazenada em memória, que entra no contexto da próxima tentativa. Funciona bem em tarefas com sinal claro de sucesso (testes, verificadores).

python
# Reflexion — versão didática com oracle (rodar_testes)
def reflexion_solve(task: str, oracle, max_attempts: int = 3) -> str:
    memory: list[str] = []          # lições aprendidas entre tentativas
    for attempt in range(max_attempts):
        prompt = (
            f"Tarefa: {task}\n\n"
            f"Lições de tentativas anteriores:\n" +
            "\n".join(f"- {m}" for m in memory) +
            "\n\nProduza solução."
        )
        solution = llm_call(prompt)
        passed, feedback = oracle(solution)      # ex: rodar testes
        if passed:
            return solution

        # Reflexão: LLM critica a tentativa à luz do feedback
        reflection = llm_call(
            f"Tarefa: {task}\nSolução tentada: {solution}\n"
            f"Feedback do oracle: {feedback}\n\n"
            "Em 1-2 frases, o que deu errado e o que fazer diferente na próxima tentativa?"
        )
        memory.append(reflection)
    return solution   # última tentativa, mesmo se falhou

📋 Agent escreve código — testes unitários existem

Reflexion

Oracle (testes) dá sinal discreto. Reflexion vira um loop de debug assistido — lê traceback, reflete, corrige. Papers mostram ganho de 10-30pp em SWE-bench vs ReAct puro.

📋 Agent responde pergunta subjetiva (ex: resumo)

ReAct puro ou best-of-N

Sem oracle confiável, self-critique vira concordar consigo mesmo. Melhor amostrar N respostas e usar LLM-as-judge (ou heurística) para escolher a melhor.

Plan-and-Execute: planejador separado do executor

Em tarefas longas, misturar planejamento e execução na mesma chamada polui o contexto. Plan-and-Execute divide: um LLM (forte) cria o plano, um LLM (barato) executa passo a passo. Reduz custo e melhora robustez em tarefas de 5+ passos.

🗺️ Plan-and-Execute

  User query
      │
      ▼
 ┌─────────────────┐
 │  Planner LLM    │  (Opus/GPT-5 — caro, alta qualidade)
 │  → plan[1..N]   │
 └────────┬────────┘
          │
          ├───► Step 1: executor_llm + tools
          ├───► Step 2: executor_llm + tools
          ├───► Step 3: executor_llm + tools
          │
          ▼
 ┌─────────────────┐
 │ Replan on fail? │  ← se passo falha, planner volta
 └────────┬────────┘
          ▼
     Final answer
💡
Plano explícito é auditável: você pode mostrar ao usuário o que o agente vai fazer antes de executar. Em fluxos com ações irreversíveis (enviar email, fechar ticket), plan + approval humano antes de execute é padrão de segurança.

Tree of Thoughts (ToT): busca em árvore

ToT trata raciocínio como busca. Em cada nó, o LLM propõe K próximos passos; um avaliador (outro LLM ou heurística) pontua cada galho; mantém os top-B por nível (beam search). Custo explode — só vale em problemas de busca com estado avaliável.

TécnicaChamadas por queryGanho típico
Zero-shot1baseline
Chain of Thought1 (mais tokens)+10-20pp
Self-consistency (N=5)5+3-8pp sobre CoT
Best-of-N + judge5-10+5-12pp sobre CoT
Tree of Thoughts (B=5, D=3)30-100++10-30pp em problemas de busca
⚠️
Em 2026 o padrão prático virou best-of-N + judge ou self-consistency — captura 80% do ganho do ToT com 10% do custo. ToT canônico sobrou em benchmarks de pesquisa.

Router: o padrão mais subestimado

Router = classificador LLM barato que manda a query para o sub-pipeline certo. Em produtos reais, 60-80% das queries são "simples" (FAQ, saudação, help básico) e não precisam de agent caro. Router separa.

python
# Router barato com Haiku 4.5 (ou gpt-4o-mini) decidindo rota
from anthropic import Anthropic
client = Anthropic()

ROUTES = ["faq", "search_rag", "math_code", "book_meeting", "refuse"]

def route(query: str) -> str:
    r = client.messages.create(
        model="claude-haiku-4-5-20251001",
        max_tokens=30,
        system=(
            "Classifique a intenção do usuário em UMA das rotas: " +
            ", ".join(ROUTES) +
            ". Responda apenas com a palavra da rota."
        ),
        messages=[{"role": "user", "content": query}],
    )
    choice = r.content[0].text.strip().lower()
    return choice if choice in ROUTES else "search_rag"

# Dispatch
def handle(query: str) -> str:
    route_name = route(query)
    match route_name:
        case "faq":          return answer_from_faq_cache(query)
        case "search_rag":   return rag_pipeline(query)
        case "math_code":    return code_agent(query)
        case "book_meeting": return tool_chain_booking(query)
        case "refuse":       return "Não posso ajudar com isso."

Reliability patterns: o que não aparece nos papers

PadrãoProblema que resolveCusto
max_steps + budget de tokensLoops infinitos, estouro de custoZero — é só um contador
Tool result truncationTool devolve 100k tokens e estoura contextoUm slice do resultado
Retry com backoff em toolFlakiness de APIs externas1-2 tentativas extra
Human-in-the-loop em ações irreversíveisAgent manda email errado ou deleta dadosUX de confirmação
Trace estruturado (LangSmith, Langfuse)Debug do raciocínio em falhas rarasInfra de observability
Prompt caching da system prompt+toolsCusto alto em agents com muitas toolsConfig; reduz custo em ~90%

Perguntas típicas

Preciso de LangChain/LlamaIndex para fazer agent?

Não. SDKs nativos (Anthropic, OpenAI) já entregam tool_use com poucas linhas. Frameworks ajudam em orquestração de multi-agent, mas adicionam camada que pode esconder bugs. Para agent simples, comece com SDK nativo + 50 linhas de loop próprio. Suba para framework quando sentir a fricção.

Agent com 20 tools funciona?

Em modelos fortes (Opus, GPT-5), sim — mas a precisão na escolha cai com número de tools. Mitigação: agrupe tools em "categorias" e use um router de 2 níveis (categoria → tool específica), ou use MCP server que expõe só as tools relevantes à task.

Como testar um agent?

Três camadas: (1) unit em cada tool (função pura), (2) integração em trajectories canônicas (mock tool results, checa passos), (3) end-to-end em golden set de tasks reais com oracle quando possível. Framework: pytest + snapshot das trajectories para detectar drift.

Agent pode ser determinístico?

Em temperature=0, quase — mas ordering de tool_use e variações de cache ainda causam drift. Para fluxos que exigem determinismo, use workflow. Se precisa de agent, aceite variância e meça distribuição (p50/p95 de custo/passos), não caso único.
Take-aways. Antes de "é agent", pergunte "é workflow?" — 80% das vezes workflow cobre. ReAct é a base; Reflexion brilha com oracle; Plan-and-Execute para tarefas longas; ToT é caro e especializado; Router é o ganho barato que todo mundo esquece. Reliability (max_steps, retry, truncation, HIL) não é opcional em produção. Próximo: multi-agent e orchestrator-worker — quando dividir a tarefa entre vários agents vale.
🧩

Quiz rápido

4 perguntas · Acerte tudo e ganhe o badge 🎯 Gabarito

Continue lendo