Agent Patterns: ReAct, Reflexion e Tree of Thoughts
- ⬜🧩 RAG: por que "só jogar tudo no LLM" não funciona(Engenharia AI-Native)
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
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.
| Atributo | Workflow | Agent |
|---|---|---|
| Controle de fluxo | Código fixo | LLM decide |
| Previsibilidade | Alta | Baixa |
| Custo por execução | Previsível | Variável, pode estourar |
| Debug | Direto — cada passo é código | Mais difícil — trace do modelo |
| Quando usar | 80% dos casos reais | Tarefa com caminho desconhecido a priori |
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).
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)
# 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")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).
# 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
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)
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.
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
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écnica | Chamadas por query | Ganho típico |
|---|---|---|
| Zero-shot | 1 | baseline |
| Chain of Thought | 1 (mais tokens) | +10-20pp |
| Self-consistency (N=5) | 5 | +3-8pp sobre CoT |
| Best-of-N + judge | 5-10 | +5-12pp sobre CoT |
| Tree of Thoughts (B=5, D=3) | 30-100+ | +10-30pp em problemas de busca |
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.
# 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ão | Problema que resolve | Custo |
|---|---|---|
| max_steps + budget de tokens | Loops infinitos, estouro de custo | Zero — é só um contador |
| Tool result truncation | Tool devolve 100k tokens e estoura contexto | Um slice do resultado |
| Retry com backoff em tool | Flakiness de APIs externas | 1-2 tentativas extra |
| Human-in-the-loop em ações irreversíveis | Agent manda email errado ou deleta dados | UX de confirmação |
| Trace estruturado (LangSmith, Langfuse) | Debug do raciocínio em falhas raras | Infra de observability |
| Prompt caching da system prompt+tools | Custo alto em agents com muitas tools | Config; reduz custo em ~90% |
Perguntas típicas
❓ Preciso de LangChain/LlamaIndex para fazer agent?
❓ Agent com 20 tools funciona?
❓ Como testar um agent?
❓ Agent pode ser determinístico?
Quiz rápido
4 perguntas · Acerte tudo e ganhe o badge 🎯 Gabarito