Neste artigo, vamos explorar o padrão ReAct (Reasoning + Acting), uma das abordagens mais elegantes para construir agentes baseados em modelos de linguagem (LLMs). Enquanto o RAG resolve o problema do conhecimento, fornecendo ao modelo informações que ele não tem, o ReAct resolve o problema da tomada de decisão sequencial. Em outras palavras, como um modelo deve agir em um ambiente que muda a cada passo?

Clique aqui para abrir o código deste artigo no Google Colab

A Ideia Central do ReAct

Um LLM comum recebe uma pergunta e devolve uma resposta, ponto final. Não há iteração, não há percepção do ambiente, não há correção de curso.

O padrão ReAct muda isso ao estruturar cada ciclo de decisão em dois momentos. No primeiro, chamado de Thought (Pensamento), o modelo raciocina brevemente sobre a situação atual antes de agir. No segundo, chamado de Action (Ação), ele escolhe e executa uma ação com base nesse raciocínio.

Depois da ação, o ambiente retorna uma nova observação, que alimenta o próximo ciclo. Esse loop se repete até que o agente conclua a tarefa ou esgote o limite de passos.

A grande vantagem é que o modelo não age “no escuro”. Ao externalizar o raciocínio antes de cada ação, o agente consegue ajustar sua estratégia passo a passo, reagindo ao que o ambiente responde.

Preparação do Ambiente

1. Instalação

!pip install minigrid==3.0.0

2. Importações

import time
import os
import re
from google.colab import dados_usuario

import gymnasium as gym
import minigrid

from google import genai

3. Configuração do LLM

Vamos usar o Gemini, do Google, que oferece modelos gratuitos para experimentação. Basta criar uma chave de API no Google AI Studio e configurá-la no Colab:

Utilizando a API do Google Gemini

CHAVE_API = dados_usuario.get('GOOGLE_PESSOAL')

CLIENTE_GEMINI = genai.Client(api_key=CHAVE_API)
ID_MODELO_GEMINI = "gemini-2.5-flash"

4. Códigos Auxiliares

Antes de montar o agente, precisamos de dois blocos de código de suporte: um para representar o mapa de forma textual e outro para gerar respostas com o modelo.

Wrapper para Representação Textual

O primeiro é um wrapper que converte as observações do MiniGrid, que por padrão são arrays numéricos, em um mapa de texto legível. O código do wrapper em si envolve detalhes internos da biblioteca, que não vamos colocar aqui. O resultado é que cada célula do ambiente passa a ser representada por um único caractere, conforme a tabela abaixo.

Caractere Significado
# Parede
. Chão (célula vazia)
O Objetivo
L Lava
> v < ^ Agente (e sua direção)

^ cima v baixo < esquerda > direita

Função para Acessar o Modelo

O segundo utilitário é uma função simples que encapsula a chamada ao modelo:

# Função para facilitar a geração de respostas com o modelo
def gerar_resposta_gemini(prompt_sistema, prompt):
    resposta = CLIENTE_GEMINI.models.generate_content(
        model=ID_MODELO_GEMINI,
        contents=prompt,
        config={'system_instruction': prompt_sistema, 'temperature': 0.1}
    )
    return resposta.text

5. Testando o Ambiente

Antes de soltar o agente, vale explorar o ambiente manualmente para entender como ele funciona. Criamos o ambiente, encapsulamos com o wrapper e aplicamos uma sequência de ações aleatórias para ver o mapa mudar a cada passo:

ambiente_original = gym.make("MiniGrid-LavaCrossingS9N3-v0")

ambiente = WrapperTextoMiniGrid(ambiente_original)

obs, _ = ambiente.reset()

print("Observação inicial (mapa):")
print(obs)

# Loop aplicando sequência de ações aleatórias
for _ in range(10):
    acao = ambiente.action_space.sample()
    obs, recompensa, encerrado, truncado, info = ambiente.step(acao)

    print("-- ação (aleatória) --")
    print(acao)

    print("-- estado --")
    print(obs)

    if encerrado or truncado:
        break

O ambiente escolhido é o MiniGrid-LavaCrossingS9N3-v0, uma grade 9×9 com lava no caminho, onde o agente precisa encontrar uma rota segura até o objetivo. A cada passo, ele deve escolher uma entre três ações possíveis: GIRA_ANTI_HORARIO, GIRA_HORARIO ou FRENTE.

As Partes Importantes do Agente ReAct

O Prompt Principal (System Prompt)

O coração do agente ReAct é o system prompt, o conjunto de instruções fixas que o modelo recebe a cada passo do loop. Ele define as regras do ambiente, os símbolos do mapa, as ações disponíveis e, crucialmente, o formato de resposta esperado:

SYSTEM_PROMPT = """
Você controla um agente que navega em um ambiente quadriculado 2D (um "grid").
Seu objetivo é guiar o agente pelo ambiente para encontrar e alcançar a posição do objetivo.

# REGRAS DO AMBIENTE:
- Você receberá a observação da sua sala atual, na forma de um "grid" representado em texto.
- Cada célula da sala é representada por um único caractere, seguindo esta representação:
    - '#' (parede)
    - '.' (chão, célula vazia)
    - 'O' (posição do objetivo)
    - 'L' (lava)
- Indicadores do agente: serão colocados na representação do ambiente para mostrar sua posição atual e a direção para a qual você está voltado.
  - Pode ser um dos seguintes: '^' (cima), 'v' (baixo), '<' (esquerda) ou '>' (direita).
- Ações disponíveis:
    - GIRA_ANTI_HORARIO: Gira 90 graus no sentido anti-horário.
    - GIRA_HORARIO: Gira 90 graus no sentido horário.
    - FRENTE: Move um passo na direção para a qual você está voltado.

# FORMATO DA RESPOSTA:
Sua resposta deve ter apenas duas linhas e NADA MAIS. Não inclua texto conversacional.
PENSAMENTO: <Explique um breve raciocínio para justificar a escolha da próxima ação>
AÇÃO: <Uma das ações listadas acima>

## Exemplo de resposta:
PENSAMENTO: Estou de frente para o objetivo, que está na próxima célula adiante.
AÇÃO: FRENTE
"""

