mozak.tech Engenharia de IA Corporativa 29%

Parte II — Sistemas que Raciocinam e Agem de Forma Autônoma

2.2 — Padrões de Raciocínio Computacional: ReAct, Planejamento e Reflexão (ReAct & CoT)

Objetivo da Aula

Ao concluir esta aula, você será capaz de:

Implementar o padrão ReAct (Reasoning + Acting) com tool calls intercaladas

Implementar Plan-Execute: planejamento JSON seguido de execução determinística

Implementar Reflection: geração → crítica → melhoria cíclica

Selecionar o padrão adequado para cada tipo de tarefa

Comparar latência, custo e qualidade entre os 3 padrões

Por que isso importa

O pattern de raciocínio define como o agente pensa, não só o que faz. A mesma tarefa com ReAct, Plan-Execute e Reflection vai produzir resultados diferentes, com custos e latências diferentes.

Em 2024, “agente de IA” se tornou sinônimo de “loop com tool calls”. Mas profissionais que entendem os padrões de raciocínio tomam decisões muito melhores de design:

RAG pipeline para documento novo: ReAct (cada busca informa a próxima)

Pipeline de CI/CD assistido: Plan-Execute (plano validado antes de executar)

Geração de relatório executivo: Reflection (qualidade > velocidade)

Sistema de atendimento ao cliente: ReAct com guardrails

Geração de código com testes: Plan-Execute + Reflection no output

Entender os 3 padrões é a diferença entre um dev que “usa LLM” e um que arquiteta sistemas de IA.

Conceitos Fundamentais

Decisão de Arquitetura: ReAct vs Plan-Execute vs Reflection

São três estratégias de raciocínio com perfis distintos de custo, latência, qualidade e adaptabilidade:
• ReAct (raciocinar → agir → observar → repetir): adapta-se a resultados inesperados, boa para exploração e diagnóstico. Imprevisível em número de iterações — definir max_iterations como válvula de custo.
• Plan-Execute (planejar tudo → executar deterministicamente): auditável, estável, boa para fluxos irreversíveis onde o plano pode ser revisado por um humano antes da execução. Não se adapta a falhas durante a execução.
• Reflection (gerar → criticar → refinar): maximiza qualidade ao custo de 2N+1 chamadas ao LLM. Use quando qualidade da saída é crítica e latência é tolerável.
Regra geral: prefira Plan-Execute para ações de alto impacto; ReAct para diagnóstico e exploração; Reflection para geração de conteúdo de alta qualidade.

Padrão ReAct (Reasoning + Acting)

O nome vem do paper “ReAct: Synergizing Reasoning and Acting in Language Models” (Yao et al., 2022). A ideia: intercalar raciocínio e ação, onde cada ação informa o próximo raciocínio.

Thought: Preciso calcular juros compostos. Fórmula: A=P(1+r)^n

Action: calcular(10000 * (1+0.01)**24)

Observation: 12697.35

Thought: O resultado é R$12.697. Agora verifico a fórmula de juros simples para comparar.

Action: calcular(10000 * (1 + 0.01 * 24))

Observation: 12400

Thought: Tenho ambos os valores. Posso responder.

O Claude implementa ReAct naturalmente quando você: 1. Passa tools=TOOLS_SCHEMA 2. Adiciona response.content inteiro ao histórico (inclui os TextBlocks de raciocínio)

def react_agent(tarefa: str) -> str:

    mensagens = [{'role': 'user', 'content': f'Execute a tarefa passo a passo:\n{tarefa}'}]

    

    for _ in range(8):

        response = client.messages.create(

            model='claude-haiku-4-5-20251001',

            max_tokens=600,

            tools=TOOLS,

            messages=mensagens,

        )

        mensagens.append({'role': 'assistant', 'content': response.content})

        

        if response.stop_reason == 'end_turn':

            return next((b.text for b in response.content if hasattr(b, 'text')), '')

        

        tool_results = []

        for block in response.content:

            if block.type != 'tool_use':

                continue

            resultado = TOOL_FUNCTIONS[block.name](**block.input)

            tool_results.append({'type': 'tool_result', 'tool_use_id': block.id, 'content': resultado})

        

        mensagens.append({'role': 'user', 'content': tool_results})

    

    return 'Iterações esgotadas.'

