O loop ReAct e native tool use

TL;DR

ReAct (Reasoning + Acting), introduzido por Yao et al. em 2022, virou o padrão mental de agents. Combina raciocínio (“thoughts”) com ações (tool calls) e observações em loop. Em 2026, ninguém mais formata ReAct textualmente — LLMs modernos têm native tool use (Anthropic, OpenAI, Google), e o loop é gerenciado pelo SDK. Mas o mental model é o mesmo. Um agent é um while loop que termina quando o LLM diz “acabei” (end_turn) ou bate max_steps.

ReAct — o padrão original

Objective: "Find the 5 most recent papers about context engineering and summarize them."
 
Thought: I need to search for recent papers on this topic.
Action: web_search(query="context engineering LLM agents 2025", limit=10)
Observation: [lista de 10 resultados]
 
Thought: I have candidates. I need to read the top 5.
Action: read_url(url="https://arxiv.org/abs/2506.12345")
Observation: [conteúdo do paper 1]
 
... (repete para papers 2-5)
 
Thought: I have enough. Let me synthesize.
Final answer: [sumário estruturado dos 5 papers]

Três elementos sempre presentes:

ElementoFunção
ThoughtRaciocínio sobre próximo passo
ActionTool call concreta
ObservationResultado da tool, alimentado de volta

Native tool use — como funciona em 2026

LLMs modernos não pedem ReAct textual — eles têm tool use nativo, com schema estruturado.

from anthropic import Anthropic
 
client = Anthropic()
 
tools = [
    {
        "name": "web_search",
        "description": "Search the web for recent articles and pages",
        "input_schema": {
            "type": "object",
            "properties": {
                "query": {"type": "string"},
                "limit": {"type": "integer", "default": 10}
            },
            "required": ["query"]
        }
    }
]
 
def run_agent(objective: str, max_steps: int = 15):
    messages = [{"role": "user", "content": objective}]
 
    for step in range(max_steps):
        response = client.messages.create(
            model="claude-sonnet-4-6",
            max_tokens=4096,
            tools=tools,
            messages=messages
        )
        messages.append({"role": "assistant", "content": response.content})
 
        if response.stop_reason == "end_turn":
            final = next((b.text for b in response.content if b.type == "text"), "")
            return final
 
        tool_results = []
        for block in response.content:
            if block.type == "tool_use":
                result = execute_tool(block.name, block.input)
                tool_results.append({
                    "type": "tool_result",
                    "tool_use_id": block.id,
                    "content": result
                })
        messages.append({"role": "user", "content": tool_results})
 
    raise RuntimeError("Max steps exceeded")

Esse é o “hello world” de um agent. Todos os frameworks são variações disso.

Os 4 stop reasons que importam

stop_reasonSignificadoO que fazer
end_turnAgent terminou — tem resposta finalSair do loop
tool_useAgent quer chamar toolExecutar + alimentar resultado
max_tokensGeração foi cortadaAumentar max_tokens ou re-prompt
stop_sequenceBateu em sequência de paradaTratar conforme caso

Padrões dentro do loop

1. Tool use paralelo

LLMs modernos podem retornar múltiplas tool calls num mesmo turno. Vantagem: latência menor (executar em paralelo no seu código).

2. Chain-of-thought entrelaçado

Em modelos com extended thinking (Claude 4+), o reasoning fica em block separado, invisível ao próximo turno mas usado pelo modelo para decisão.

3. Self-correction

Quando tool retorna erro, agent vê e tenta de novo:

Action: read_url("https://fake.com/missing")
Observation: 404 not found
Thought: URL deu 404. Vou tentar outra fonte.
Action: read_url("https://other.com/article")

Padrão poderoso. Requer descrições de erro claras (ver 03 - Tool design — princípios e categorias).

O loop visualizado em produção

graph LR
    A["LLM decide"] --> B{"stop_reason?"}
    B -->|end_turn| Z["Return final"]
    B -->|tool_use| C["Execute tools"]
    C --> D["Append results"]
    D --> A
    B -->|max_tokens| E["Resume / chunked"]
    A -.->|max_steps| F["Raise: stuck"]

Pitfalls do loop

1. max_steps ausente

Agent decide errado, fica em loop, queima budget. Sempre defina max_steps. Padrão: 15-30.

2. Tool result silencioso

Tool retorna None, agent não sabe que falhou, repete. Fix: sempre retorne mensagem informativa.

3. Output gigante de tool

Tool retorna 50K tokens. Atenção dilui (03 - Context rot e atenção diluída). Fix: truncate, paginar, ou retornar só relevante.

4. Loop sem progresso

Agent chama mesma tool com mesmos args repetidamente. Fix: detectar duplicação, abortar, injetar prompt “tente algo diferente”.

Variantes além de ReAct

PadrãoDiferença
Plan-then-executePlano completo primeiro, depois executa. Menos flexível, mais previsível
Self-askDecompõe pergunta em sub-perguntas, responde cada uma
ReflexionReflete sobre falhas antes de tentar de novo (custo alto)
Tree-of-ThoughtExplora múltiplos caminhos, escolhe melhor (custo muito alto)

ReAct continua sendo o default certo na maioria dos casos.

Veja também

Referências

  • Yao et al.ReAct: Reasoning and Acting (arxiv:2210.03629)
  • Schick et al.Toolformer (arxiv:2302.04761)
  • AnthropicTool use documentation (2026)
  • OpenAIFunction calling guide (2026)