O modelo precisa de uma descrição precisa do ambiente, pois sem isso ele não consegue interpretar o mapa. E o formato de saída é rígido: PENSAMENTO: na primeira linha, AÇÃO: na segunda. Essa rigidez permite que o código extraia o pensamento e a ação da resposta de forma confiável com uma expressão regular simples.

O Prompt de Observação

A cada passo, além do system prompt fixo, o agente recebe um prompt de observação com o estado atual do mapa:

modelo_observacao = """# OBSERVAÇÃO ATUAL:
{SALA_ATUAL}
"""

O {SALA_ATUAL} é substituído pelo mapa textual gerado pelo ambiente naquele momento. Por exemplo, o modelo pode receber algo assim:

#########
#.......#
#.L.L.>.#
#.......#
#....O..#
#########

Com essa informação, o modelo sabe exatamente onde está, para onde está olhando e onde fica o objetivo.

Códigos Auxiliares do Agente

Precisamos de mais dois utilitários antes do loop. O primeiro mapeia os nomes textuais das ações para os códigos numéricos que o ambiente gym aceita:

# Para mapear as ações textuais em códigos numéricos aceitos pelo ambiente gym
MAPA_ACOES = {
    "GIRA_ANTI_HORARIO": 0,
    "GIRA_HORARIO": 1,
    "FRENTE": 2,
}

O segundo extrai o pensamento e a ação do texto de resposta do modelo usando expressões regulares:

def extrair_pensamento_e_acao(texto_resposta):
    match_pensamento = re.search(r"PENSAMENTO: (.*)", texto_resposta)
    str_pensamento = match_pensamento.group(1) if match_pensamento else "(não encontrado)"

    match_acao = re.search(r"AÇÃO: (GIRA_ANTI_HORARIO|GIRA_HORARIO|FRENTE)", texto_resposta)
    if match_acao:
        str_acao = match_acao.group(1)
    else:
        print(f"Erro: O modelo não formatou a ação corretamente. Resposta: {texto_resposta}")
        str_acao = ""

    return str_pensamento, str_acao

O Loop Principal

Aqui está o nosso agente explorando o ambiente:

Animação do Minigrid

O loop é onde o padrão ReAct ganha vida. A cada iteração, ele monta o prompt com o mapa atual, consulta o LLM, extrai o pensamento e a ação da resposta e executa essa ação no ambiente:

ambiente = WrapperTextoMiniGrid(ambiente_original)
# Reinicia o ambiente
obs, _ = ambiente.reset()
encerrado = False
truncado = False
contagem_passos = 0

# Loop Principal
print("Iniciando loop do Agente ReAct...\n")

print("-" * 20)
print("OBSERVAÇÃO INICIAL:")
print(obs)
print("-" * 20)

while not (encerrado or truncado) and contagem_passos < 20:
    # 1. Monta o prompt com a observação atual
    prompt_obs = modelo_observacao.format(SALA_ATUAL=obs)

    # 2. Obtém resposta do modelo de linguagem
    texto_resposta = gerar_resposta_gemini(PROMPT_SISTEMA, prompt_obs)

    # 3. Extrai o pensamento e a ação da resposta em texto
    pensamento, str_acao = extrair_pensamento_e_acao(texto_resposta)

    # 4. Exibe o ciclo ReAct
    print("-" * 20)
    print(f"Passo: {contagem_passos}")
    print("-" * 20)
    print("PENSAMENTO DO MODELO:", pensamento)
    print("AÇÃO ESCOLHIDA:", str_acao)

    # 5. Executa a ação
    numero_acao = MAPA_ACOES[str_acao]
    obs, recompensa, encerrado, truncado, _ = ambiente.step(numero_acao)
    contagem_passos += 1

    # 6. Exibe a nova observação resultante
    print("NOVA OBSERVAÇÃO:")
    print(obs)
    print("-" * 20)

    # 7. Pausa breve para acompanhar o agente em ação
    time.sleep(3.0)

if encerrado:
    if recompensa > 0:
        print("\nSUCESSO: O modelo chegou ao objetivo!")
    else:
        print("\nFALHA: O agente morreu ou falhou!")

ambiente.close()
print("Ambiente encerrado.")

O modelo nunca recebe memória de passos anteriores. A cada iteração, ele raciocina do zero a partir do mapa atual. Ainda assim, isso é suficiente para navegar o ambiente, porque o mapa já carrega toda a informação necessária para a decisão local.

O loop termina quando o agente chega ao objetivo (com recompensa positiva), cai na lava (com recompensa zero) ou esgota os 20 passos permitidos.

Clique aqui para abrir o código deste artigo no Google Colab

Conclusão

O padrão ReAct é uma forma poderosa de transformar um LLM em um agente capaz de agir em ambientes dinâmicos. Antes de cada ação, o modelo pensa em voz alta; depois age; depois observa o resultado e repete. Esse ciclo de pensar → agir → observar é o núcleo de muitos sistemas de agentes modernos.

O exemplo com MiniGrid é propositalmente simples, mas a mesma arquitetura pode ser aplicada a tarefas muito mais complexas, como navegar pela web, executar código, interagir com APIs ou gerenciar arquivos. O que muda é o ambiente e as ações disponíveis; a lógica do loop ReAct permanece a mesma.