Quando ReAct vence: tarefas exploratórias onde cada resultado muda o plano. Diagnóstico de bugs, pesquisa iterativa, tarefas onde o caminho não é conhecido antecipadamente.

Padrão Plan-Execute

Dois estágios separados: primeiro cria um plano completo, depois executa deterministicamente cada passo.

[Estágio 1 — PLAN]

Input: "Calcule juros compostos de R$10.000 a 1% por 24 meses"

Output (JSON): [

  {"passo": 1, "acao": "buscar_info", "parametro": "juros compostos fórmula"},

  {"passo": 2, "acao": "calcular", "parametro": "10000 * (1+0.01)**24"},

]



[Estágio 2 — EXECUTE]

Passo 1: buscar_info("juros compostos fórmula") → "A=P(1+r)^n"

Passo 2: calcular("10000 * (1+0.01)**24") → "12697.35"



[Estágio 3 — SYNTHESIZE]

Input: tarefa + resultados

Output: resposta final

def plan_execute_agent(tarefa: str) -> str:

    # Etapa 1: gerar plano JSON

    plan_response = client.messages.create(

        model='claude-haiku-4-5-20251001',

        max_tokens=400,

        messages=[{

            'role': 'user',

            'content': f'Crie um plano JSON: [{{"passo":1,"acao":"calcular|buscar_info","parametro":"..."}}]\nTAREFA: {tarefa}'

        }],

    )

    plano = json.loads(plan_response.content[0].text)

    

    # Etapa 2: executar deterministicamente

    resultados = []

    for passo in plano:

        func = TOOL_FUNCTIONS.get(passo['acao'])

        if func:

            chave = 'expressao' if passo['acao'] == 'calcular' else 'topico'

            resultado = func(**{chave: passo['parametro']})

            resultados.append(f"Passo {passo['passo']}: {resultado}")

    

    # Etapa 3: síntese

    sintese = client.messages.create(

        model='claude-haiku-4-5-20251001',

        max_tokens=300,

        messages=[{'role': 'user', 'content': f'Tarefa: {tarefa}\nResultados: {resultados}\nSintetize.'}],

    )

    return sintese.content[0].text

Vantagem: o plano pode ser validado por um humano antes de executar — útil para ações irreversíveis. Desvantagem: se o passo 2 falhar, o plano não se adapta (ao contrário do ReAct).

Quando Plan-Execute vence: tarefas bem definidas onde o plano pode ser auditado. Pipelines de dados, geração de relatórios com passos fixos, automação de workflows.

Padrão Reflection

Gera uma resposta inicial, depois a critica e melhora iterativamente.

[Turno 1] Gerar resposta inicial

[Turno 2] Criticar a resposta: "Faltou dados quantitativos no ponto 2"

[Turno 3] Melhorar baseado na crítica

[Turno 4] Criticar novamente... (até N ciclos)

def reflection_agent(tarefa: str, ciclos: int = 2) -> str:

    # Gerar resposta inicial

    resposta = client.messages.create(

        model='claude-haiku-4-5-20251001',

        max_tokens=400,

        messages=[{'role': 'user', 'content': tarefa}],

    ).content[0].text

    

    for ciclo in range(ciclos):

        # Criticar

        critica = client.messages.create(

            model='claude-haiku-4-5-20251001',

            max_tokens=300,

            messages=[{

                'role': 'user',

                'content': f'Critique esta resposta e liste 2-3 melhorias:\n\nRESPOSTA: {resposta}'

            }],

        ).content[0].text

        

        # Melhorar

        resposta = client.messages.create(

            model='claude-haiku-4-5-20251001',

            max_tokens=400,

            messages=[{

                'role': 'user',

                'content': f'Melhore a resposta com base nas críticas:\n\nResposta: {resposta}\nCríticas: {critica}'

            }],

        ).content[0].text

    

    return resposta

Custo: 2N+1 chamadas de API para N ciclos de reflexão. Para ciclos=2: 5 chamadas. Use com parcimônia.

Quando Reflection vence: geração de texto de alta qualidade (relatórios executivos, documentação, propostas), código complexo, qualquer saída onde “bom o suficiente na primeira tentativa” não serve.

Aprofundamento Técnico

Comparativo Empírico dos 3 Padrões

Para a tarefa “Calcule R$10.000 a 1% a.m. por 24 meses com juros compostos”:

Métrica

ReAct

Plan-Execute

Reflection

Chamadas de API

2-4

3 (fixo)

5 (2 ciclos)

Latência

Variável

Previsível

Alta

Qualidade

Boa

Boa

Excelente

Adaptabilidade

Alta

Baixa

N/A

Custo

Médio

Baixo-médio

Alto

ReAct com Tool Calling Paralelo

O Claude pode chamar múltiplas tools em paralelo em uma iteração (parallelism in tool use):

Iteração 1:

  → buscar_info('juros compostos')  ← chamadas paralelas

  → calcular('10000 * (1+0.01)**24')

Isso acontece quando o Claude vê que as ações são independentes. Para habilitar, o SDK do Python não precisa de configuração adicional — o Claude decide automaticamente. Os tool_use blocks vão aparecer múltiplos no mesmo response.content.

Seu loop já suporta isso:

for block in response.content:

    if block.type != 'tool_use':

        continue

    # Processa todos os tool_use blocks da iteração

Plan-Execute com Validação de Plano

Em produção, você pode validar o plano antes de executar:

def plan_execute_com_validacao(tarefa: str) -> str:

    plano = gerar_plano(tarefa)

    

    # Validar: máximo de passos, ações permitidas

    if len(plano) > 10:

        raise ValueError(f'Plano muito longo: {len(plano)} passos')

    

    acoes_permitidas = {'calcular', 'buscar_info'}

    for passo in plano:

        if passo['acao'] not in acoes_permitidas:

            raise ValueError(f'Ação não permitida: {passo["acao"]}')

    

    # Human-in-the-loop: mostrar plano e confirmar

    print(f'Plano gerado:\n{json.dumps(plano, indent=2, ensure_ascii=False)}')

    confirmacao = input('Confirmar execução? (s/n): ')

    if confirmacao.lower() != 's':

        return 'Execução cancelada pelo usuário.'

    

    return executar_plano(plano, tarefa)

Reflection com Critério de Parada

def reflection_com_criterio(tarefa: str, max_ciclos: int = 3) -> str:

    resposta = gerar_resposta_inicial(tarefa)

    

    for ciclo in range(max_ciclos):

        avaliacao = client.messages.create(

            model='claude-haiku-4-5-20251001',

            max_tokens=100,

            messages=[{

                'role': 'user',

                'content': f'Esta resposta está boa o suficiente? Responda apenas: APROVADO ou MELHORAR.\n\nResposta: {resposta}'

            }],

        ).content[0].text.strip()

        

        if 'APROVADO' in avaliacao.upper():

            print(f'Aprovado no ciclo {ciclo+1}')

            break

        

        resposta = melhorar_resposta(resposta, tarefa)

    

    return resposta

Exemplos Anotados

Exemplo 1: ReAct para Cálculo Financeiro

tarefa = 'Calcule quanto vale R$10.000 em juros compostos com 1% ao mês por 24 meses. Mostre o cálculo passo a passo.'



# O Claude vai (tipicamente) fazer:

# Iter 1: buscar_info('juros compostos') para confirmar fórmula

# Iter 2: calcular('10000 * (1+0.01)**24') 

# Iter 3 (end_turn): explicar resultado com contexto



# Output esperado:

# "R$10.000 a 1% ao mês por 24 meses = R$12.697,35 (juros compostos)

#  Isso é R$2.697,35 de juros, 26,97% de crescimento.

#  Para comparação, juros simples dariam apenas R$12.400."

Exemplo 2: Diferença de Qualidade — Reflection

# Sem reflection:

resultado_simples = 'Juros compostos têm crescimento exponencial. A fórmula é A=P(1+r)^n.'



# Com 2 ciclos de reflection:

resultado_refletido = """

Juros compostos são fundamentais em finanças. A fórmula A=P(1+r)^n mostra como

o capital cresce exponencialmente:



- P = R$10.000 (principal)

- r = 0,01 (1% ao mês)  

- n = 24 (24 meses = 2 anos)

- A = R$10.000 × (1,01)^24 = R$12.697,35



Comparado com juros simples (R$12.400), a diferença de R$297,35 parece pequena,

mas em 120 meses (10 anos) seria: compostos R$33.004 vs simples R$22.000 — 

uma diferença de R$11.004 (50% a mais).



Regra prática: taxa 1% ao mês ≈ 12,68% ao ano (não 12%, por causa do efeito composto).

"""

Padrões e Armadilhas

Padrões

Padrão 1: Instrução explícita de raciocínio no user message para ReAct

# Bom: pede raciocínio explícito

'Pense passo a passo antes de agir. Para cada passo, explique o raciocínio.'



# Menor qualidade: só pede resultado

'Resolva esta tarefa.'

Padrão 2: Plan-Execute com JSON Schema estrito

'Formato: [{"passo": int, "acao": "calcular|buscar_info", "parametro": "string"}]'

Schema explícito produz JSON válido em 95%+ dos casos. Sem schema, o modelo pode retornar JSON com campos extras ou nomes diferentes.

Padrão 3: Número de ciclos de Reflection como parâmetro configurável Para produção, 1-2 ciclos é suficiente. Para geração crítica (contratos, relatórios médicos), 3. Nunca hardcode.

Armadilhas

⚠️ Armadilha 1: Usar Reflection para tudo Reflection usa 2N+1 chamadas. Para uma query simples (“qual o capital do Brasil?”), é desperdício. Reserve para outputs de alta qualidade.

⚠️ Armadilha 2: Plan-Execute sem tratamento de erro no plan_response

# PERIGOSO: json.loads() vai explodir se o Claude não retornar JSON puro

plano = json.loads(plan_response.content[0].text)



# SEGURO: parse com fallback

try:

    texto = plan_response.content[0].text

    # Extrair JSON mesmo se tiver texto ao redor

    inicio = texto.find('[')

    fim = texto.rfind(']') + 1

    plano = json.loads(texto[inicio:fim])

except Exception:

    plano = [{'passo': 1, 'acao': 'buscar_info', 'parametro': tarefa}]

⚠️ Armadilha 3: ReAct sem limite de iterações em tarefas abertas Tarefas vagas como “pesquise tudo sobre IA” podem gerar 20+ iterações. Limite de 8 é seguro para a maioria dos casos.

⚗ Laboratório prático — mozak.tech
Se não for realizar o laboratório, pule para o próximo capítulo.

Ponte para o Lab

O starter desta unidade implementa os 3 padrões completos — sem TODOs de código.

Rodando:

python starter.py

O que observar: 1. ReAct: quantas iterações o Claude usa? O raciocínio (TextBlocks) aparece no output? 2. Plan-Execute: o plano JSON gerado faz sentido? Quantos passos? 3. Reflection: a resposta após 2 ciclos é visivelmente melhor que a inicial?

Experimentos para aprofundar: - Mude ciclos=2 para ciclos=1 e ciclos=3 em Reflection — observe a diferença - Adicione print(resposta) após cada ciclo para ver a evolução - Implemente o critério de parada antecipada do Exemplo “Reflection com Critério de Parada” - Para Plan-Execute: adicione validação que rejeita planos com mais de 5 passos

Agora você está pronto para o lab.